从零开始之驱动发开、linux驱动(二十六、三星平台framebuffer)

从零开始系列 同时被 2 个专栏收录
86 篇文章 39 订阅

一、总述

本驱动是基于三星的s5pv210处理器分析,对于三星平台所有的framebuffer驱动基本都是一样。对应于s5pv210中的内部外设Display Controller (FIMD)模块。

framefuffer驱动是基于字符设备驱动,在使用platform总线封装编写。

二、驱动源码的分布

1、驱动代码的源文件分布:

(1):drivers/video/fb-dev/s3c-fb.c,  驱动主体框架。 

(2):arch/arm/mach-s5pv210/mach-smdkv210.c,负责提供platform_device,这个文件里面提供了很多的基于platform总线编写的驱动需要的platform_device,mach文件是每一个移植好的内核都会提供这个文件的.

(3):arch/arm/plat-samsung/devs.c,为platform_device提供一些硬件描述信息。
 

三星平台的framebuffer设备基于平台总线创建。

分为两部分:

一部分是和三星的所有处理器通用的操作接口,和配置接口,在platform bus中称作driver。

另一部分是和LCD相关的一些时序,分辨率等容易变化的参数,在platform bus中称作device。

 

我们这里先分析和LCD相关的对不同硬件,可能变化的参数,当然这些参数都是我们上节fb_info中的参数。

首先我们先看几个时序相关的参数:

static struct s3c_fb_pd_win smdkv210_fb_win0 = {
	.max_bpp	= 32,            /* 最大支持的bpp */
	.default_bpp	= 24,        /* 默认的每像素的位数 */
	.xres		= 800,           /* 横向分辨率 */
	.yres		= 480,           /* 纵向分辨率 */
};

static struct fb_videomode smdkv210_lcd_timing = {
	.left_margin	= 13,        /* 六个时序参数,后面一节会移植到我板子,再分析 */
	.right_margin	= 8,
	.upper_margin	= 7,
	.lower_margin	= 5,
	.hsync_len	= 3,
	.vsync_len	= 1,
	.xres		= 800,            /* 横向分辨率 */
	.yres		= 480,            /* 纵向分辨率 */
};


static struct s3c_fb_platdata smdkv210_lcd0_pdata __initdata = {
	.win[0]		= &smdkv210_fb_win0,
	.vtiming	= &smdkv210_lcd_timing,
	.vidcon0	= VIDCON0_VIDOUT_RGB | VIDCON0_PNRMODE_RGB,   /* 用于控制面板数据格式,三星目前支持RGB和I80 */
	.vidcon1	= VIDCON1_INV_HSYNC | VIDCON1_INV_VSYNC,      /*  用于控制面板数据输出,即控制HSYNC和VSYNC是否要翻转 */
	.setup_gpio	= s5pv210_fb_gpio_setup_24bpp,        /* 用于初始化LCD相关的gpio模式 */
};


void s5pv210_fb_gpio_setup_24bpp(void)
{
    /* 设置LCD相关的28个寄存器,24bpp数据线+4条时钟或时钟极性相关的 */
	s5pv210_fb_cfg_gpios(S5PV210_GPF0(0), 8);
	s5pv210_fb_cfg_gpios(S5PV210_GPF1(0), 8);
	s5pv210_fb_cfg_gpios(S5PV210_GPF2(0), 8);
	s5pv210_fb_cfg_gpios(S5PV210_GPF3(0), 4);

	/* Set DISPLAY_CONTROL register for Display path selection.
	 *
	 * ouput   |   RGB   |   I80   |   ITU
	 * -----------------------------------
	 *  00     |   MIE   |  FIMD   |  FIMD
	 *  01     | MDNIE   | MDNIE   |  FIMD
	 *  10     |  FIMD   |  FIMD   |  FIMD
	 *  11     |  FIMD   |  FIMD   |  FIMD
	 */
	writel(0x2, S5P_MDNIE_SEL);    /* 显示模式 */
}


上面的参数,是通过下面函数,进行设置到平台设备的数据中的


static void __init smdkv210_machine_init(void)
{
	......
	s3c_fb_set_platdata(&smdkv210_lcd0_pdata);
    ......
}

/* 该函数的作用是把我们的参数,放到平台数据设备的data里面 */
void __init s3c_fb_set_platdata(struct s3c_fb_platdata *pd)
{
	s3c_set_platdata(pd, sizeof(struct s3c_fb_platdata),
			 &s3c_device_fb);
}

/* 平台设备 */
struct platform_device s3c_device_fb = {
	.name		= "s3c-fb",        /* 平台设备需要driver的name一致才能匹配 */
	.id		= -1,
	.num_resources	= ARRAY_SIZE(s3c_fb_resource),    /* 资源大小 */
	.resource	= s3c_fb_resource,                    /* 资源类型 */
	.dev		= {
		.dma_mask		= &samsung_device_dma_mask,    /* dma设备 */
		.coherent_dma_mask	= DMA_BIT_MASK(32),
	},
};


/* 把我们上面的smdkv210_lcd0_pdata放到平台设备的数据里 */
void __init *s3c_set_platdata(void *pd, size_t pdsize,
			      struct platform_device *pdev)
{
	void *npd;

	if (!pd) {        /* 判断有效 */
		/* too early to use dev_name(), may not be registered */
		printk(KERN_ERR "%s: no platform data supplied\n", pdev->name);
		return NULL;
	}

	npd = kmemdup(pd, pdsize, GFP_KERNEL);    /* 动态申请空间,拷贝一份上面传进来的参数 */
	if (!npd) {
		printk(KERN_ERR "%s: cannot clone platform data\n", pdev->name);
		return NULL;
	}

	pdev->dev.platform_data = npd;        /* 绑定到平台设备的数据里 */
	return npd;
}

/* 看一下和lcd相关的资源有哪些 */
static struct resource s3c_fb_resource[] = {
	[0] = DEFINE_RES_MEM(S3C_PA_FB, SZ_16K),    /* 控制寄存器等 */
	[1] = DEFINE_RES_IRQ(IRQ_LCD_VSYNC),        /* 中断 */
	[2] = DEFINE_RES_IRQ(IRQ_LCD_FIFO),         /* 中断 */
	[3] = DEFINE_RES_IRQ(IRQ_LCD_SYSTEM),       /* 中断 */
};


关于LCD,还要注意的一点是,LCD是需要背光才能显示的,所以我们这里也要看一下背光的接口函数,方便后面移植时修改。

static struct platform_device smdkv210_lcd_lte480wv = {
	.name			= "platform-lcd",        /* 背光名字 */
	.dev.parent		= &s3c_device_fb.dev,    /* 依赖于lcd */
	.dev.platform_data	= &smdkv210_lcd_lte480wv_data,    /* 背光相关数据接口 */
};


static struct plat_lcd_data smdkv210_lcd_lte480wv_data = {
	.set_power	= smdkv210_lte480wv_set_power,
};

static void smdkv210_lte480wv_set_power(struct plat_lcd_data *pd,
					unsigned int power)
{
	if (power) {        /* power为真,即开背光 */
#if !defined(CONFIG_BACKLIGHT_PWM)    /* 如果开启PWM调光功能,则配置PWM */
		gpio_request_one(S5PV210_GPD0(3), GPIOF_OUT_INIT_HIGH, "GPD0");
		gpio_free(S5PV210_GPD0(3));
#endif

		/* fire nRESET on power up 设置背光端口为高电平  */
		gpio_request_one(S5PV210_GPH0(6), GPIOF_OUT_INIT_HIGH, "GPH0");

		gpio_set_value(S5PV210_GPH0(6), 0);
		mdelay(10);

		gpio_set_value(S5PV210_GPH0(6), 1);
		mdelay(10);

		gpio_free(S5PV210_GPH0(6));
	} else {
#if !defined(CONFIG_BACKLIGHT_PWM)
		gpio_request_one(S5PV210_GPD0(3), GPIOF_OUT_INIT_LOW, "GPD0");
		gpio_free(S5PV210_GPD0(3));
#endif
	}
}

设备这边最后,就看平台设备是怎么注册的

/* lcd和背光的平台数据添加到一个统一数组中 */
static struct platform_device *smdkv210_devices[] __initdata = {
    ......
	&s3c_device_fb,        
    ......
	&smdkv210_lcd_lte480wv,
};


static void __init smdkv210_machine_init(void)
{
    ......
	
	s3c_fb_set_platdata(&smdkv210_lcd0_pdata);

    .....
    /* 在这里统一注册所有的平台设备 */
	platform_add_devices(smdkv210_devices, ARRAY_SIZE(smdkv210_devices));

    .....
}

这里我们要注意一个点,三星为了尽可能的对所有平台的代码重复使用,对有些平台数据的name重新由赋值了一遍,其中就包括我们的平fb设备。

MACHINE_START(SMDKV210, "SMDKV210")
	/* Maintainer: Kukjin Kim <kgene.kim@samsung.com> */
	.atag_offset	= 0x100,
	.init_irq	= s5pv210_init_irq,
	.map_io		= smdkv210_map_io,    /* 在这个函数设置的,启动阶段就执行 */
	.init_machine	= smdkv210_machine_init,
	.init_time	= samsung_timer_init,
	.restart	= s5pv210_restart,
	.reserve	= &smdkv210_reserve,
MACHINE_END


static void __init smdkv210_map_io(void)
{
	s5pv210_init_io(NULL, 0);        /* 这里设置 */
	s3c24xx_init_clocks(clk_xusbxti.rate);
	s3c24xx_init_uarts(smdkv210_uartcfgs, ARRAY_SIZE(smdkv210_uartcfgs));
	samsung_set_timer_source(SAMSUNG_PWM2, SAMSUNG_PWM4);
}

void __init s5pv210_init_io(struct map_desc *mach_desc, int size)
{
	/* initialize the io descriptors we need for initialization */
	iotable_init(s5pv210_iodesc, ARRAY_SIZE(s5pv210_iodesc));
	if (mach_desc)
		iotable_init(mach_desc, size);

	/* detect cpu id and rev. */
	s5p_init_cpu(S5P_VA_CHIPID);

    /* 这里设置的fb的name */
	s3c_init_cpu(samsung_cpu_id, cpu_ids, ARRAY_SIZE(cpu_ids));


	samsung_pwm_set_platdata(&s5pv210_pwm_variant);
}



static struct cpu_table cpu_ids[] __initdata = {
	{
		.idcode		= S5PV210_CPU_ID,
		.idmask		= S5PV210_CPU_MASK,
		.map_io		= s5pv210_map_io,        /* 调用这个函数 */
		.init_clocks	= s5pv210_init_clocks,
		.init_uarts	= s5pv210_init_uarts,
		.init		= s5pv210_init,
		.name		= name_s5pv210,
	},
};

void __init s3c_init_cpu(unsigned long idcode,
			 struct cpu_table *cputab, unsigned int cputab_size)
{
	cpu = s3c_lookup_cpu(idcode, cputab, cputab_size);

	if (cpu == NULL) {
		printk(KERN_ERR "Unknown CPU type 0x%08lx\n", idcode);
		panic("Unknown S3C24XX CPU");
	}

	printk("CPU %s (id 0x%08lx)\n", cpu->name, idcode);

	if (cpu->init == NULL) {
		printk(KERN_ERR "CPU %s support not enabled\n", cpu->name);
		panic("Unsupported Samsung CPU");
	}

	if (cpu->map_io)
		cpu->map_io();    /* 调用函数 */
}



void __init s5pv210_map_io(void)
{
	/* initialise device information early */
	s5pv210_default_sdhci0();
	s5pv210_default_sdhci1();
	s5pv210_default_sdhci2();
	s5pv210_default_sdhci3();

	s3c_adc_setname("samsung-adc-v3");

	s3c_cfcon_setname("s5pv210-pata");

	s3c_fimc_setname(0, "s5pv210-fimc");
	s3c_fimc_setname(1, "s5pv210-fimc");
	s3c_fimc_setname(2, "s5pv210-fimc");

	/* the i2c devices are directly compatible with s3c2440 */
	s3c_i2c0_setname("s3c2440-i2c");
	s3c_i2c1_setname("s3c2440-i2c");
	s3c_i2c2_setname("s3c2440-i2c");

	s3c_fb_setname("s5pv210-fb");        /* 重新覆盖之前的名字为s5pv210-fb */

	/* Use s5pv210-keypad instead of samsung-keypad */
	samsung_keypad_setname("s5pv210-keypad");

	/* setup TV devices */
	s5p_hdmi_setname("s5pv210-hdmi");

	s3c64xx_spi_setname("s5pv210-spi");
}



/* Re-define device name depending on support. */
static inline void s3c_fb_setname(char *name)
{
#ifdef CONFIG_S3C_DEV_FB
	s3c_device_fb.name = name;
#endif
}

 

接下来我们分析平台总线driver相关的操作接口。

最开始先了解一下基本的数据结构

struct platform_driver {
	int (*probe)(struct platform_device *);        /* dev和drv匹配上执行 */
	int (*remove)(struct platform_device *);       /* 卸载模块时执行 */
	void (*shutdown)(struct platform_device *);
	int (*suspend)(struct platform_device *, pm_message_t state);
	int (*resume)(struct platform_device *);
	struct device_driver driver;
	const struct platform_device_id *id_table;    /* 匹配表,用这个表匹配dev传过来的name */
	bool prevent_deferred_probe;                  /* 默认都是true */
};

三星framebuffer的drv数据结构

static struct platform_driver s3c_fb_driver = {
	.probe		= s3c_fb_probe,            /* 探针函数,主要用来映射资源,申请资源中断,初始化硬件,注册设备等 */
	.remove		= s3c_fb_remove,           /* 释放probe申请的资源,卸载设备等 */
	.id_table	= s3c_fb_driver_ids,       /* 匹配规则中的一种 */
	.driver		= {
		.name	= "s3c-fb",                /* 按名称匹配 */
		.owner	= THIS_MODULE,
		.pm	= &s3cfb_pm_ops,               /* 电源管理相关 */
	},
};

我们可以看到,这里有了分歧,有了两种可能的匹配一种四id_table,另一种是直接用driver里面的name。要想知道为什么有两种我们就要先说为什么需要这样做。

原因如下:

同一个厂家在不同芯片中的lcd控制器基本是一样的,但因为芯片每次升级等原因,会有寄存器或者部分操作的差异。

因为绝大多数的操作都是一样的,这部分代码不需要为每个处理器都,所以只需要把不同那个处理器的不同差异做出来,就可以。而这里不同处理器的差异就是用id_table来管理的。

绝大多数平台总线都是用driver里的name来匹配的,只有比较少的情况才会用id_table来匹配。关于匹配规则,我会在驱动模型写一个关于平台总线的。

下面 文章是我们分析的2.6.35.7内核的,有关于匹配部分的。

https://blog.csdn.net/qq_16777851/article/details/81350037

这里说明一点,id_table的匹配优先级是高于name的。

 

先看一下三星id_table的内容


static struct platform_device_id s3c_fb_driver_ids[] = {
	{
		.name		= "s3c-fb",
		.driver_data	= (unsigned long)&s3c_fb_data_64xx,
	}, {
		.name		= "s5pc100-fb",
		.driver_data	= (unsigned long)&s3c_fb_data_s5pc100,
	}, {
		.name		= "s5pv210-fb",                                /* 我们的后面覆盖成这个了,所以匹配上后会用s3c_fb_data_s5pv210 */
		.driver_data	= (unsigned long)&s3c_fb_data_s5pv210,
	}, {
		.name		= "exynos4-fb",
		.driver_data	= (unsigned long)&s3c_fb_data_exynos4,
	}, {
		.name		= "exynos5-fb",
		.driver_data	= (unsigned long)&s3c_fb_data_exynos5,
	}, {
		.name		= "s3c2443-fb",
		.driver_data	= (unsigned long)&s3c_fb_data_s3c2443,
	}, {
		.name		= "s5p64x0-fb",
		.driver_data	= (unsigned long)&s3c_fb_data_s5p64x0,
	},
	{},
};

下面我们看一下,s5pv210的driver_data里面放的是什么


static struct s3c_fb_driverdata s3c_fb_data_s5pv210 = {
	.variant = {
		.nr_windows	= 5,                /* 窗口数量 */
		.vidtcon	= VIDTCON0,         /* VIDTCON0寄存器相对LCD首寄存器的偏移 */
		.wincon		= WINCON(0),        /* WINCON0寄存器相对LCD首寄存器的偏移 */
		.winmap		= WINxMAP(0),       /* WINCON0寄存器相对LCD首寄存器的偏移 */
		.keycon		= WKEYCON,          /* WKEYCON寄存器相对LCD首寄存器的偏移 */
		.osd		= VIDOSD_BASE,      /* VIDOSD_BASE寄存器相对LCD首寄存器的偏移 */
		.osd_stride	= 16,
		.buf_start	= VIDW_BUF_START(0),    /* 显存起始地址寄存器相对LCD首寄存器的偏移 */
		.buf_size	= VIDW_BUF_SIZE(0),     /* 帧缓存大小寄存器 */
		.buf_end	= VIDW_BUF_END(0),      /* 显存结束地址寄存器相对LCD首寄存器的偏移 */

		.palette = {        /* 调测板数据 */
			[0] = 0x2400,
			[1] = 0x2800,
			[2] = 0x2c00,
			[3] = 0x3000,
			[4] = 0x3400,
		},

		.has_shadowcon	= 1,
		.has_blendcon	= 1,
		.has_clksel	= 1,                 
		.has_fixvclk	= 1,           
	},
	.win[0]	= &s3c_fb_data_s5p_wins[0],        /* 5个窗口的数据 */
	.win[1]	= &s3c_fb_data_s5p_wins[1],
	.win[2]	= &s3c_fb_data_s5p_wins[2],
	.win[3]	= &s3c_fb_data_s5p_wins[3],
	.win[4]	= &s3c_fb_data_s5p_wins[4],
};

 

/* 每个窗口都支持调色板 */
static struct s3c_fb_win_variant s3c_fb_data_s5p_wins[] = {
	[0] = {
		.has_osd_c	= 1,           /* 设置有OSD C寄存器 */
		.osd_size_off	= 0x8,     /* 寄存器位于OSD_BASE的给定偏移处 */
		.palette_sz	= 256,         /* 条目中调色板的大小 */
		.valid_bpp	= (VALID_BPP1248 | VALID_BPP(13) |    /* 有效的可设bpp,dev传过来的会对比是否有效 */
				   VALID_BPP(15) | VALID_BPP(16) |
				   VALID_BPP(18) | VALID_BPP(19) |
				   VALID_BPP(24) | VALID_BPP(25) |
				   VALID_BPP(32)),
	},
	[1] = {
		.has_osd_c	= 1,            /* 设置有OSD C寄存器 */
		.has_osd_d	= 1,            /* 设置有OSD D寄存器 */
		.osd_size_off	= 0xc,      /* 寄存器位于OSD_BASE的给定偏移处 */
		.has_osd_alpha	= 1,       /* 设置可以更改窗口的Alpha透明度 */
		.palette_sz	= 256,         /* 条目中调色板的大小 */
		.valid_bpp	= (VALID_BPP1248 | VALID_BPP(13) |
				   VALID_BPP(15) | VALID_BPP(16) |
				   VALID_BPP(18) | VALID_BPP(19) |
				   VALID_BPP(24) | VALID_BPP(25) |
				   VALID_BPP(32)),
	},
	[2] = {
		.has_osd_c	= 1,            /* 设置有OSD C寄存器 */
		.has_osd_d	= 1,            /* 设置有OSD D寄存器 */
		.osd_size_off	= 0xc,      /* 寄存器位于OSD_BASE的给定偏移处 */
		.has_osd_alpha	= 1,        /* 设置可以更改窗口的Alpha透明度 */
		.palette_sz	= 256,          /* 条目中调色板的大小 */
		.valid_bpp	= (VALID_BPP1248 | VALID_BPP(13) |
				   VALID_BPP(15) | VALID_BPP(16) |
				   VALID_BPP(18) | VALID_BPP(19) |
				   VALID_BPP(24) | VALID_BPP(25) |
				   VALID_BPP(32)),
	},
	[3] = {
		.has_osd_c	= 1,            /* 设置有OSD C寄存器 */
		.has_osd_alpha	= 1,        /* 设置可以更改窗口的Alpha透明度 */
		.palette_sz	= 256,          /* 条目中调色板的大小 */
		.valid_bpp	= (VALID_BPP1248 | VALID_BPP(13) |
				   VALID_BPP(15) | VALID_BPP(16) |
				   VALID_BPP(18) | VALID_BPP(19) |
				   VALID_BPP(24) | VALID_BPP(25) |
				   VALID_BPP(32)),
	},
	[4] = {
		.has_osd_c	= 1,           /* 设置有OSD C寄存器 */
		.has_osd_alpha	= 1,       /* 设置可以更改窗口的Alpha透明度 */
		.palette_sz	= 256,         /* 条目中调色板的大小 */
		.valid_bpp	= (VALID_BPP1248 | VALID_BPP(13) |
				   VALID_BPP(15) | VALID_BPP(16) |
				   VALID_BPP(18) | VALID_BPP(19) |
				   VALID_BPP(24) | VALID_BPP(25) |
				   VALID_BPP(32)),
	},
};

OSD_C寄存器的作用是,设置窗口大小

0#窗口的OSD_C和1#窗口的OSD_D一样,所以1#会多一个OSD_C

 

总体和下图一样,1、2、3、4号窗口才有ALPHA_R/G/B_H_F寄存器,可以设置透明度,

同时0、1、2号窗口要设置OSDSIZE

这个是每个窗口的不同之处。

一些特有的固定的数据结构看完之后我们看最主要的probe函数

#define platform_get_device_id(pdev)	((pdev)->id_entry)    /* 在总线的匹配中match设置的 匹配的id_entry */



static int s3c_fb_probe(struct platform_device *pdev)
{
	const struct platform_device_id *platid;
	struct s3c_fb_driverdata *fbdrv;
	struct device *dev = &pdev->dev;
	struct s3c_fb_platdata *pd;
	struct s3c_fb *sfb;
	struct resource *res;
	int win;
	int ret = 0;
	u32 reg;

	platid = platform_get_device_id(pdev);    /* 在id_table里面根据.name得到s5pv210的driver_data */
	fbdrv = (struct s3c_fb_driverdata *)platid->driver_data;

	if (fbdrv->variant.nr_windows > S3C_FB_MAX_WIN) {    /* 检测,三星硬件最多支持5个窗口 */
		dev_err(dev, "too many windows, cannot attach\n");
		return -EINVAL;
	}

	pd = dev_get_platdata(&pdev->dev);    /* 得到设备那边传过来的数据 */
	if (!pd) {
		dev_err(dev, "no platform data specified\n");
		return -EINVAL;
	}

    /* 申请一个三星的fb设备,这里会保存所有硬件相关的状态信息 */
	sfb = devm_kzalloc(dev, sizeof(struct s3c_fb), GFP_KERNEL);
	if (!sfb) {
		dev_err(dev, "no memory for framebuffers\n");
		return -ENOMEM;
	}

	dev_dbg(dev, "allocate new framebuffer %p\n", sfb);

	sfb->dev = dev;        /* 把设备绑定到三星的fb中 */
	sfb->pdata = pd;       /* 把dev那边传过来的数据绑定到三星的fb中 */
	sfb->variant = fbdrv->variant;    /* 可变的参数绑定到三星的fb中 */

	spin_lock_init(&sfb->slock);

	sfb->bus_clk = devm_clk_get(dev, "lcd");    /* 得到lcd的时钟 */
	if (IS_ERR(sfb->bus_clk)) {
		dev_err(dev, "failed to get bus clock\n");
		return PTR_ERR(sfb->bus_clk);
	}

	clk_prepare_enable(sfb->bus_clk);    /* 使能时钟 */

	if (!sfb->variant.has_clksel) {
		sfb->lcd_clk = devm_clk_get(dev, "sclk_fimd");    /* 的待fimd时钟 */
		if (IS_ERR(sfb->lcd_clk)) {
			dev_err(dev, "failed to get lcd clock\n");
			ret = PTR_ERR(sfb->lcd_clk);
			goto err_bus_clk;
		}

		clk_prepare_enable(sfb->lcd_clk);    /* 使能fimd时钟 */
	}

	pm_runtime_enable(sfb->dev);    /* 电源管理开启 */

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);    /* 得到寄存器资源 */
	sfb->regs = devm_ioremap_resource(dev, res);    /* 映射寄存器资源,得到首地址放到三星的fb中 */
	if (IS_ERR(sfb->regs)) {
		ret = PTR_ERR(sfb->regs);
		goto err_lcd_clk;
	}

	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);    /* 得到中断资源0 */
	if (!res) {
		dev_err(dev, "failed to acquire irq resource\n");
		ret = -ENOENT;
		goto err_lcd_clk;
	}
	sfb->irq_no = res->start;        /* 中断号放到三星的fb中 */
	ret = devm_request_irq(dev, sfb->irq_no, s3c_fb_irq,
			  0, "s3c_fb", sfb);        /* 申请资源,并绑定中断处理函数 */
	if (ret) {
		dev_err(dev, "irq request failed\n");
		goto err_lcd_clk;
	}

	dev_dbg(dev, "got resources (regs %p), probing windows\n", sfb->regs);

	platform_set_drvdata(pdev, sfb);    /* 把sfb绑定到driver_data上 */
	pm_runtime_get_sync(sfb->dev);      

	/* setup gpio and output polarity controls */

	pd->setup_gpio();                    /* 调用我们dev那边的lcd的gpio初始化函数 */

	writel(pd->vidcon1, sfb->regs + VIDCON1);    /* 把dev那边的参数写到对应的映射好的寄存器 */

	/* set video clock running at under-run */
	if (sfb->variant.has_fixvclk) {            /* 这里我们前面设置了时钟运行的 */
		reg = readl(sfb->regs + VIDCON1);
		reg &= ~VIDCON1_VCLK_MASK;
		reg |= VIDCON1_VCLK_RUN;                
		writel(reg, sfb->regs + VIDCON1);
	}

	/* zero all windows before we do anything 先把所有窗口的寄存器清空 */

	for (win = 0; win < fbdrv->variant.nr_windows; win++)
		s3c_fb_clear_win(sfb, win);

	/* initialise colour key controls 所有窗口的colour key设置 */
	for (win = 0; win < (fbdrv->variant.nr_windows - 1); win++) {
		void __iomem *regs = sfb->regs + sfb->variant.keycon;

		regs += (win * 8);
		writel(0xffffff, regs + WKEYCON0);
		writel(0xffffff, regs + WKEYCON1);
	}

    /* 设置时序和模式相关的到寄存器里 */
	s3c_fb_set_rgb_timing(sfb);

	/* we have the register setup, start allocating framebuffers */

	for (win = 0; win < fbdrv->variant.nr_windows; win++) {
		if (!pd->win[win])        /* 三星是最多支持五个,但我们在dev那边只传了一个窗口的数据 */
			continue;

        /* 注册fb设备到fb_class中 */
		ret = s3c_fb_probe_win(sfb, win, fbdrv->win[win],
				       &sfb->windows[win]);
		if (ret < 0) {
			dev_err(dev, "failed to create window %d\n", win);
			for (; win >= 0; win--)
				s3c_fb_release_win(sfb, sfb->windows[win]);
			goto err_pm_runtime;
		}
	}

	platform_set_drvdata(pdev, sfb);    /* 把三星的fb设置到drv中的私有数据(sfb中包含映射后的所有可用的参数) */
	pm_runtime_put_sync(sfb->dev);    /* 电源管理同步 */

	return 0;

err_pm_runtime:
	pm_runtime_put_sync(sfb->dev);

err_lcd_clk:
	pm_runtime_disable(sfb->dev);

	if (!sfb->variant.has_clksel)
		clk_disable_unprepare(sfb->lcd_clk);

err_bus_clk:
	clk_disable_unprepare(sfb->bus_clk);

	return ret;
}

关于上面的大部分都比较简单,这里我们分析三 个函数

1.时序和显示模式设置


/**
 * s3c_fb_set_rgb_timing() - set video timing for rgb interface.
 * @sfb: The base resources for the hardware.
 *
 * Set horizontal and vertical lcd rgb interface timing.
 */
static void s3c_fb_set_rgb_timing(struct s3c_fb *sfb)
{
	struct fb_videomode *vmode = sfb->pdata->vtiming;
	void __iomem *regs = sfb->regs;        /* 得到lcd寄存器基址 */
	int clkdiv;
	u32 data;

	if (!vmode->pixclock)        /* 如果我们在设备那边设置时钟像素,用下面函数计算 */
		s3c_fb_missing_pixclock(vmode); /* 这里调用了一个函数 s3c_fb_missing_pixclock,就是通过这个函数计算出来的 */

	clkdiv = s3c_fb_calc_pixclk(sfb, vmode->pixclock);    /*  */

	data = sfb->pdata->vidcon0;
	data &= ~(VIDCON0_CLKVAL_F_MASK | VIDCON0_CLKDIR);    /* 清时钟位 */

	if (clkdiv > 1)
		data |= VIDCON0_CLKVAL_F(clkdiv-1) | VIDCON0_CLKDIR;    /* 计算时钟 */
	else
		data &= ~VIDCON0_CLKDIR;	/* 1:1 clock */

	if (sfb->variant.is_2443)
		data |= (1 << 5);
	writel(data, regs + VIDCON0);    /* 设置时钟VCLK */

    /* 设置V方向时间参数 */
	data = VIDTCON0_VBPD(vmode->upper_margin - 1) |
	       VIDTCON0_VFPD(vmode->lower_margin - 1) |
	       VIDTCON0_VSPW(vmode->vsync_len - 1);
	writel(data, regs + sfb->variant.vidtcon);

    /* 设置H方向时间参数 */
	data = VIDTCON1_HBPD(vmode->left_margin - 1) |
	       VIDTCON1_HFPD(vmode->right_margin - 1) |
	       VIDTCON1_HSPW(vmode->hsync_len - 1);
	writel(data, regs + sfb->variant.vidtcon + 4);

    /* 设置分辨率 */
	data = VIDTCON2_LINEVAL(vmode->yres - 1) |
	       VIDTCON2_HOZVAL(vmode->xres - 1) |
	       VIDTCON2_LINEVAL_E(vmode->yres - 1) |
	       VIDTCON2_HOZVAL_E(vmode->xres - 1);
	writel(data, regs + sfb->variant.vidtcon + 8);
}

2.中断处理函数


static irqreturn_t s3c_fb_irq(int irq, void *dev_id)
{
	struct s3c_fb *sfb = dev_id;
	void __iomem  *regs = sfb->regs;
	u32 irq_sts_reg;

	spin_lock(&sfb->slock);

	irq_sts_reg = readl(regs + VIDINTCON1);

	if (irq_sts_reg & VIDINTCON1_INT_FRAME) {    /* 判断是不是有VSYNC帧中断,有表明一屏数据刷完了,可以唤醒等待刷第二屏的设备 */

		/* VSYNC interrupt, accept it,清中断 */
		writel(VIDINTCON1_INT_FRAME, regs + VIDINTCON1);

		sfb->vsync_info.count++;    /* 每次中断,都会更新count值 */
		wake_up_interruptible(&sfb->vsync_info.wait);    /* 唤醒这个设备等待列表上的进程 */
	}

	/* We only support waiting for VSYNC for now, so it's safe
	 * to always disable irqs here.
	 */
	s3c_fb_disable_irq(sfb);    /* 关掉fb中断 */

	spin_unlock(&sfb->slock);
	return IRQ_HANDLED;
}


既然这里关掉了中断,那肯定是有那里开启中断。
当然我们裸机中很多时候lcd都是不可开启中断的,如果应用层不开启中断,直接在显存写数据也是可以显示的
唯一就是,注意,如果写的太快,在A帧图像还没显示完,B帧已经又开始写显存的情况会有花屏出现的。
所以最好就是在A进程写的时候,其它进程等待,等A写完其它在写,这样就需要上面的等待队列的


三星这里是用ioctl来开启的
static int s3c_fb_ioctl(struct fb_info *info, unsigned int cmd,
			unsigned long arg)
{
	struct s3c_fb_win *win = info->par;
	struct s3c_fb *sfb = win->parent;
	int ret;
	u32 crtc;

	switch (cmd) {
	case FBIO_WAITFORVSYNC:    /* 应用层传过来的是等待VSYNC信号,则会 */
		if (get_user(crtc, (u32 __user *)arg)) {
			ret = -EFAULT;
			break;
		}

		ret = s3c_fb_wait_for_vsync(sfb, crtc);    /* 调用这个等待vsync中断 */
		break;
	default:
		ret = -ENOTTY;
	}

	return ret;
}



/**
 * s3c_fb_wait_for_vsync() - sleep until next VSYNC interrupt or timeout
 * @sfb: main hardware state
 * @crtc: head index.
 */
static int s3c_fb_wait_for_vsync(struct s3c_fb *sfb, u32 crtc)
{
	unsigned long count;
	int ret;

	if (crtc != 0)
		return -ENODEV;

	pm_runtime_get_sync(sfb->dev);

	count = sfb->vsync_info.count;    /* 这里保存了conut的值 */
	s3c_fb_enable_irq(sfb);    /* 开启中断 */
    /* 这边会把使用ioctl等待vsync信号的进程加入到等待队列,当然这里做了时间限制,最多等待50ms */
    /* 在count和sfb->vsync_info.count不一样时,即表明,在中断中更新过count值,表明该进程被唤醒已经可以使用了 */
	ret = wait_event_interruptible_timeout(sfb->vsync_info.wait,
				       count != sfb->vsync_info.count,
				       msecs_to_jiffies(VSYNC_TIMEOUT_MSEC));

	pm_runtime_put_sync(sfb->dev);    /* 如果有休眠lcd,这里写之前要唤醒的 */

	if (ret == 0)
		return -ETIMEDOUT;

	return 0;
}





看到这里我们肯定就能猜到指行顺序了


1.写显存
2.调用ioctl,开中断,并睡眠到等待队列
3.显示完会自动调用中断函数,唤醒等待队列上的进程,关中断
4.根据ioctl返回值确定是不是真的显示完了,可以显示下一帧了(因为上面的等待队列可以被信号打断的)


当然我们也可以不使用中断,即不使用ioctl来查询是不是空闲,则进程不会睡眠,直接在fb中写内容就可以


最后说一下三星的fb中断使能,或禁止是用下面的函数来实现的

/* irq_flags bits */
#define S3C_FB_VSYNC_IRQ_EN	0        /*  */

#define VSYNC_TIMEOUT_MSEC 50



/**
 * s3c_fb_enable_irq() - enable framebuffer interrupts
 * @sfb: main hardware state
 */
static void s3c_fb_enable_irq(struct s3c_fb *sfb)
{
	void __iomem *regs = sfb->regs;
	u32 irq_ctrl_reg;

    /* 这里使用了一个小技巧,对irq_flags的S3C_FB_VSYNC_IRQ_EN位先保存,作为范围,后置位
       即,如果已经使能则不会再重复执行下面的使能中断
     */
	if (!test_and_set_bit(S3C_FB_VSYNC_IRQ_EN, &sfb->irq_flags)) {
		/* IRQ disabled, enable it */
		irq_ctrl_reg = readl(regs + VIDINTCON0);

		irq_ctrl_reg |= VIDINTCON0_INT_ENABLE;
		irq_ctrl_reg |= VIDINTCON0_INT_FRAME;

		irq_ctrl_reg &= ~VIDINTCON0_FRAMESEL0_MASK;
		irq_ctrl_reg |= VIDINTCON0_FRAMESEL0_VSYNC;
		irq_ctrl_reg &= ~VIDINTCON0_FRAMESEL1_MASK;
		irq_ctrl_reg |= VIDINTCON0_FRAMESEL1_NONE;

		writel(irq_ctrl_reg, regs + VIDINTCON0);
	}
}

/**
 * s3c_fb_disable_irq() - disable framebuffer interrupts
 * @sfb: main hardware state
 */
static void s3c_fb_disable_irq(struct s3c_fb *sfb)
{
	void __iomem *regs = sfb->regs;
	u32 irq_ctrl_reg;

	if (test_and_clear_bit(S3C_FB_VSYNC_IRQ_EN, &sfb->irq_flags)) {
		/* IRQ enabled, disable it */
		irq_ctrl_reg = readl(regs + VIDINTCON0);

		irq_ctrl_reg &= ~VIDINTCON0_INT_FRAME;
		irq_ctrl_reg &= ~VIDINTCON0_INT_ENABLE;

		writel(irq_ctrl_reg, regs + VIDINTCON0);
	}
}



 

 

上面的所有都属于三星平台的硬件初始化以及利用平台总线进行的数据填充和绑定

下面这个函数就是真正的属于framebuffer的注册了,这里面就没有硬件操作,只是把我们的初始化的lcd控制器注册到内核的frameuffer类里面去。

 


/**
 * s3c_fb_probe_win() - register an hardware window
 * @sfb: The base resources for the hardware
 * @variant: The variant information for this window.
 * @res: Pointer to where to place the resultant window.
 *
 * Allocate and do the basic initialisation for one of the hardware's graphics
 * windows.
 */
static int s3c_fb_probe_win(struct s3c_fb *sfb, unsigned int win_no,
			    struct s3c_fb_win_variant *variant,
			    struct s3c_fb_win **res)
{
	struct fb_var_screeninfo *var;
	struct fb_videomode initmode;
	struct s3c_fb_pd_win *windata;
	struct s3c_fb_win *win;
	struct fb_info *fbinfo;
	int palette_size;
	int ret;

	dev_dbg(sfb->dev, "probing window %d, variant %p\n", win_no, variant);

	init_waitqueue_head(&sfb->vsync_info.wait);    /* 初始化vsync的等待队列(中断使用) */

	palette_size = variant->palette_sz * 4;        /* 从初始化条侧板大小 */

    /* 申请一个fb窗口设备(当然这个fb窗口是包含了系统的fb_info结构的) */
	fbinfo = framebuffer_alloc(sizeof(struct s3c_fb_win) +
				   palette_size * sizeof(u32), sfb->dev);
	if (!fbinfo) {
		dev_err(sfb->dev, "failed to allocate framebuffer\n");
		return -ENOENT;
	}

	windata = sfb->pdata->win[win_no];    /* 得到窗口数据 */
	initmode = *sfb->pdata->vtiming;      /* 得到时序模式数据 */

	WARN_ON(windata->max_bpp == 0);
	WARN_ON(windata->xres == 0);
	WARN_ON(windata->yres == 0);

    /* 把数据填充到fbinfo最中,fb_info中的数据就可以应用层和驱动层用sttribute交互了 */
	win = fbinfo->par;
	*res = win;
	var = &fbinfo->var;
	win->variant = *variant;
	win->fbinfo = fbinfo;
	win->parent = sfb;
	win->windata = windata;
	win->index = win_no;
	win->palette_buffer = (u32 *)(win + 1);

    /* 计算需要的显存大小,申请显存空间 */
	ret = s3c_fb_alloc_memory(sfb, win);
	if (ret) {
		dev_err(sfb->dev, "failed to allocate display memory\n");
		return ret;
	}

	/* setup the r/b/g positions for the window's palette */
	if (win->variant.palette_16bpp) {    /* 这个窗口如果支持16位的条侧板,设置默认参数 */
		/* Set RGB 5:6:5 as default */
		win->palette.r.offset = 11;
		win->palette.r.length = 5;
		win->palette.g.offset = 5;
		win->palette.g.length = 6;
		win->palette.b.offset = 0;
		win->palette.b.length = 5;

	} else {
		/* Set 8bpp or 8bpp and 1bit alpha,不支持16位调色板,就设置24bpp的显示模式 */
		win->palette.r.offset = 16;
		win->palette.r.length = 8;
		win->palette.g.offset = 8;
		win->palette.g.length = 8;
		win->palette.b.offset = 0;
		win->palette.b.length = 8;
	}

	/* setup the initial video mode from the window 设置窗口分辨率 */
	initmode.xres = windata->xres;
	initmode.yres = windata->yres;
	fb_videomode_to_var(&fbinfo->var, &initmode);    /* 把我们的模式参数写给fbinfo中的可变参数中 */

    /* 设置固定参数 */
	fbinfo->fix.type	= FB_TYPE_PACKED_PIXELS;
	fbinfo->fix.accel	= FB_ACCEL_NONE;
	fbinfo->var.activate	= FB_ACTIVATE_NOW;
	fbinfo->var.vmode	= FB_VMODE_NONINTERLACED;
	fbinfo->var.bits_per_pixel = windata->default_bpp;    /* z设置像素位数 */
	fbinfo->fbops		= &s3c_fb_ops;    /* 设置操作接口函数 */
	fbinfo->flags		= FBINFO_FLAG_DEFAULT;
	fbinfo->pseudo_palette  = &win->pseudo_palette;    /*  绑定条侧板 */

	/* prepare to actually start the framebuffer */

    /* 检查可变参数,比如分辨率不能大于显存空间和虚拟分辨率,比如是否支持dev传过来的bpp数 */
	ret = s3c_fb_check_var(&fbinfo->var, fbinfo);    
	if (ret < 0) {
		dev_err(sfb->dev, "check_var failed on initial video params\n");
		return ret;
	}

	/* create initial colour map创建初始颜色映射(调色板) */

	ret = fb_alloc_cmap(&fbinfo->cmap, win->variant.palette_sz, 1);
	if (ret == 0)
		fb_set_cmap(&fbinfo->cmap, fbinfo);
	else
		dev_err(sfb->dev, "failed to allocate fb cmap\n");

	s3c_fb_set_par(fbinfo);    /* 设置寄存器参数,包括显存的起始地址,结束地址,窗口参数,大小,模式,开关显示模式等等 */

	dev_dbg(sfb->dev, "about to register framebuffer\n");

	/* run the check_var and set_par on our configuration. */

	ret = register_framebuffer(fbinfo);    /* 最终注册到fb类下面去 */
	if (ret < 0) {
		dev_err(sfb->dev, "failed to register framebuffer\n");
		return ret;
	}

	dev_info(sfb->dev, "window %d: fb %s\n", win_no, fbinfo->fix.id);

	return 0;
}

这个函数中最重要的就是申请显存和绑定fops函数,因为LCD用到了DMA显示,所以申请的空间物理地址必须是连续的,所以要用专门的函数申请显存


/**
 * s3c_fb_alloc_memory() - allocate display memory for framebuffer window
 * @sfb: The base resources for the hardware.
 * @win: The window to initialise memory for.
 *
 * Allocate memory for the given framebuffer.
 */
static int s3c_fb_alloc_memory(struct s3c_fb *sfb, struct s3c_fb_win *win)
{
	struct s3c_fb_pd_win *windata = win->windata;
	unsigned int real_size, virt_size, size;
	struct fb_info *fbi = win->fbinfo;
	dma_addr_t map_dma;

	dev_dbg(sfb->dev, "allocating memory for display\n");
    
    /* 计算实际分辨率和虚拟分辨率 */
	real_size = windata->xres * windata->yres;
	virt_size = windata->virtual_x * windata->virtual_y;

	dev_dbg(sfb->dev, "real_size=%u (%u.%u), virt_size=%u (%u.%u)\n",
		real_size, windata->xres, windata->yres,
		virt_size, windata->virtual_x, windata->virtual_y);

    /* 按照大的分辨率来申请显存 */
	size = (real_size > virt_size) ? real_size : virt_size;
    /* 计算显存需要的字节数 */
	size *= (windata->max_bpp > 16) ? 32 : windata->max_bpp;    
	size /= 8;

    /* 得到需要的字节数得到页大小 */
	fbi->fix.smem_len = size;
	size = PAGE_ALIGN(size);

	dev_dbg(sfb->dev, "want %u bytes for window\n", size);

    /* 申请物理地址连续的内存 */
	fbi->screen_base = dma_alloc_writecombine(sfb->dev, size,
						  &map_dma, GFP_KERNEL);
	if (!fbi->screen_base)
		return -ENOMEM;

	dev_dbg(sfb->dev, "mapped %x to %p\n",
		(unsigned int)map_dma, fbi->screen_base);

    /* 清空申请的内存 */
	memset(fbi->screen_base, 0x0, size);
	fbi->fix.smem_start = map_dma;    /* 注意smem_start 是物理地址 */

	return 0;
}

 

我们可以看到三星没有做我们常见到的open,read,write之类,而是做了一些功能性的函数,这些函数主要是在sys文件系统中attribute里面参数被更改后,因为需要更改硬件寄存器里面的数据,而调用的。


/*
 * Frame buffer operations
 *
 * LOCKING NOTE: those functions must _ALL_ be called with the console
 * semaphore held, this is the only suitable locking mechanism we have
 * in 2.6. Some may be called at interrupt time at this point though.
 *
 * The exception to this is the debug related hooks.  Putting the fb
 * into a debug state (e.g. flipping to the kernel console) and restoring
 * it must be done in a lock-free manner, so low level drivers should
 * keep track of the initial console (if applicable) and may need to
 * perform direct, unlocked hardware writes in these hooks.
 */

struct fb_ops {
	/* open/release and usage marking */
	struct module *owner;
	int (*fb_open)(struct fb_info *info, int user);
	int (*fb_release)(struct fb_info *info, int user);

	/* For framebuffers with strange non linear layouts or that do not
	 * work with normal memory mapped access
	 */
	ssize_t (*fb_read)(struct fb_info *info, char __user *buf,
			   size_t count, loff_t *ppos);
	ssize_t (*fb_write)(struct fb_info *info, const char __user *buf,
			    size_t count, loff_t *ppos);

	/* checks var and eventually tweaks it to something supported,
	 * DO NOT MODIFY PAR */
	int (*fb_check_var)(struct fb_var_screeninfo *var, struct fb_info *info);

	/* set the video mode according to info->var */
	int (*fb_set_par)(struct fb_info *info);

	/* set color register */
	int (*fb_setcolreg)(unsigned regno, unsigned red, unsigned green,
			    unsigned blue, unsigned transp, struct fb_info *info);

	/* set color registers in batch */
	int (*fb_setcmap)(struct fb_cmap *cmap, struct fb_info *info);

	/* blank display */
	int (*fb_blank)(int blank, struct fb_info *info);

	/* pan display */
	int (*fb_pan_display)(struct fb_var_screeninfo *var, struct fb_info *info);

	/* Draws a rectangle */
	void (*fb_fillrect) (struct fb_info *info, const struct fb_fillrect *rect);
	/* Copy data from area to another */
	void (*fb_copyarea) (struct fb_info *info, const struct fb_copyarea *region);
	/* Draws a image to the display */
	void (*fb_imageblit) (struct fb_info *info, const struct fb_image *image);

	/* Draws cursor */
	int (*fb_cursor) (struct fb_info *info, struct fb_cursor *cursor);

	/* Rotates the display */
	void (*fb_rotate)(struct fb_info *info, int angle);

	/* wait for blit idle, optional */
	int (*fb_sync)(struct fb_info *info);

	/* perform fb specific ioctl (optional) */
	int (*fb_ioctl)(struct fb_info *info, unsigned int cmd,
			unsigned long arg);

	/* Handle 32bit compat ioctl (optional) */
	int (*fb_compat_ioctl)(struct fb_info *info, unsigned cmd,
			unsigned long arg);

	/* perform fb specific mmap */
	int (*fb_mmap)(struct fb_info *info, struct vm_area_struct *vma);

	/* get capability given var */
	void (*fb_get_caps)(struct fb_info *info, struct fb_blit_caps *caps,
			    struct fb_var_screeninfo *var);

	/* teardown any resources to do with this framebuffer */
	void (*fb_destroy)(struct fb_info *info);

	/* called at KDB enter and leave time to prepare the console */
	int (*fb_debug_enter)(struct fb_info *info);
	int (*fb_debug_leave)(struct fb_info *info);
};

/* 主要是硬件操作函数,包括ioctl中设置参数,或sttribute中更改参数等被调用 */
static struct fb_ops s3c_fb_ops = {
	.owner		= THIS_MODULE,
	.fb_check_var	= s3c_fb_check_var,    /* 检测参数有效性 */
	.fb_set_par	= s3c_fb_set_par,          /* 设置参数(设置前肯定会检测有效性) */
	.fb_blank	= s3c_fb_blank,
	.fb_setcolreg	= s3c_fb_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
	.fb_pan_display	= s3c_fb_pan_display,
	.fb_ioctl	= s3c_fb_ioctl,
};

 

这里我们还是以上一节的应用层通过attribute设置bpp为例说明怎么调用fb_ops 里的函数设置硬件寄存器

static ssize_t store_bpp(struct device *device, struct device_attribute *attr,
			 const char *buf, size_t count)
{
	struct fb_info *fb_info = dev_get_drvdata(device);
	struct fb_var_screeninfo var;
	char ** last = NULL;
	int err;

	var = fb_info->var;
	var.bits_per_pixel = simple_strtoul(buf, last, 0);    /* 字符串转数字 */
	if ((err = activate(fb_info, &var)))    /* 设置到硬件中 */
		return err;
	return count;
}



static int activate(struct fb_info *fb_info, struct fb_var_screeninfo *var)
{
	int err;

	var->activate |= FB_ACTIVATE_FORCE;
	console_lock();
	fb_info->flags |= FBINFO_MISC_USEREVENT;
	err = fb_set_var(fb_info, var);        /* 设置可变参数(这里是会更新所有的寄存器参数的,虽然我们只改变了一个) */
	fb_info->flags &= ~FBINFO_MISC_USEREVENT;
	console_unlock();
	if (err)
		return err;
	return 0;
}




int
fb_set_var(struct fb_info *info, struct fb_var_screeninfo *var)
{
	int flags = info->flags;
	int ret = 0;

	if (var->activate & FB_ACTIVATE_INV_MODE) {    /* 检查是不是要更新模式 */
		struct fb_videomode mode1, mode2;

		fb_var_to_videomode(&mode1, var);
		fb_var_to_videomode(&mode2, &info->var);
		/* make sure we don't delete the videomode of current var */
		ret = fb_mode_is_equal(&mode1, &mode2);

		if (!ret) {
		    struct fb_event event;

		    event.info = info;
		    event.data = &mode1;
		    ret = fb_notifier_call_chain(FB_EVENT_MODE_DELETE, &event);
		}

		if (!ret)
		    fb_delete_videomode(&mode1, &info->modelist);


		ret = (ret) ? -EINVAL : 0;
		goto done;
	}

    /* 比较参数有没被更新,有则需要重新设置寄存器 */
	if ((var->activate & FB_ACTIVATE_FORCE) ||
	    memcmp(&info->var, var, sizeof(struct fb_var_screeninfo))) {
		u32 activate = var->activate;

		/* When using FOURCC mode, make sure the red, green, blue and
		 * transp fields are set to 0.
		 */
		if ((info->fix.capabilities & FB_CAP_FOURCC) &&
		    var->grayscale > 1) {
			if (var->red.offset     || var->green.offset    ||
			    var->blue.offset    || var->transp.offset   ||
			    var->red.length     || var->green.length    ||
			    var->blue.length    || var->transp.length   ||
			    var->red.msb_right  || var->green.msb_right ||
			    var->blue.msb_right || var->transp.msb_right)
				return -EINVAL;
		}

		if (!info->fbops->fb_check_var) {
			*var = info->var;
			goto done;
		}

		ret = info->fbops->fb_check_var(var, info);    /* 检测参数有效性 */

		if (ret)
			goto done;

		if ((var->activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) {
			struct fb_var_screeninfo old_var;
			struct fb_videomode mode;

			if (info->fbops->fb_get_caps) {
				ret = fb_check_caps(info, var, activate);

				if (ret)
					goto done;
			}

			old_var = info->var;
			info->var = *var;

			if (info->fbops->fb_set_par) {
				ret = info->fbops->fb_set_par(info);        /* 设置参数到硬件寄存器 */

				if (ret) {
					info->var = old_var;
					printk(KERN_WARNING "detected "
						"fb_set_par error, "
						"error code: %d\n", ret);
					goto done;
				}
			}

			fb_pan_display(info, &info->var);    /* 调用我们自己实现的,更新显存的数据 */
			fb_set_cmap(&info->cmap, info);
			fb_var_to_videomode(&mode, &info->var);    /* 更新显示模式 */

			if (info->modelist.prev && info->modelist.next &&
			    !list_empty(&info->modelist))
				ret = fb_add_videomode(&mode, &info->modelist);

			if (!ret && (flags & FBINFO_MISC_USEREVENT)) {
				struct fb_event event;
				int evnt = (activate & FB_ACTIVATE_ALL) ?
					FB_EVENT_MODE_CHANGE_ALL :
					FB_EVENT_MODE_CHANGE;

				info->flags &= ~FBINFO_MISC_USEREVENT;
				event.info = info;
				event.data = &mode;
				fb_notifier_call_chain(evnt, &event);
			}
		}
	}

 done:
	return ret;
}

可以看到上层如果更新参数,下面要经过各种计算,检查这个参数能不能设置。同时要更新和所有参数(有些参数是根据改变的参数计算的)并是设置到硬件到寄存器。采样上层的一次更改才会生效。

 

同时l可以利用ioct更改lcd的显示模式或得到参数信息等,这些只要设置相关的,都需要用到前面硬件操作函数

static long fb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	struct fb_info *info = file_fb_info(file);

	if (!info)
		return -ENODEV;
	return do_fb_ioctl(info, cmd, arg);
}


static long do_fb_ioctl(struct fb_info *info, unsigned int cmd,
			unsigned long arg)
{
	struct fb_ops *fb;
	struct fb_var_screeninfo var;
	struct fb_fix_screeninfo fix;
	struct fb_con2fbmap con2fb;
	struct fb_cmap cmap_from;
	struct fb_cmap_user cmap;
	struct fb_event event;
	void __user *argp = (void __user *)arg;
	long ret = 0;

	switch (cmd) {
	case FBIOGET_VSCREENINFO:        /* 把可变参数数据结构拷贝到应用层 */
		if (!lock_fb_info(info))
			return -ENODEV;
		var = info->var;
		unlock_fb_info(info);

		ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;
		break;
	case FBIOPUT_VSCREENINFO:    /* 应用层设置驱动中的可变参数 */
		if (copy_from_user(&var, argp, sizeof(var)))
			return -EFAULT;
		console_lock();
		if (!lock_fb_info(info)) {
			console_unlock();
			return -ENODEV;
		}
		info->flags |= FBINFO_MISC_USEREVENT;
		ret = fb_set_var(info, &var);        /* 设置函数 */
		info->flags &= ~FBINFO_MISC_USEREVENT;
		unlock_fb_info(info);
		console_unlock();
		if (!ret && copy_to_user(argp, &var, sizeof(var)))    /* 设置完后,在写给应用层(可能应用层改变的那个参数是别的计算出来的,这种是写不成功的) */
			ret = -EFAULT;
		break;
	case FBIOGET_FSCREENINFO:         /* 把固定参数数据结构拷贝到应用层 */
		if (!lock_fb_info(info))
			return -ENODEV;
		fix = info->fix;
		unlock_fb_info(info);

		ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;
		break;
	case FBIOPUTCMAP:        /* 设置应用层的cmap */
		if (copy_from_user(&cmap , argp, sizeof(cmap)))
			return -EFAULT; 
		ret = fb_set_user_cmap(&cmap, info);
		break;
	case FBIOGETCMAP:
		if (copy_from_user(&cmap, argp, sizeof(cmap)))
			return -EFAULT;
		if (!lock_fb_info(info))
			return -ENODEV;
		cmap_from = info->cmap;
		unlock_fb_info(info);
		ret = fb_cmap_to_user(&cmap_from, &cmap);
		break;
	case FBIOPAN_DISPLAY:    /* 设置偏移 */
		if (copy_from_user(&var, argp, sizeof(var)))
			return -EFAULT;
		console_lock();
		if (!lock_fb_info(info)) {
			console_unlock();
			return -ENODEV;
		}
		ret = fb_pan_display(info, &var);
		unlock_fb_info(info);
		console_unlock();
		if (ret == 0 && copy_to_user(argp, &var, sizeof(var)))
			return -EFAULT;
		break;
	case FBIO_CURSOR:
		ret = -EINVAL;
		break;
	case FBIOGET_CON2FBMAP:
		if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
			return -EFAULT;
		if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
			return -EINVAL;
		con2fb.framebuffer = -1;
		event.data = &con2fb;
		if (!lock_fb_info(info))
			return -ENODEV;
		event.info = info;
		fb_notifier_call_chain(FB_EVENT_GET_CONSOLE_MAP, &event);
		unlock_fb_info(info);
		ret = copy_to_user(argp, &con2fb, sizeof(con2fb)) ? -EFAULT : 0;
		break;
	case FBIOPUT_CON2FBMAP:
		if (copy_from_user(&con2fb, argp, sizeof(con2fb)))
			return -EFAULT;
		if (con2fb.console < 1 || con2fb.console > MAX_NR_CONSOLES)
			return -EINVAL;
		if (con2fb.framebuffer >= FB_MAX)
			return -EINVAL;
		if (!registered_fb[con2fb.framebuffer])
			request_module("fb%d", con2fb.framebuffer);
		if (!registered_fb[con2fb.framebuffer]) {
			ret = -EINVAL;
			break;
		}
		event.data = &con2fb;
		console_lock();
		if (!lock_fb_info(info)) {
			console_unlock();
			return -ENODEV;
		}
		event.info = info;
		ret = fb_notifier_call_chain(FB_EVENT_SET_CONSOLE_MAP, &event);
		unlock_fb_info(info);
		console_unlock();
		break;
	case FBIOBLANK:
		console_lock();
		if (!lock_fb_info(info)) {
			console_unlock();
			return -ENODEV;
		}
		info->flags |= FBINFO_MISC_USEREVENT;
		ret = fb_blank(info, arg);
		info->flags &= ~FBINFO_MISC_USEREVENT;
		unlock_fb_info(info);
		console_unlock();
		break;
	default:
		if (!lock_fb_info(info))
			return -ENODEV;
		fb = info->fbops;
		if (fb->fb_ioctl)
			ret = fb->fb_ioctl(info, cmd, arg);    /* 设置开启中断 */
		else
			ret = -ENOTTY;
		unlock_fb_info(info);
	}
	return ret;
}

 

  • 1
    点赞
  • 0
    评论
  • 8
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值