Ft5x06_ts驱动程序的重写

     我们分析内核源码可知,Ft5x06_ts触摸屏驱动程序涉及如下内容:

1.  Linux下I2C驱动框架

2.  Linux下中断系统

3.  Linux下workqueue机制

4.  Linux下输入子系统

5.  中断初始化、I2C控制器相关初始化。在arch/arm/mach-exynos/mach-smdk4x12.c中的s3c_i2c1_set_platdata(&tiny4412_i2c1_data);

    

      Ft5x06_ts触摸屏驱动相关的源文件包括:  Ft5x06_ts.c、Ft5x06_ts.h、plat/ft5x0x_touch.h、Ts-if.c、Tiny4412_1wire_host.c(背光控制和一线精准触摸)等。

      内核中,Ft5x06_ts触摸屏驱动都被静态地编译进了内核,不方便调试,我们如何动态地加载此驱动呢,需要基于bus-dev-drv模型重新设计此驱动程序,步骤如下:


1.  配置内核

(1)去掉内核原有的驱动

make  menuconfig后去掉 < >   FocalTech ft5x0x TouchScreen driver一项。

  Symbol: TOUCHSCREEN_FT5X0X_SINGLE [=n]                                                            |
 Type  : boolean                                                                                   |
  Prompt: Disable MULTI-Touch Mode                                                                  |
     Defined at drivers/input/touchscreen/Kconfig:327                                                |
     Depends on: !S390 && !UML && INPUT [=y] && INPUT_TOUCHSCREEN [=y] && TOUCHSCREEN_FT5X0X [=n]    |
     Location:                                                                                       |
       -> Device Drivers                                                                             |
         -> Input device support                                                                     |
           -> Generic input layer (needed for keyboard, mouse, ...) (INPUT [=y])                     |
             -> Touchscreens (INPUT_TOUCHSCREEN [=y])                                                |
               -> FocalTech ft5x0x TouchScreen driver (TOUCHSCREEN_FT5X0X [=n])


(2)修改内核源码

      修改mach-Tiny4412.c中2215行,注释掉即可。

     //i2c_register_board_info(1, smdk4x12_i2c_devs1,ARRAY_SIZE(smdk4x12_i2c_devs1));

     如果配置内核时,去掉了ft05txx驱动,这里不用注释掉,因为此函数中有个条件编译,如果去掉ft05txx驱动,#ifdef CONFIG_TOUCHSCREEN_FT5X0X就不成立


2.  实现dev


i2c_touchscreen_dev.c :


#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/regmap.h>
#include <linux/slab.h>

#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>


#include <plat/ft5x0x_touch.h>
static struct ft5x0x_i2c_platform_data ft5x0x_pdata = {
	.gpio_irq		= EXYNOS4_GPX1(6),//对应原理图中EINT14
	.irq_cfg		= S3C_GPIO_SFN(0xf),
	.screen_max_x	= 800,
	.screen_max_y	= 1280,
	.pressure_max	= 255,
};

//0x50表示I2C设备的地址,一般在	I2C设备芯片手册可以查到
static struct i2c_board_info ft5x0x_info = {	
	I2C_BOARD_INFO("ft5x0x_ts", (0x70 >> 1)),//这个名字要和drv程序中的id_table中名字要一样
	.platform_data = &ft5x0x_pdata,
};

static struct i2c_client *ft5x0x_client;

static int ft5x0x_dev_init(void)
{
	struct i2c_adapter *i2c_adap;
/*1.触摸屏这个I2C设备挂接在了处理器的第一条总线上
  2.把这个总线号改为5,也能成功加载此驱动,原因在于i2c_new_device而不是i2c_new_probed_device方法
*/
	int busNum = 1 ;
	
	printk("ft5x0x dev of bus-dev-drv module_init!\n");
	
	i2c_adap = i2c_get_adapter(busNum);//这里要实验的触摸屏是挂接在第1条I2C总线上的,所以这里的参数是1
	if (!i2c_adap) {
		pr_err("failed to get adapter i2c%d\n", busNum);
		return -ENODEV;
	}
	
	ft5x0x_client = i2c_new_device(i2c_adap, &ft5x0x_info);//设置和注册i2c_client结构体
	if (!ft5x0x_client){
		//pr_err("failed to register %s to i2c%d\n",ft5x0x_info.type, busNum);
		pr_err("failed to register ft5x0x to i2c%d\n",busNum);
		return -ENODEV;
	}	
	
	i2c_put_adapter(i2c_adap);
	
	return 0;
}

static void ft5x0x_dev_exit(void)
{
	printk("ft5x0x dev of bus-dev-drv module_exit!\n");
	i2c_unregister_device(ft5x0x_client);
}


module_init(ft5x0x_dev_init);
module_exit(ft5x0x_dev_exit);
MODULE_LICENSE("GPL");



3.  实现drv


i2c_touchscreen_drv.c :

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#include <linux/err.h>

#ifdef CONFIG_HAS_EARLYSUSPEND
#include <linux/earlysuspend.h>
#endif

#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <plat/ctouch.h>
#include <plat/ft5x0x_touch.h>

#include "ft5x06_ts.h"

#ifndef CONFIG_TOUCHSCREEN_FT5X0X_SINGLE
#define CONFIG_FT5X0X_MULTITOUCH		1
#endif

#define TOUCH_MAX_X						0x700
#define TOUCH_MAX_Y						0x400

static int swap_xy = 0;
static int scal_xy = 0;


/*---------------------------------------------------------
 * Chip I/O operations
 */

static struct i2c_client *this_client;

static int ft5x0x_i2c_rxdata(char *rxdata, int length) 
{
	int ret;
	struct i2c_msg msgs[] = {
		{
			.addr	= this_client->addr,
			.flags	= 0,
			.len	= 1,
			.buf	= rxdata,
		},
		{
			.addr	= this_client->addr,
			.flags	= I2C_M_RD,
			.len	= length,
			.buf	= rxdata,
		},
	};

	ret = i2c_transfer(this_client->adapter, msgs, 2);
	if (ret < 0)
		pr_err("%s: i2c read error: %d\n", __func__, ret);

	return ret;
}

#if 0
static int ft5x0x_i2c_txdata(char *txdata, int length) 
{
	int ret;
	struct i2c_msg msg[] = {
		{
			.addr	= this_client->addr,
			.flags	= 0,
			.len	= length,
			.buf	= txdata,
		},
	};

	ret = i2c_transfer(this_client->adapter, msg, 1);
	if (ret < 0)
		pr_err("%s: i2c write error: %d\n", __func__, ret);

	return ret;
}

static int ft5x0x_write_reg(u8 addr, u8 val) 
{
	u8 buf[4];
	int ret;

	buf[0] = addr;
	buf[1] = val;
	ret = ft5x0x_i2c_txdata(buf, 2);
	if (ret < 0) {
		pr_err("write 0x%02x to reg (0x%02x) failed, %d", addr, val, ret);
		return -1;
	}

	return 0;
}
#endif

static int ft5x0x_read_reg(u8 addr, u8 *pdata) 
{
	u8 buf[4] = { 0 };
	struct i2c_msg msgs[] = {
		{
			.addr	= this_client->addr,
			.flags	= 0,
			.len	= 1,
			.buf	= buf,
		},
		{
			.addr	= this_client->addr,
			.flags	= I2C_M_RD,
			.len	= 1,
			.buf	= buf,
		},
	};
	int ret;

	buf[0] = addr;

	ret = i2c_transfer(this_client->adapter, msgs, 2);
	if (ret < 0) {
		pr_err("read reg (0x%02x) error, %d\n", addr, ret);
	} else {
		*pdata = buf[0];
	}

	return ret;
}

static int ft5x0x_read_fw_ver(unsigned char *val)
{
	int ret;

	*val = 0xff;
	ret = ft5x0x_read_reg(FT5X0X_REG_FIRMID, val);
	if (*val == 0x06) {
#if 0
		swap_xy = 1;
		scal_xy = 1;
#endif
	} else {
		/* TODO: Add support for other version */
	}

	return ret;
}


/*---------------------------------------------------------
 * Touch core support
 */

static void ft5x0x_ts_report(struct ft5x0x_ts_data *ts) 
{
	struct ft5x0x_event *event = &ts->event;
	int x, y;
	int i;

#ifdef CONFIG_FT5X0X_MULTITOUCH
	for (i = 0; i < event->touch_point; i++) {
		if (swap_xy) {
			x = event->y[i];
			y = event->x[i];
		} else {
			x = event->x[i];
			y = event->y[i];
		}

		if (scal_xy) {
			x = (x * ts->screen_max_x) / TOUCH_MAX_X;
			y = (y * ts->screen_max_y) / TOUCH_MAX_Y;
		}

		input_report_abs(ts->input_dev, ABS_MT_POSITION_X, x);
		input_report_abs(ts->input_dev, ABS_MT_POSITION_Y, y);

		input_report_abs(ts->input_dev, ABS_MT_PRESSURE, event->pressure);
		input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, event->pressure);
		input_report_abs(ts->input_dev, ABS_MT_TRACKING_ID, i);

		input_mt_sync(ts->input_dev);
	}
#else
	if (event->touch_point == 1) {
		if (swap_xy) {
			x = event->y[i];
			y = event->x[i];
		} else {
			x = event->x[i];
			y = event->y[i];
		}

		if (scal_xy) {
			x = (x * ts->screen_max_x) / TOUCH_MAX_X;
			y = (y * ts->screen_max_y) / TOUCH_MAX_Y;
		}

		input_report_abs(ts->input_dev, ABS_X, x);
		input_report_abs(ts->input_dev, ABS_Y, y);
		input_report_abs(ts->input_dev, ABS_PRESSURE, event->pressure);
	}

	input_report_key(ts->input_dev, BTN_TOUCH, 1);
#endif

	input_sync(ts->input_dev);
}

static void ft5x0x_ts_release(struct ft5x0x_ts_data *ts) 
{
#ifdef CONFIG_FT5X0X_MULTITOUCH
#if 0
	/* NOT needed for ICS */
	input_report_abs(ts->input_dev, ABS_MT_TOUCH_MAJOR, 0);
#endif
	input_mt_sync(ts->input_dev);
#else
	input_report_abs(ts->input_dev, ABS_PRESSURE, 0);
	input_report_key(ts->input_dev, BTN_TOUCH, 0);
#endif

	input_sync(ts->input_dev);
}

static int ft5x0x_read_data(struct ft5x0x_ts_data *ts) 
{
	struct ft5x0x_event *event = &ts->event;
	u8 buf[32] = { 0 };
	int ret;

#ifdef CONFIG_FT5X0X_MULTITOUCH
	ret = ft5x0x_i2c_rxdata(buf, 31);
#else
	ret = ft5x0x_i2c_rxdata(buf, 7);
#endif
	if (ret < 0) {
		printk("%s: read touch data failed, %d\n", __func__, ret);
		return ret;
	}

	memset(event, 0, sizeof(struct ft5x0x_event));

	event->touch_point = buf[2] & 0x07;

	if (!event->touch_point) {
		ft5x0x_ts_release(ts);
		return 1;
	}

#ifdef CONFIG_FT5X0X_MULTITOUCH
	switch (event->touch_point) {
		case 5:
			event->x[4] = (s16)(buf[0x1b] & 0x0F)<<8 | (s16)buf[0x1c];
			event->y[4] = (s16)(buf[0x1d] & 0x0F)<<8 | (s16)buf[0x1e];
		case 4:
			event->x[3] = (s16)(buf[0x15] & 0x0F)<<8 | (s16)buf[0x16];
			event->y[3] = (s16)(buf[0x17] & 0x0F)<<8 | (s16)buf[0x18];
		case 3:
			event->x[2] = (s16)(buf[0x0f] & 0x0F)<<8 | (s16)buf[0x10];
			event->y[2] = (s16)(buf[0x11] & 0x0F)<<8 | (s16)buf[0x12];
		case 2:
			event->x[1] = (s16)(buf[0x09] & 0x0F)<<8 | (s16)buf[0x0a];
			event->y[1] = (s16)(buf[0x0b] & 0x0F)<<8 | (s16)buf[0x0c];
		case 1:
			event->x[0] = (s16)(buf[0x03] & 0x0F)<<8 | (s16)buf[0x04];
			event->y[0] = (s16)(buf[0x05] & 0x0F)<<8 | (s16)buf[0x06];
			break;
		default:
			printk("%s: invalid touch data, %d\n", __func__, event->touch_point);
			return -1;
	}
#else
	if (event->touch_point == 1) {
		event->x[0] = (s16)(buf[0x03] & 0x0F)<<8 | (s16)buf[0x04];
		event->y[0] = (s16)(buf[0x05] & 0x0F)<<8 | (s16)buf[0x06];
	}
#endif

	event->pressure = 200;

	return 0;
}

static void ft5x0x_ts_pen_irq_work(struct work_struct *work) {
	struct ft5x0x_ts_data *ts = container_of(work, struct ft5x0x_ts_data, work);

	if (!ft5x0x_read_data(ts)) {
		ft5x0x_ts_report(ts);
	}

	enable_irq(this_client->irq);
}

static irqreturn_t ft5x0x_ts_interrupt(int irq, void *dev_id) {
	struct ft5x0x_ts_data *ts = dev_id;

	disable_irq_nosync(this_client->irq);

	if (!work_pending(&ts->work)) {
		queue_work(ts->queue, &ts->work);
	}

	return IRQ_HANDLED;
}


/*---------------------------------------------------------
 * I2C client driver functions
 */

#ifdef CONFIG_HAS_EARLYSUSPEND
static void ft5x0x_ts_suspend(struct early_suspend *handler)
{
#if 0
	struct ft5x0x_ts_data *ts;

	ts = container_of(handler, struct ft5x0x_ts_data, early_suspend);

	disable_irq(this_client->irq);
	cancel_work_sync(&ts->work);
	flush_workqueue(ts->queue);

	ft5x0x_set_reg(FT5X0X_REG_PMODE, PMODE_HIBERNATE);
#endif

	printk("ft5x0x_ts: suspended\n");
}

static void ft5x0x_ts_resume(struct early_suspend *handler)
{
#if 0
	/* Wakeup: output_L --> 100ms --> output_H --> 100ms */
	enable_irq(this_client->irq);
#endif

	printk("ft5x0x_ts: resumed\n");
}
#endif

static int ft5x0x_ts_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
	struct ft5x0x_i2c_platform_data *pdata;
	struct ft5x0x_ts_data *ts;
	struct input_dev *input_dev;
	unsigned char val;
	unsigned int ctp_id;
	int err = -EINVAL;

	printk("ft5x0x_ts_probe match OK!\n");
	
	
	ctp_id = tiny4412_get_ctp();
	if (ctp_id != CTP_FT5X06 && ctp_id != CTP_AUTO) {
		return -ENODEV;
	}

	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
		err = -ENODEV;
		goto exit_check_functionality_failed;
	}

	ts = kzalloc(sizeof(*ts), GFP_KERNEL);
	if (!ts) {
		err = -ENOMEM;
		goto exit_alloc_data_failed;
	}

	pdata = client->dev.platform_data;
	if (!pdata) {
		dev_err(&client->dev, "failed to get platform data\n");
		goto exit_no_pdata;
	}

	ts->screen_max_x = pdata->screen_max_x;
	ts->screen_max_y = pdata->screen_max_y;
	ts->pressure_max = pdata->pressure_max;

	ts->gpio_irq = pdata->gpio_irq;
	if (ts->gpio_irq != -EINVAL) {
		client->irq = gpio_to_irq(ts->gpio_irq);
	} else {
		goto exit_no_pdata;
	}
	if (pdata->irq_cfg) {
		s3c_gpio_cfgpin(ts->gpio_irq, pdata->irq_cfg);
		s3c_gpio_setpull(ts->gpio_irq, S3C_GPIO_PULL_NONE);
	}

	ts->gpio_wakeup = pdata->gpio_wakeup;
	ts->gpio_reset = pdata->gpio_reset;

	INIT_WORK(&ts->work, ft5x0x_ts_pen_irq_work);
	this_client = client;
	i2c_set_clientdata(client, ts);

	ts->queue = create_singlethread_workqueue(dev_name(&client->dev));
	if (!ts->queue) {
		err = -ESRCH;
		goto exit_create_singlethread;
	}

	input_dev = input_allocate_device();
	if (!input_dev) {
		err = -ENOMEM;
		dev_err(&client->dev, "failed to allocate input device\n");
		goto exit_input_dev_alloc_failed;
	}

	ts->input_dev = input_dev;

	set_bit(EV_SYN, input_dev->evbit);
	set_bit(EV_ABS, input_dev->evbit);
	set_bit(EV_KEY, input_dev->evbit);

#ifdef CONFIG_FT5X0X_MULTITOUCH
	set_bit(ABS_MT_TRACKING_ID, input_dev->absbit);
	set_bit(ABS_MT_TOUCH_MAJOR, input_dev->absbit);
	set_bit(ABS_MT_WIDTH_MAJOR, input_dev->absbit);
	set_bit(ABS_MT_POSITION_X, input_dev->absbit);
	set_bit(ABS_MT_POSITION_Y, input_dev->absbit);

	input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, ts->screen_max_x, 0, 0);
	input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, ts->screen_max_y, 0, 0);
	input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, ts->pressure_max, 0, 0);
	input_set_abs_params(input_dev, ABS_MT_WIDTH_MAJOR, 0, 200, 0, 0);
	input_set_abs_params(input_dev, ABS_MT_TRACKING_ID, 0, FT5X0X_PT_MAX, 0, 0);
#else
	set_bit(ABS_X, input_dev->absbit);
	set_bit(ABS_Y, input_dev->absbit);
	set_bit(ABS_PRESSURE, input_dev->absbit);
	set_bit(BTN_TOUCH, input_dev->keybit);

	input_set_abs_params(input_dev, ABS_X, 0, ts->screen_max_x, 0, 0);
	input_set_abs_params(input_dev, ABS_Y, 0, ts->screen_max_y, 0, 0);
	input_set_abs_params(input_dev, ABS_PRESSURE, 0, ts->pressure_max, 0 , 0);
#endif

	input_dev->name = FT5X0X_NAME;
	input_dev->phys = "input(mt)";
	input_dev->id.bustype = BUS_I2C;
	input_dev->id.vendor = 0x12FA;
	input_dev->id.product = 0x2143;
	input_dev->id.version = 0x0100;

	err = input_register_device(input_dev);
	if (err) {
		input_free_device(input_dev);
		dev_err(&client->dev, "failed to register input device %s, %d\n",
				dev_name(&client->dev), err);
		goto exit_input_dev_alloc_failed;
	}

	msleep(3);
	err = ft5x0x_read_fw_ver(&val);
	if (err < 0) {
		dev_err(&client->dev, "chip not found\n");
		goto exit_irq_request_failed;
	}

	err = request_irq(client->irq, ft5x0x_ts_interrupt,
			IRQ_TYPE_EDGE_FALLING /*IRQF_TRIGGER_FALLING*/, "ft5x0x_ts", ts);
	if (err < 0) {
		dev_err(&client->dev, "Request IRQ %d failed, %d\n", client->irq, err);
		goto exit_irq_request_failed;
	}

	disable_irq(client->irq);

	dev_info(&client->dev, "Firmware version 0x%02x\n", val);

#ifdef CONFIG_HAS_EARLYSUSPEND
	ts->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
	ts->early_suspend.suspend = ft5x0x_ts_suspend;
	ts->early_suspend.resume = ft5x0x_ts_resume;
	register_early_suspend(&ts->early_suspend);
#endif

	enable_irq(client->irq);

	tiny4412_set_ctp(CTP_FT5X06);
	dev_info(&client->dev, "FocalTech ft5x0x TouchScreen initialized\n");
	return 0;

exit_irq_request_failed:
	input_unregister_device(input_dev);

exit_input_dev_alloc_failed:
	cancel_work_sync(&ts->work);
	destroy_workqueue(ts->queue);

exit_create_singlethread:
	i2c_set_clientdata(client, NULL);

exit_no_pdata:
	kfree(ts);

exit_alloc_data_failed:
exit_check_functionality_failed:
	dev_err(&client->dev, "probe ft5x0x TouchScreen failed, %d\n", err);

	return err;
}

static int __devexit ft5x0x_ts_remove(struct i2c_client *client) //当加载dev.ko和drv.ko成功后,卸载任意一个(dev驱动或drv驱动)驱动,都会调用drv的remove函数<span style="display: none; width: 0px; height: 0px;" id="transmark"></span>
{
	struct ft5x0x_ts_data *ts = i2c_get_clientdata(client);

	printk("ft5x0x drv of bus-dev-drv at24cxx_remove!\n");
	
#ifdef CONFIG_HAS_EARLYSUSPEND
	unregister_early_suspend(&ts->early_suspend);
#endif

	if (client->irq) {
		free_irq(client->irq, ts);
	}

	cancel_work_sync(&ts->work);
	destroy_workqueue(ts->queue);

	i2c_set_clientdata(client, NULL);
	input_unregister_device(ts->input_dev);
	if (ts->input_dev)
		kfree(ts->input_dev);

	kfree(ts);

	return 0;
}

//用到哪些就声明哪些内容,比如driver_data用不到,所以这里就写0
static const struct i2c_device_id ft5x0x_ts_id[] = {
	{ FT5X0X_NAME, 0 },
	{ }
};

MODULE_DEVICE_TABLE(i2c, ft5x0x_ts_id);

/* 1. 分配/设置i2c_driver */
static struct i2c_driver ft5x0x_ts_driver = {
	.probe		= ft5x0x_ts_probe,
	.remove		= __devexit_p(ft5x0x_ts_remove),
	.id_table	= ft5x0x_ts_id,
	.driver	= {
		.name	= FT5X0X_NAME,//在这里,这个名字并不重要,重要的是id_table里面的名字,所以这里可以随便起
		.owner	= THIS_MODULE,
	},
};

static int __init ft5x0x_ts_init(void)
{
	
	int ret;
	
	printk("ft5x0x drv of bus-dev-drv module_init!\n");
	
	/* 2. 注册i2c_driver */
	ret = i2c_add_driver(&ft5x0x_ts_driver);
	if (ret != 0){
		pr_err("Failed to register ft5x0x I2C driver: %d\n", ret);
	}
		
	return ret;
}

static void __exit ft5x0x_ts_exit(void)
{
	printk("ft5x0x drv of bus-dev-drv module_exit!\n");
	i2c_del_driver(&ft5x0x_ts_driver);
}

module_init(ft5x0x_ts_init);
module_exit(ft5x0x_ts_exit);

MODULE_AUTHOR("<wenfs@Focaltech-systems.com>");
MODULE_DESCRIPTION("FocalTech ft5x0x TouchScreen driver");
MODULE_LICENSE("GPL");

4.  编译模块之Makefile


Makefile:


KERN_DIR = /home/samba/linuxKernel_ext4Fs_src/linux-3.5-2015-8

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= i2c_touchscreen_dev.o
obj-m	+= i2c_touchscreen_drv.o

5.  测试

    测试时加载模块后才运行QT程序。    



  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值