HSD AIM915 916 芯片调试

        在车机显示 系统中,AIM915X和AIM916X作为车机和显示屏之间的传输芯片,车机的LVDS视频信号传到显示屏;控制信号如I2C、GPIO可实现双向透传;

一、设备树

开发平台:IMX6D

1、设备节点

2、timing参数

二、分辨率

1、修改为屏幕适配的分辨率

路径:drivers/video/fbdev/core/modedb.c

2、修改相关启动参数

烧录后会显示相关信息

cat /proc/cmdline

3、查看修改后的设备信息

cat /sys/class/graphics/fb0/mode

三、AIM915X AIM916X芯片调试

1、原理图

AIM915X:

AIM916X:

2、IIC地址

915:

Linux 7位地址为0x0C

916:

Linux 7位地址为0x2C

3、PDB

4、上电时序

5、配置步骤

#第 1 步:初始化 915X Rx/Tx 相关寄存器

i2cset -f -y 2 0x0c 0x6A 0x01 

sleep 0.01

i2cset -f -y 2 0x0c 0x66 0x02 

sleep 0.01

i2cset -f -y 2 0x0c 0xAD 0x02 

sleep 0.01

i2cset -f -y 2 0x0c 0x06 0x59 

sleep 0.01

i2cset -f -y 2 0x0c 0x2F 0x05 

sleep 0.01

i2cset -f -y 2 0x0c 0x2A 0x30 

sleep 0.01

i2cset -f -y 2 0x0c 0x2C 0x01 

sleep 0.01

i2cset -f -y 2 0x0c 0x33 0x35 

sleep 0.01

i2cset -f -y 2 0x0c 0x32 0xA2 

sleep 0.01

i2cset -f -y 2 0x0c 0x34 0x00 

sleep 0.01

i2cset -f -y 2 0x0c 0xA3 0x50 

sleep 0.01

i2cset -f -y 2 0x0c 0x03 0x3A 

#第 2 步:选择 915X/916X 工作模式,根据需要选择一种模式,进行配置。第 2 步配置完成后,延时 30ms,再配置第 3 步。

40bit 模式

i2cset -f -y 2 0x2c 0x42 0x12 

i2cset -f -y 2 0x0c 0x6c 0x12 

sleep 0.3

# 第 3 步:915X 外设 I2C 地址映射

# 此处是映射触摸的地址,GT9XX(0x14),此处可以不要,后续文章会更新调试触摸的过程
i2cset -f -y 2 0x0c 0x07 0x28 

i2cset -f -y 2 0x0c 0x08 0x28 

#第 4 步:916X Rx/Tx 相关寄存器

i2cset -f -y 2 0x0c 0x20 0x03 

i2cset -f -y 2 0x0c 0x48 0xFF 

i2cset -f -y 2 0x2c 0x67 0x37 

i2cset -f -y 2 0x2c 0x49 0x3F 

i2cset -f -y 2 0x0c 0x5A 0xF0 

i2cset -f -y 2 0x0c 0x28 0x04 

i2cset -f -y 2 0x2c 0x49 0x3A 

i2cset -f -y 2 0x2c 0x49 0x3E 

i2cset -f -y 2 0x2c 0x49 0x3F 

i2cset -f -y 2 0x2c 0x49 0x3E 

i2cset -f -y 2 0x2c 0x49 0x3F 

i2cset -f -y 2 0x2c 0x49 0x3E 

i2cset -f -y 2 0x2c 0x49 0x3F 

i2cset -f -y 2 0x2c 0x49 0x3E 

i2cset -f -y 2 0x2c 0x49 0x3F 

i2cset -f -y 2 0x2c 0x49 0x3E 

i2cset -f -y 2 0x2c 0x49 0x3F 

i2cset -f -y 2 0x2c 0x49 0x3E 

i2cset -f -y 2 0x2c 0x49 0x3F 

i2cset -f -y 2 0x2c 0x49 0x3E 

i2cset -f -y 2 0x2c 0x49 0x3F 

i2cset -f -y 2 0x2c 0x49 0x3E 

i2cset -f -y 2 0x2c 0x49 0x3F 

i2cset -f -y 2 0x2c 0x49 0x3E 

i2cset -f -y 2 0x2c 0x49 0x3F 

i2cset -f -y 2 0x2c 0x49 0x3E 

i2cset -f -y 2 0x2c 0x49 0x3F 

i2cset -f -y 2 0x2c 0x5A 0xF4 

i2cset -f -y 2 0x2c 0x40 0xF8 

i2cset -f -y 2 0x2c 0x28 0x00 

i2cset -f -y 2 0x2c 0x40 0xF0 

i2cset -f -y 2 0x2c 0x67 0x33 

#第 5 步:配置 915X 的 GPIO[3:0]GPIO[5:8]至 916X 相应 GPIO 的透传(透传指的是915端控制远端916的GPIO状态)

GPIO2 用作 PWM 915 in 916 out

i2cget -y 2 0x0c 0x0B

# 916端本地使能背光

i2cset -y 2 0x2C 0x1D 0x99 

#第 6 步:配置 915X 端接收 916X 端输入的中断

i2cset -f -y 2 0x0C 0x7F 0x21 

#第 7 步:915X 完成初始化

i2cset -f -y 2 0x0c 0x03 0x1A 

i2cset -f -y 2 0x0c 0x44 0x16 

i2cset -f -y 2 0x0c 0x6F 0x80 

i2cset -f -y 2 0x0c 0x72 0x02 

i2cset -f -y 2 0x0c 0x72 0x03 

i2cset -f -y 2 0x0c 0x6F 0x00 

i2cset -f -y 2 0x0c 0x25 0x07 

i2cset -f -y 2 0x0c 0x26 0x00 

i2cset -f -y 2 0x0c 0x6A 0xC9 

i2cset -f -y 2 0x0c 0x72 0x02 

i2cset -f -y 2 0x0c 0x72 0x03 

i2cset -f -y 2 0x0c 0x73 0x02 

i2cset -f -y 2 0x0c 0x73 0x03 

i2cset -f -y 2 0x0c 0x26 0x04 

i2cset -f -y 2 0x0c 0x6a 0x01 

i2cset -f -y 2 0x0c 0x72 0xFF 

i2cset -f -y 2 0x0c 0x23 0x37 

i2cset -f -y 2 0x0c 0x25 0x0F 

i2cset -f -y 2 0x0c 0x26 0x00 

i2cset -f -y 2 0x0c 0x6F 0x80 

i2cset -f -y 2 0x0c 0x71 0xBF 

i2cset -f -y 2 0x0c 0x72 0xBE 

i2cset -f -y 2 0x0c 0x71 0xFF 

i2cset -f -y 2 0x0c 0x72 0xFF 

i2cset -f -y 2 0x0c 0x72 0xFE 

i2cset -f -y 2 0x0c 0x72 0xFF 

i2cset -f -y 2 0x0c 0x72 0xFE 

i2cset -f -y 2 0x0c 0x72 0xFF 

i2cset -f -y 2 0x0c 0x72 0xFE 

i2cset -f -y 2 0x0c 0x72 0xFF 

i2cset -f -y 2 0x0c 0x72 0xFE 

i2cset -f -y 2 0x0c 0x72 0xFF 

i2cset -f -y 2 0x0c 0x72 0xFE 

i2cset -f -y 2 0x0c 0x72 0xFF 

i2cset -f -y 2 0x0c 0x72 0xFE 

i2cset -f -y 2 0x0c 0x72 0xFF 

i2cset -f -y 2 0x0c 0x72 0xFE 

i2cset -f -y 2 0x0c 0x72 0xFF 

i2cset -f -y 2 0x0c 0x72 0xFE 

i2cset -f -y 2 0x0c 0x72 0xFF 

i2cset -f -y 2 0x0c 0x72 0xFE 

i2cset -f -y 2 0x0c 0x72 0xFF 

i2cset -f -y 2 0x0c 0x26 0x04 

i2cset -f -y 2 0x0c 0x6a 0x09 

i2cset -f -y 2 0x0c 0x6f 0x00 

i2cset -f -y 2 0x0c 0x6a 0x01 

i2cset -f -y 2 0x0c 0x23 0x33 

#第八步:驱动编写

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/unistd.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/semaphore.h>
#include <linux/kthread.h>
#include <linux/notifier.h>
#include <linux/fb.h>
#include <linux/reboot.h>

#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include "aim915916.h"


#define DYNAMIC_DETECT_AIM955956 1

static struct i2c_client *aim955_client = NULL;
static struct i2c_client *aim956_client = NULL;
#ifdef DYNAMIC_DETECT_AIM955956
struct task_struct *glink_detect_kt = NULL;
static int link_detect_kernel_thread(void *data);
static void link_detect_kernel_thread_cleanup_module(void);
#endif
static int aim955_init_status = -1;	
static int aim956_init_status = -1;
static int reconfig_aim955956_status = -1;

int reset_915(void)
{
    int ret = 0;
    unsigned char value = 0;
    unsigned char addr = 0;

		addr		=		0x01;
		value		=		0x01;
		if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
			printk("I2C_RESET\n");
			return ret;
		}
		msleep(100);
}


int remote_mode(void)
{
    int ret = 0;
    unsigned char value = 0;
    unsigned char addr = 0;

		addr		=		0x0a;
		value		=		0x05;
		if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
			printk("I2C_AIM956 error: 0x%x 0x%x\n", addr, value);
			return ret;
		}
	
		addr		=		0x1c;
		value		=		0x03;
		if((ret = i2c_smbus_write_byte_data(aim956_client, addr, value)) < 0){
			printk("I2C_AIM956 error: 0x%x 0x%x\n", addr, value);
			return ret;
		}
		


}

int reset_mode(int mode)
{
    int ret = 0;
    unsigned char value = 0;
    unsigned char addr = 0;

    if (aim956_client == NULL) {
        printk("aim956_client is NULL\n");
        return -EINVAL;
    }
//gpio1 RESET
    if (mode == resetin) {
        addr = 0x1D;
        value = 0x53;
		printk("reset set input\n");
    } else if (mode == resetoutlow) {
        addr = 0x1D;
        value = 0x01;
		printk("reset set ouput\n");
    }else if (mode == resetouthigh) {
        addr = 0x1D;
        value = 0x09;
		printk("reset set ouput\n");
    } else if(mode == intin){//INT input  gpio0
		addr = 0x1c;
        value = 0x03;
		printk("INT set input\n");
    }else if(mode == intouthigh){// INT output
		addr = 0x1c;
        value = 0x09;
		printk("INT set input1\n");
    }else if(mode == intoutlow){// INT output
		addr = 0x1c;
        value = 0x01;
		printk("INT set input1\n");
    }

	
    ret = i2c_smbus_write_byte_data(aim956_client, addr, value);
    if (ret < 0) {
        printk("I2C_AIM956 error: addr=0x%x value=0x%x ret=%d\n", addr, value, ret);
        return ret;
    }

    return 0;
}

EXPORT_SYMBOL(reset_mode); 
EXPORT_SYMBOL(remote_mode);


//#define TIME_DELAY 100


int configure_aim956955(void)
{
    int ret = 0;
    unsigned char value = 0;
    unsigned char addr = 0;
	
	//第二步 40bit
	addr		=		0x42;
	value		=		0x12;
	if((ret = i2c_smbus_write_byte_data(aim956_client, addr, value)) < 0){
		printk("I2C_AIM956 error: 0x%x 0x%x\n", addr, value);
		return ret;
	}

	addr		=		0x6c;
	value		=		0x12;
	if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
		printk("I2C_AIM956 error: 0x%x 0x%x\n", addr, value);
		return ret;
	}
	msleep(30);


    //#第 3 步:955 外设 I2C 地址映射
    //TP i2caddr,0x14 * 2 = 0x28
    
    addr 	=	0x07;
    value 	= 	0x28;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
	
    addr 	=	0x08;
    value 	= 	0x28;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }

    //light
    
    addr 	=	0x51;
    value 	= 	0x52;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
    addr 	=	0x58;
    value 	= 	0x52;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }

	//
	
	#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	//printk("step3\n");

    //第四步 916X Rx/Tx 相关寄存器
    
    addr 	=	0x20;
    value 	= 	0x03;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif

    addr 	=	0x48;
    value 	= 	0xFF;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif

    addr 	=	0x67;
    value 	= 	0x37;
    if((ret = i2c_smbus_write_byte_data(aim956_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif

    addr 	=	0x49;
    value 	= 	0x3F;
    if((ret = i2c_smbus_write_byte_data(aim956_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x5A;
    value 	= 	0xF0;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x28;
    value 	= 	0x04;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x49;
    value 	= 	0x3A;
    if((ret = i2c_smbus_write_byte_data(aim956_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif

	addr 	=	0x49;
    value 	= 	0x3E;
    if((ret = i2c_smbus_write_byte_data(aim956_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif

	addr 	=	0x49;
    value 	= 	0x3F;
    if((ret = i2c_smbus_write_byte_data(aim956_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x5A;
    value 	= 	0xF4;
    if((ret = i2c_smbus_write_byte_data(aim956_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x40;
    value 	= 	0xF8;
    if((ret = i2c_smbus_write_byte_data(aim956_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x28;
    value 	= 	0x00;
    if((ret = i2c_smbus_write_byte_data(aim956_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x40;
    value 	= 	0xF0;
    if((ret = i2c_smbus_write_byte_data(aim956_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x67;
    value 	= 	0x33;
    if((ret = i2c_smbus_write_byte_data(aim956_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif

	//printk ("step 4\n");
	

    //第 5 步:配置 915 的 GPIO[3:0]、D_GPIO[3:0]、GPIO[8:5]_reg 至 956 相应 GPIO 的透传
    //GPIO2 PWM 

    addr 	=	0x0B;
    value 	= 	0x30;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }

    addr 	=	0x1D;
    value 	= 	0x50;
    if((ret = i2c_smbus_write_byte_data(aim956_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }

	//printk ("step 5\n");
        
    //#第 6 步:配置 915 端接收 916 端输入的中断
    addr 	=	0x7F;
    value 	= 	0x21;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif


    //#第 7 步:955 完成初始化
    addr 	=	0x03;
    value 	= 	0x1A;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif

    addr 	=	0x44;
    value 	= 	0x16;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM956 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif

    addr 	=	0x6F;
    value 	= 	0x80;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0x02;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0x03;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x6F;
    value 	= 	0x00;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x25;
    value 	= 	0x07;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x26;
    value 	= 	0x00;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x6A;
    value 	= 	0xC9;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0x02;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0x03;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0x02;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0x03;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x26;
    value 	= 	0x04;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x6A;
    value 	= 	0x01;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xFF;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x23;
    value 	= 	0x37;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x25;
    value 	= 	0x0F;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x26;
    value 	= 	0x00;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x6F;
    value 	= 	0x80;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x71;
    value 	= 	0xBF;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xBF;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x71;
    value 	= 	0xFF;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	//
	addr 	=	0x72;
    value 	= 	0xFF;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xFE;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xFF;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xFE;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xFF;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xFE;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xFF;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xFE;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xFF;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xFE;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xFF;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xFE;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xFF;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xFE;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xFF;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xFE;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xFF;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x72;
    value 	= 	0xFE;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
	#ifdef TIME_DELAY
	msleep(TIME_DELAY);
#endif

	
	addr 	=	0x72;
    value 	= 	0xFF;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	//
	addr 	=	0x26;
    value 	= 	0x04;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif

	addr 	=	0x6A;
    value 	= 	0x09;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x6F;
    value 	= 	0x00;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x6A;
    value 	= 	0x01;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
			#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif
	
	addr 	=	0x23;
    value 	= 	0x33;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
		#ifdef TIME_DELAY
	msleep(TIME_DELAY);
	#endif

	//iic直通模式
/*	
	addr 	=	0x03;
    value 	= 	0x08;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }

	//addr 	=	0x0d;
    //value 	= 	0x9e;
    //if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
    //    printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
    //    return ret;
   // }
//add 916X 

	addr 	=	0x03;
    value 	= 	0x09;
    if((ret = i2c_smbus_write_byte_data(aim956_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }

	addr 	=	0x04;
    value 	= 	0x80;
    if((ret = i2c_smbus_write_byte_data(aim956_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
*/
	
    printk("AIM955 AIM956 single lvds 20221018 Setup success.\n");
    return ret;
}

int configure_aim955(void)
{
    int ret = 0;
	unsigned char value = 0;
	unsigned char addr = 0;

	reset_915();//初始化失败,reset
	msleep(100);
	

#if 0
    //第 0 步骤: 初始化 955 之前,先重启设备
    addr 	=	0x01;
    value 	= 	0x02;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 reset before kernel driver init: 0x%x 0x%x, ret: %d\n", addr, value, ret);
        //return ret;
    }
    //955 重启过后, 延迟100ms.
    msleep(100);
#endif

    //第 1 步:初始化 915 Rx/Tx 相关寄存器
    addr 	=	0x6A;
    value 	= 	0x01;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
    msleep(1);

    addr 	=	0x66;
    value 	= 	0x02;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
    msleep(1);

    /*DES ID*/
    addr 	=	0xAD;
    value 	= 	0x02;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
    msleep(1);

    addr 	=	0x06;
    value 	= 	0x59;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
    msleep(1);

    addr 	=	0x2F;
    value 	= 	0x05;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
    msleep(1);

    addr 	=	0x2A;
    value 	= 	0x30;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
    msleep(1);

    addr 	=	0x2C;
    value 	= 	0x01;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
    msleep(1);

    addr 	=	0x33;
    value 	= 	0x35;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
    msleep(1);

    addr 	=	0x32;
    value 	= 	0xA2;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
    msleep(1);

    addr 	=	0x34;
    value 	= 	0x00;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
    msleep(1);

    addr 	=	0xA3;
    value 	= 	0x50;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
    msleep(1);

    addr 	=	0x03;
    value 	= 	0x3A;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 error: 0x%x 0x%x\n", addr, value);
        return ret;
    }
    msleep(1);

    return ret;
}

static unsigned long is_cabled = 0;

static ssize_t aim955956_is_link_show(struct device *dev,
			struct device_attribute *attr, char *buf)
{
    printk("aim955956_is_link_show ...\n");

    u8 detect_reg = 0x07;
    //u8 detect_reg_data = 0;
    int data = i2c_smbus_read_byte_data(aim956_client, detect_reg);
    printk("aim - 955 - 956 is_cabled: 0x%x, invert: 0x%x\n", data, (data & 0xff));
    if((data & 0xff) == 0x19){
        is_cabled = 1;
    }else{
        is_cabled = 0;
    }
	return sprintf(buf, "%d\n", is_cabled);
}

static ssize_t aim955_probe_status_show(struct device *dev,
			struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "%d\n", aim955_init_status);
}

static ssize_t aim956_probe_status_show(struct device *dev,
			struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "%d\n", aim956_init_status);
}

#ifdef DYNAMIC_DETECT_AIM955956
static ssize_t aim956_reconfig_num_show(struct device *dev,
			struct device_attribute *attr, char *buf)
{
    return sprintf(buf, "%d\n", reconfig_aim955956_status);
}
#endif

static DEVICE_ATTR(is_linked, 0664, aim955956_is_link_show,NULL);
static DEVICE_ATTR(955_probe, 0664, aim955_probe_status_show,NULL);
static DEVICE_ATTR(956_probe, 0664, aim956_probe_status_show,NULL);

#ifdef DYNAMIC_DETECT_AIM955956
static DEVICE_ATTR(aim955956_reconfig_num, 0664, aim956_reconfig_num_show,NULL);
#endif

static struct attribute *aim955956_attributes[] = {
	&dev_attr_is_linked.attr,
	&dev_attr_955_probe.attr,
	&dev_attr_956_probe.attr,
#ifdef DYNAMIC_DETECT_AIM955956    
	&dev_attr_aim955956_reconfig_num.attr,
#endif    
	NULL
};

static const struct attribute_group aim955956_attr_group = {
	.attrs = aim955956_attributes,
};

static int __init aim956_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret = 0;
    aim956_client = client;
    printk("aim956_probe ... \n");

    ret = configure_aim956955();
    aim956_init_status = ret;
    printk("aim956_probe ... ..., ret = %d\n", ret);

#ifdef DYNAMIC_DETECT_AIM955956
	glink_detect_kt = kthread_create(link_detect_kernel_thread, (void*)aim956_client , "glink_detect_kt");
	if(!IS_ERR(glink_detect_kt))
		wake_up_process(glink_detect_kt);
	else {
		printk("create glink_detect_kt watchdog thread failed!!\n");
		glink_detect_kt = 0;
	}
#endif
	
    return ret;
}

static int aim956_remove(struct i2c_client *client)
{
#ifdef DYNAMIC_DETECT_AIM955956
	link_detect_kernel_thread_cleanup_module();
#endif
    return 0;
}

static const struct i2c_device_id aim956_id[] = {
    { "aim956", 0 },
    { }
};
	
static const struct of_device_id aim956_match_table[] = {
		{.compatible = "aimx916",},
		{ },
	};


static struct i2c_driver aim956_driver = {
    .driver     = {
        .name   = "aim956",
        .owner  = THIS_MODULE,
        .of_match_table = aim956_match_table,
    },
    .probe      = aim956_probe,
    .remove     = aim956_remove,
    .id_table   = aim956_id,
};


#ifdef DYNAMIC_DETECT_AIM955956

int reconfigure_aim955956(void)
{
    int ret = 0;
	unsigned char value = 0;
	unsigned char addr = 0;
#if 1
    //第 0 步骤: 初始化 955 之前,先重启设备
    addr 	=	0x01;
    value 	= 	0x01;
    if((ret = i2c_smbus_write_byte_data(aim955_client, addr, value)) < 0){
        printk("I2C_AIM955 reset before kernel driver init: 0x%x 0x%x, ret: %d\n", addr, value, ret);
        //return ret;
    }
    //955 重启过后, 延迟100ms.
    msleep(100);
#endif
	reconfig_aim955956_status++;
    ret = configure_aim955();
    msleep(10);
    if (ret < 0)
        return ret;
    else
        ret = configure_aim956955();

    return ret;
}

bool isLinkDetected(struct i2c_client *client)
{
    u8 detect_reg = 0x67;
    int data = i2c_smbus_read_byte_data(aim956_client, detect_reg);
    //printk("isLinkDetected aim - 955 - 956 is_cabled: 0x%x, invert: 0x%x\n", data, (data & 0xff));
    if((data & 0xff) == 0x33){
        return true;
    }else{
        return false;
    }
}

static void link_detect_kernel_thread_cleanup_module(void)
{  
	if(glink_detect_kt){  
		kthread_stop(glink_detect_kt);  
		glink_detect_kt = NULL;  
	}  
}  

static int link_detect_kernel_thread(void *data)
{
	struct i2c_client *client = (struct i2c_client *)data;
	while(!kthread_should_stop())
	{
		if(isLinkDetected(client) == true) {
			// do nothing.
		} else {
			printk("----------------link_detect_kernel_thread call reconfigure_aim955956()-------------- \n");
			reconfig:
			if(reconfigure_aim955956() < 0){
				schedule_timeout_interruptible(msecs_to_jiffies(500));
				goto reconfig;
			}
		}
		schedule_timeout_interruptible(msecs_to_jiffies(1000));
	}

}
#endif


static int __init aim955_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret = 0;
    aim955_client = client;
    printk("aim955_probe ... \n");
    ret = configure_aim955();
    aim955_init_status = ret;
    printk("aim955_probe ... ..., ret = %d\n", ret);

    sysfs_create_group(&aim955_client->dev.kobj,&aim955956_attr_group);

    return ret;
}

static int aim955_remove(struct i2c_client *client)
{
    sysfs_remove_group(&aim955_client->dev.kobj,&aim955956_attr_group);
    return 0;
}

static const struct i2c_device_id aim955_id[] = {
    { "aim955", 0 },
    { }
};

static const struct of_device_id aim955_match_table[] = {
	{.compatible = "aimx915",},
	{ },
};



static struct i2c_driver aim955_driver = {
    .driver     = {
        .name   = "aim955",
        .owner  = THIS_MODULE,
        .of_match_table = aim955_match_table,
    },
    .probe      = aim955_probe,
    .remove     = aim955_remove,
    .id_table   = aim955_id,
    
};

static int __init aim955956_init(void)
{
	int ret = 0;

    printk("aim955956_init\n");

	ret = i2c_add_driver(&aim955_driver);
	ret = i2c_add_driver(&aim956_driver);

    return ret;
	
}

static void __exit aim955956_exit(void)
{
	printk("aim955956_exit\n");

    i2c_del_driver(&aim955_driver);
	i2c_del_driver(&aim956_driver); 
}

MODULE_AUTHOR("xth");
MODULE_DESCRIPTION("aim955-956-xxx Driver");
MODULE_LICENSE("GPL");

module_init(aim955956_init);
//subsys_initcall(aim955956_init);

module_exit(aim955956_exit);


驱动写的比较简单,大家可自行完善

四、测试

问题1:

写入寄存器测试后,出现图像偏移的情况

原因: 屏配置了 SYNC 模式, RGB芯片输出 DE 模式

解决方案:拆掉R103电阻,使DE悬空后显示正常

问题2:驱动寄存器初始化会失败的情况

915有对应的复位寄存器,在每次初始化915X寄存器之前进行复位,重新写入

添加915复位代码:

I2C重试机制:

修改后反复测试20次未出现I2C初始化失败的情况

热插拔检测:

  1. isLinkDetected 函数:通过I2C读取设备的特定寄存器(0x67),检查返回的数据是否为0x33。如果是,返回true,表示检测到连接;否则返回false

  2. link_detect_kernel_thread_cleanup_module 函数:如果全局线程glink_detect_kt存在,则停止该线程并将其指针置为NULL,用于清理工作。

  3. link_detect_kernel_thread 函数:这是一个内核线程,循环检查链接状态。如果检测到链接,则什么都不做;如果未检测到链接,则调用reconfigure_aim955956函数进行重新配置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

春风从不入睡、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值