<Android> Qualcomm DSI MIPI读写
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进行比较,从而防止用户空间程序“蓄意”破坏内核空间;