欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

<Android> Qualcomm DSI MIPI读写

程序员文章站 2022-07-01 20:18:25
...
static struct mdss_panel_data *g_pdata;

int mdss_dsi_panel_init(struct device_node *node,
	struct mdss_dsi_ctrl_pdata *ctrl_pdata,
	bool cmd_cfg_cont_splash)
{
	// ...

	g_pdata = &(ctrl_pdata->panel_data);

	return 0;
}

//////////////////////////////////////////////////////////////////////////
#include <asm/uaccess.h>
#define REGFLAG_DELAY       0xFC
#define LIC_DELAY(table)	(table->flag == REGFLAG_DELAY)
#define LIC_CMD(table)		(table->opbuf[0])
struct qcom_lcd_setting_table {
	unsigned char flag;
	int count;
	unsigned char opbuf[65];  /* opbuf[0]=cmd */
};
struct qcom_lcd_initcode {
	int total;
	struct qcom_lcd_setting_table **lines;
};

#define LCD_LINE_MAX_CHAR		1024
#define LCD_INIT_CODE_MAX_LINE	1024
#define QIS_DIGIT(x)			((x >= '0') && (x <= '9'))
#define QIS_UPCHAR(x)			((x >= 'A') && (x <= 'Z'))
#define QIS_LWCHAR(x)			((x >= 'a') && (x <= 'z'))
struct qcom_lcd_initcode g_qcomlic;

void qcom_print_lic(char *title, struct qcom_lcd_setting_table *table) 
{
	int i;

	pr_info("======= %s =======\n", title);
	pr_info("cmd: 0x%02X%s\n", LIC_CMD(table), LIC_DELAY(table) ? "(Delay)" : "");
	pr_info("count: %d\n", table->count & 0xFF);
	if (LIC_DELAY(table)) {
		return;
	}

	pr_info("data:\n");
	for (i = 1; i < table->count; i+=2) {
		pr_info("%02X, %02X\n", table->opbuf[i], table->opbuf[i+1]);
	}
	pr_info("===================\n");
}
int qcom_shex_to_int(char *hex, int length)
{
	int ret = 0;
	int i;
	char single;

	int base = 1;
	for (i = length-1; i >= 0; i--) {
		single = hex[i];
		if ((single == '\r')) {
			continue;
		}
		if ((single == 'x') || (single == 'X')){
			break;
		}
		if ((single >= '0') && (single <= '9')) {
			ret += (single - '0') * base;
		} else if ((single >= 'a') && (single <= 'f')) {
			ret += (single - 'a' + 10) * base;
		} else if((single >= 'A') && (single <= 'F')) {
			ret += (single - 'A' + 10) * base;
		} else {
			return -EINVAL;
		}
		base *= 16;
	}
	return ret;
}
/*
 * otherwise 0
 */ 
static int qcom_parse_lic_line(char *singleLine, int length) 
{
	char single;
	int i = 0;
	int ret;
	bool data = false;
	char single_byte[10] = {0};
	char bytes[65] = {0};
	int isbyte = 0;
	int ibyte = 0;
	struct qcom_lcd_setting_table *ptable = NULL;

	if (strstr(singleLine, "//")) {
		bool comment = false;
		for (i = 0; i < length; i++) {
			single = singleLine[i];
			if (single == '/') {
				comment = true;
				continue;
			}
			else if (QIS_LWCHAR(single) || QIS_UPCHAR(single)) {
				if (!comment) {
					break;
				} else {
					return 0;
				}
			}
		}
	} 
	pr_err("line(%d): %s\n", length, singleLine);
	if (strstr(singleLine, "GEN_WR(")) {
		ptable = kzalloc(sizeof(struct qcom_lcd_setting_table), GFP_KERNEL);
		for (i = 0; i < length; i++) {
			single = singleLine[i];
			if (single == '(') {
				data = true;
				continue;
			} 
			if (data) {
				if ((single == ',') || (single == ')')) {
					ret = qcom_shex_to_int(single_byte, isbyte);
					if (ret < 0) {
						pr_err("wrong data: %s\n", single_byte);
						return 0;
					}
					bytes[ibyte++] = (unsigned char)ret;
					if (single == ')') {
						break;
					}
					isbyte = 0;
					memset(single_byte, 0, 10);
				} else {
					single_byte[isbyte++] = single;
				}
			}
		}
		if (ibyte > 0) {
			ptable->count = ibyte;
			memcpy(ptable->opbuf, bytes, ptable->count);
		}
	} else if (strstr(singleLine, "delayms_pc(")) {
		ptable = kzalloc(sizeof(struct qcom_lcd_setting_table), GFP_KERNEL);
		ptable->flag = REGFLAG_DELAY;
		for (i = 0; i < length; i++) {
			single = singleLine[i];
			if (single == '(') {
				data = true;
				continue;
			} 
			if (data) {
				if (single == ')') {
					ptable->count = simple_strtoll(single_byte, NULL, 10);
					break;
				}
				single_byte[isbyte++] = single;
			}
		}
	}

	if (ptable) {
		char title[20] = {0};

		if (g_qcomlic.total == 0) { 
			if (LIC_DELAY(ptable)) {
				return 0;
			}
		}
		
		g_qcomlic.lines[g_qcomlic.total++] = ptable;

		snprintf(title, PAGE_SIZE, "data[%d]", g_qcomlic.total-1);
		qcom_print_lic(title, ptable);

	}
	return 0;
}


void qcom_get_cmds(char *filename)
{
	struct file *pcfg_file;
	char single;
	char *readbuf = NULL;
	char *singleLine = NULL;
	int ret;
	int i = 0;
	int iline = 0;
	int length = 0;
	char *filepath = NULL;
	mm_segment_t old_fs;

	filepath = kzalloc(PATH_MAX, GFP_KERNEL);
	if (!filepath) {
		pr_err("allocate mem for cmds failed!\n");
		return;
	}
	snprintf(filepath, PATH_MAX, "/sdcard/%s", filename);
	pr_info("file path: %s\n", filepath);

	pcfg_file = filp_open(filepath, O_RDONLY, 0);

	if (IS_ERR(pcfg_file)) {
		pr_err("Open %s failed!\n", filepath);
		goto no_file_out;
	}
	old_fs = get_fs();
	set_fs(KERNEL_DS);

	readbuf = kzalloc(LCD_LINE_MAX_CHAR*2+1, GFP_KERNEL);
	singleLine = kzalloc(LCD_LINE_MAX_CHAR, GFP_KERNEL);
	if (!readbuf || !singleLine) {
		pr_err("alloca memory failed!\n");
		goto out;
	}
	g_qcomlic.lines = kzalloc(sizeof(struct qcom_lcd_initcode*) * LCD_INIT_CODE_MAX_LINE,
		GFP_KERNEL);
	if (!g_qcomlic.lines) {
		pr_err("alloca memory for g_qcomlic failed!\n");
		goto out;
	}

	while (1) {
		ret = pcfg_file->f_op->read(pcfg_file, readbuf, LCD_LINE_MAX_CHAR*2, &pcfg_file->f_pos);
		if (ret <= 0) {
			break;
		}
		length = ret;
		for (i = 0; i <= length; i++) {
			single = readbuf[i];
			if ((i == length) || (single == '\n')) {
				ret = qcom_parse_lic_line(singleLine, iline);
				if (ret < 0) {
					goto out;
				}
				iline = 0;
				memset(singleLine, 0, LCD_LINE_MAX_CHAR);
				continue;
			} else if (single != '\0') { /* wchar/char */
				singleLine[iline++] = single;
			}
		}
	}
out:
	if (!IS_ERR(pcfg_file)) {
		filp_close(pcfg_file, NULL);
		set_fs(old_fs);
	}

	kfree(singleLine);
	kfree(readbuf);
no_file_out:
	kfree(filepath);
	return;
}
/* 
 * qcom_dsi_write_file: mipi cmds registers by file
 * @filepath: file located in /sdcard
 * Return: 0 success, otherwise fail
 */
int qcom_dsi_write_file(char *filepath)
{
	struct mdss_panel_data *panel_data = g_pdata;
	struct mdss_dsi_ctrl_pdata *ctrl_pdata = container_of(panel_data,
	struct mdss_dsi_ctrl_pdata, panel_data);
	char *filename = filepath;
	int i, i_cmd;

	struct dsi_panel_cmds qcom_pcmds;
	struct dsi_cmd_desc *cmds;

	if (filepath[0] == '/') {
		filename++;
	}
	if (g_qcomlic.total > 0) {
		for (i = 0; i < g_qcomlic.total; i++) {
			kfree(g_qcomlic.lines[i]);
		}
		kfree(g_qcomlic.lines);
		g_qcomlic.total = 0;
	}
	qcom_get_cmds(filename);

	if (g_qcomlic.total <= 0) {
		pr_err("file may be invalid!\n");
		return -EINVAL;
	}
	cmds = kzalloc(sizeof(struct dsi_cmd_desc) * g_qcomlic.total, GFP_KERNEL);
	if (cmds == NULL) {
		pr_err("alloc memory for cmds failed!\n");
		return -ENOMEM;
	}

	for (i = 0, i_cmd = 0; i < g_qcomlic.total; ) {
		struct dsi_cmd_desc *curr = &cmds[i_cmd++];
		struct qcom_lcd_setting_table *table = g_qcomlic.lines[i];

		curr->payload = table->opbuf;
		curr->dchdr.dtype = 0x29;
		curr->dchdr.last = 0x01;
		curr->dchdr.vc = 0x00;
		curr->dchdr.ack = 0x00;
		curr->dchdr.wait = 0x00;
		curr->dchdr.dlen = table->count;
		i++;
		if (i >= g_qcomlic.total) {
			break;
		}
		table = g_qcomlic.lines[i];
		if (LIC_DELAY(table)) {
			curr->dchdr.wait = table->count;
			i++;
		}
	}
	for (i = 0; i < i_cmd; i++)  {
		struct dsi_cmd_desc *curr = &cmds[i];
		pr_info("cmd[%d]: %02X(%d),delay(%d)\n", i, curr->payload[0], curr->dchdr.dlen, curr->dchdr.wait);
	}

	pr_info("total: %d, i_cmd: %d\n", g_qcomlic.total, i_cmd);
	qcom_pcmds.cmd_cnt = i_cmd;
	qcom_pcmds.link_state = DSI_LP_MODE;
	qcom_pcmds.cmds = cmds;

	mdss_dsi_panel_cmds_send(ctrl_pdata, &qcom_pcmds, CMD_REQ_COMMIT);

	kfree(cmds);
	if (g_qcomlic.total > 0) {
		for (i = 0; i < g_qcomlic.total; i++) {
			kfree(g_qcomlic.lines[i]);
		}
		kfree(g_qcomlic.lines);
		g_qcomlic.total = 0;
	}
	return 0;
}
/* 
 * qcom_dsi_write_regs: mipi write registers
 * @cmd: operate command
 * @data: data buffer to write, can be null
 * @size: data buffer size, can be 0
 * 
 * Return: 0 success, otherwise fail
 */
int qcom_dsi_write_regs(char cmd, char *data, int size)
{
	struct mdss_panel_data *panel_data = g_pdata;
	struct mdss_dsi_ctrl_pdata *ctrl_pdata = container_of(panel_data,
					struct mdss_dsi_ctrl_pdata, panel_data);

	int i;
	struct dsi_panel_cmds qcom_pcmds;
	struct dsi_cmd_desc wcmd;
	unsigned char *opbuf;

	opbuf = kzalloc(1+size, GFP_KERNEL);
	if (!opbuf) {
		pr_err("no memory\n");
		return -ENOMEM;
	}
	opbuf[0] = cmd;
	for (i = 0; i < size; i++) {
		opbuf[i+1] = data[i];
	}

	wcmd.payload = opbuf;
	wcmd.dchdr.dtype = 0x29;
	wcmd.dchdr.last = 0x01;
	wcmd.dchdr.vc = 0x00;
	wcmd.dchdr.ack = 0x00;
	wcmd.dchdr.wait = 0x00;
	wcmd.dchdr.dlen = size+1;

	qcom_pcmds.cmd_cnt = 1;
	qcom_pcmds.link_state = DSI_LP_MODE;
	qcom_pcmds.cmds = &wcmd;

	mdss_dsi_panel_cmds_send(ctrl_pdata, &qcom_pcmds);

	kfree(opbuf);
	return 0;
}
/* 
 * qcom_dsi_read_regs: mipi read registers
 * @addr: operate register addr
 * @buffer: buffer to save return values, cannot be null
 * @size: buffer size, cannot be 0
 * 
 * Return: 0 success, otherwise fail
 */
int qcom_dsi_read_regs(char addr, char *buffer, int size)
{
	int rx_len = 0;
	int ret;

	struct mdss_panel_data *panel_data = g_pdata;
	struct mdss_dsi_ctrl_pdata *ctrl_pdata = container_of(panel_data,
							struct mdss_dsi_ctrl_pdata, panel_data);
	
	ret = mdss_dsi_panel_cmd_read(ctrl_pdata, addr, 0x00, NULL, buffer, size);

	rx_len = ctrl_pdata->rx_len;
	
	
	if (rx_len != size) {
		pr_err("rx_len: %d, buffer_size: %d\n", rx_len, size);
		ret = -EIO;
	}

	return ret;
}

drivers\video\msm\mdss\mdss_dsi_panel.c

 

如果报错,很可能是因为使用的缓冲区超过了用户空间的地址范围。

一般系统调用会要求你使用的缓冲区不能在内核区。这个可以用set_fs()、get_fs()来解决。

在读写文件前先得到当前fs:   
mm_segment_t   old_fs=get_fs();   
并设置当前fs为内核fs:

set_fs(KERNEL_DS);   
在读写文件后再恢复原先fs:  

set_fs(old_fs);  


set_fs()、get_fs()等相关宏在文件include/asm/uaccess.h中定义。   
个人感觉这个办法比较简单。  

 在linux内核编程时,进行系统调用(如文件操作)时如果要访问用户空间的参数,可以用set_fs,get_ds等函数实现访问。

get_ds获得kernel的内存访问地址范围(IA32是4GB),

set_fs是设置当前的地址访问限制值,

get_fs是取得当前的地址访问限制值。

进程由用户态进入核态,linux进程的task_struct结构中的成员addr_limit也应该由0xBFFFFFFF变为0xFFFFFFFF(addr_limit规定了进程有用户态核内核态情况下的虚拟地址空间访问范围,在用户态,addr_limit成员值是0xBFFFFFFF也就是有3GB的虚拟内存空间,在核心态,是0xFFFFFFFF,范围扩展了1GB)。

使用这三个函数是为了安全性。为了保证用户态的地址所指向空间有效,函数会做一些检查工作。 
如果set_fs(KERNEL_DS),函数将跳过这些检查。


另外就是用flip_open函数打开文件,得到struct file *的指针fp。使用指针fp进行相应操作,如读文件可以用fp->f_ops->read。最后用filp_close()函数关闭文件。 filp_open()、filp_close()函数在fs/open.c定义,在include/linux/fs.h中声明。  

解释一点:
系统调用本来是提供给用户空间的程序访问的,所以,对传递给它的参数(比如上面的buf),它默认会认为来自用户空间,在->write()函数 中,为了保护内核空间,一般会用get_fs()得到的值来和USER_DS进行比较,从而防止用户空间程序“蓄意”破坏内核空间;