Linux触摸屏驱动分析(6410) -- s3c-ts

static struct s3c_ts_mach_info s3c_ts_platform __initdata = {
	.delay 			= 10000,		/*转化延迟*/
	.presc 			= 49,			/*转化时钟分频*/
	.oversampling_shift	= 2,		/*转化次数 1<<2 == 4次*/
	.resol_bit 		= 12,			/*转化进度*/
	.s3c_adc_con		= ADC_TYPE_2,
};

/*s3c_ts所列的资源*/
static struct resource s3c_ts_resource[] = {
	[0] = {
		.start = SAMSUNG_PA_ADC,
		.end   = SAMSUNG_PA_ADC + SZ_256 - 1,
		.flags = IORESOURCE_MEM,
	},
	[1] = {
		.start = IRQ_PENDN,
		.end   = IRQ_PENDN,
		.flags = IORESOURCE_IRQ,
	},
	[2] = {
		.start = IRQ_ADC,
		.end   = IRQ_ADC,
		.flags = IORESOURCE_IRQ,
	}
};

/*platform设备定义*/
struct platform_device s3c_device_ts = {
	.name		  = "s3c-ts",
	.id		  = -1,
	.num_resources	  = ARRAY_SIZE(s3c_ts_resource),
	.resource	  = s3c_ts_resource,
};

void __init s3c_ts_set_platdata(struct s3c_ts_mach_info *pd)
{
	struct s3c_ts_mach_info *npd;

	if (!pd) {
		printk(KERN_ERR "%s: no platform data\n", __func__);
		return;
	}

	npd = kmemdup(pd, sizeof(struct s3c_ts_mach_info), GFP_KERNEL);
	if (!npd)
		printk(KERN_ERR "%s: no memory for platform data\n", __func__);

	s3c_device_ts.dev.platform_data = npd;
}

/* linux/drivers/input/touchscreen/s3c-ts.c
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * Copyright (c) 2004 Arnaud Patard <arnaud.patard@rtp-net.org>
 * iPAQ H1940 touchscreen support
 *
 * ChangeLog
 *
 * 2004-09-05: Herbert Potzl <herbert@13thfloor.at>
 *	- added clock (de-)allocation code
 *
 * 2005-03-06: Arnaud Patard <arnaud.patard@rtp-net.org>
 *      - h1940_ -> s3c24xx (this driver is now also used on the n30
 *        machines :P)
 *      - Debug messages are now enabled with the config option
 *        TOUCHSCREEN_S3C_DEBUG
 *      - Changed the way the value are read
 *      - Input subsystem should now work
 *      - Use ioremap and readl/writel
 *
 * 2005-03-23: Arnaud Patard <arnaud.patard@rtp-net.org>
 *      - Make use of some undocumented features of the touchscreen
 *        controller
 *
 * 2006-09-05: Ryu Euiyoul <ryu.real@gmail.com>
 *      - added power management suspend and resume code
 *
 */

#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/init.h>
#include <linux/serio.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/clk.h>

#include <asm/io.h>
#include <asm/irq.h>
#include <mach/hardware.h>

#include <plat/regs-adc.h>
#include <mach/ts.h>
#include <mach/irqs.h>

#define CONFIG_TOUCHSCREEN_S3C_DEBUG
#undef CONFIG_TOUCHSCREEN_S3C_DEBUG

/* For ts->dev.id.version */
#define S3C_TSVERSION	0x0101

/*x等于0, 为等待按下中断, x等于1, 为等待松开中断**/
#define WAIT4INT(x)  (((x)<<8) | \
		     S3C_ADCTSC_YM_SEN | S3C_ADCTSC_YP_SEN | S3C_ADCTSC_XP_SEN | \
		     S3C_ADCTSC_XY_PST(3))

/*自动测量x,y坐标*/
#define AUTOPST	     (S3C_ADCTSC_YM_SEN | S3C_ADCTSC_YP_SEN | S3C_ADCTSC_XP_SEN | \
		     S3C_ADCTSC_AUTO_PST | S3C_ADCTSC_XY_PST(0))


#define DEBUG_LVL    KERN_DEBUG


/* Touchscreen default configuration */
struct s3c_ts_mach_info s3c_ts_default_cfg __initdata = {
                .delay = 		5000,//10000,
                .presc = 		49,
                .oversampling_shift = 	4,//2,
		.resol_bit = 		10
};

/*
 * Definitions & global arrays.
 */
static char *s3c_ts_name = "S3C TouchScreen";
static void __iomem 		*ts_base;
static struct resource		*ts_mem;
static struct resource		*ts_irq;
static struct clk		*ts_clock;
static struct s3c_ts_info 	*ts;

static int downflag=0;


#ifdef  CONFIG_FORLINX6410_ADC

DEFINE_SEMAPHORE(ADC_LOCK);
/* Indicate who is using the ADC controller */
#define LOCK_FREE		0
#define LOCK_TS			1
#define LOCK_ADC		2
static int adc_lock_id = LOCK_FREE;

#define	ADC_free()		(adc_lock_id == LOCK_FREE)
#define ADC_locked4TS()	(adc_lock_id == LOCK_TS)

static inline int s3c_ts_adc_lock(int id) {
        int ret;

        ret = down_trylock(&ADC_LOCK);
        if (!ret) {
                adc_lock_id = id;
        }

        return ret;
}

static inline void s3c_ts_adc_unlock(void) {
        adc_lock_id = 0;
        up(&ADC_LOCK);
}

static unsigned int _adccon, _adctsc, _adcdly;

int X6410_adc_acquire_io(void) {
        int ret;

        ret = s3c_ts_adc_lock(LOCK_ADC);
        if (!ret) {
                _adccon = readl(ts_base + S3C_ADCCON);
                _adctsc = readl(ts_base + S3C_ADCTSC);
                _adcdly = readl(ts_base + S3C_ADCDLY);

                printk("forlinx debug****X6410_adc_acquire_io();.\n");


        }



        return ret;
}
EXPORT_SYMBOL(X6410_adc_acquire_io);

void X6410_adc_release_io(void) {
        writel(_adccon, ts_base + S3C_ADCCON);
        writel(_adctsc, ts_base + S3C_ADCTSC);
        writel(_adcdly, ts_base + S3C_ADCDLY);
        writel(WAIT4INT(0), ts_base + S3C_ADCTSC);

        s3c_ts_adc_unlock();

        printk("forlinx debug*****X6410_adc_release_io();.\n");


}

EXPORT_SYMBOL(X6410_adc_release_io);

#endif

/* 当定时器到了后,就会调用touch_timer_fire函数
 *
 *  
 * */

static void touch_timer_fire(unsigned long data)
{
	unsigned long data0;
	unsigned long data1;
        int pendown;

#ifdef CONFIG_FORLINX6410_ADC
        if (!ADC_locked4TS()) {
                /* Note: pen UP interrupt detected and handled, the lock is released,
                 * so do nothing in the timer which started by ADC ISR. */
                return;
        }
#endif

		/*读取ADCDATA0 和 ADCDAT1寄存器*/
	data0 = readl(ts_base+S3C_ADCDAT0);
	data1 = readl(ts_base+S3C_ADCDAT1);

		/*判断是否按下*/
        pendown = (!(data0 & S3C_ADCDAT0_UPDOWN)) && (!(data1 & S3C_ADCDAT1_UPDOWN));

        if (pendown) {
                //down
                //printk("pendown=1.\n");
		if (ts->count) {

#ifdef CONFIG_TOUCHSCREEN_S3C_DEBUG
			{
				struct timeval tv;
				do_gettimeofday(&tv);
				printk(KERN_INFO "T: %06d, X: %03ld, Y: %03ld\n", (int)tv.tv_usec, ts->xp, ts->yp);
			}
#endif

			/*上报事件*/
		    if(downflag==0)
                     {
			input_report_abs(ts->dev, ABS_X, ts->xp);
			input_report_abs(ts->dev, ABS_Y, ts->yp);

			input_report_key(ts->dev, BTN_TOUCH, 1);
			input_report_abs(ts->dev, ABS_PRESSURE, 1);
			input_sync(ts->dev);
                     }
                    else
                    {
                       // printk("downflag=1.ignore this data.\n");
                        downflag=0;
                    }
                }

				/*如果count=0, 也就是刚进来的时候,开启ADC中断。 接着就会调用ADC中断的处理函数*/

                ts->xp = 0;
                ts->yp = 0;
                ts->count = 0;

                writel(S3C_ADCTSC_PULL_UP_DISABLE | AUTOPST, ts_base+S3C_ADCTSC);
                writel(readl(ts_base+S3C_ADCCON) | S3C_ADCCON_ENABLE_START, ts_base+S3C_ADCCON);
        }
        else {

                //up

                ts->count = 0;

                input_report_key(ts->dev, BTN_TOUCH, 0);
                input_report_abs(ts->dev, ABS_PRESSURE, 0);
                input_sync(ts->dev);

		writel(WAIT4INT(0), ts_base+S3C_ADCTSC);


#ifdef CONFIG_FORLINX6410_ADC
                if (ADC_locked4TS()) {
                        s3c_ts_adc_unlock();
                        printk("forlinx debug*****s3c_ts_adc_unlock();.\n");
                }
#endif
	}




}

static struct timer_list touch_timer =
		TIMER_INITIALIZER(touch_timer_fire, 0, 0);

/* 当触摸屏按下后,会触发tc中断
 *  
 * tc中断触发后,会调用touch_timer_fire函数
 * */
static irqreturn_t stylus_updown(int irqno, void *param)
{
	unsigned long data0;
	unsigned long data1;

#ifdef CONFIG_FORLINX6410_ADC
		/*检测是否上锁ADC_LOCK*/
        if (!ADC_locked4TS()) {
                if (s3c_ts_adc_lock(LOCK_TS)) {
                        /* Locking ADC controller failed */
                        printk("Lock ADC failed, %d\n", adc_lock_id);
                        return IRQ_HANDLED;
                }

                printk("forlinx debug***** s3c_ts_adc_lock(LOCK_TS);.\n");
        }
#endif

	data0 = readl(ts_base+S3C_ADCDAT0);
	data1 = readl(ts_base+S3C_ADCDAT1);

	/* TODO we should never get an interrupt with updown set while
	 * the timer is running, but maybe we ought to verify that the
	 * timer isn't running anyways. */


        touch_timer_fire(0);


	if(ts->s3c_adc_con==ADC_TYPE_2) {
       		__raw_writel(0x0, ts_base+S3C_ADCCLRWK);
        	__raw_writel(0x0, ts_base+S3C_ADCCLRINT);
	}
        
	return IRQ_HANDLED;
}

/* 当ADC中断触发时,就会调用ADC中断处理函数
 *
 * */

static irqreturn_t stylus_action(int irqno, void *param)
{
	unsigned long data0;
	unsigned long data1;


#ifdef CONFIG_FORLINX6410_ADC
        if (!ADC_locked4TS()) {
                if (ADC_free()) {
                        printk("Unexpected\n");

                        /* Clear ADC interrupt */
                        __raw_writel(0x0, ts_base + S3C_ADCCLRINT);
                }

                return IRQ_HANDLED;
        }
#endif

	//printk("stylus_action.\n");

	data0 = readl(ts_base+S3C_ADCDAT0);
	data1 = readl(ts_base+S3C_ADCDAT1);

	/*可以看见每次进来都是ts->xp++  ts->yp++ */

	if(ts->resol_bit==12) {
#if defined(CONFIG_TOUCHSCREEN_NEW)
		ts->yp += S3C_ADCDAT0_XPDATA_MASK_12BIT - (data0 & S3C_ADCDAT0_XPDATA_MASK_12BIT);
		ts->xp += S3C_ADCDAT1_YPDATA_MASK_12BIT - (data1 & S3C_ADCDAT1_YPDATA_MASK_12BIT);
#else 
		ts->xp += data0 & S3C_ADCDAT0_XPDATA_MASK_12BIT;
		ts->yp += data1 & S3C_ADCDAT1_YPDATA_MASK_12BIT;
#endif
	}
	else {
#if defined(CONFIG_TOUCHSCREEN_NEW)
		ts->yp += S3C_ADCDAT0_XPDATA_MASK - (data0 & S3C_ADCDAT0_XPDATA_MASK);
		ts->xp += S3C_ADCDAT1_YPDATA_MASK - (data1 & S3C_ADCDAT1_YPDATA_MASK);
#else
		ts->xp += data0 & S3C_ADCDAT0_XPDATA_MASK;
		ts->yp += data1 & S3C_ADCDAT1_YPDATA_MASK;
#endif	
	}

	/*每次中断发生,count++*/
	ts->count++;

	/* 判断count小于4。 再次触发ADC中断
	 * 当count等于4时,就启动定时器。然后跳到定时器处理函数中去
	 * */

	if (ts->count < (1<<ts->shift)) {
		writel(S3C_ADCTSC_PULL_UP_DISABLE | AUTOPST, ts_base+S3C_ADCTSC);
		writel(readl(ts_base+S3C_ADCCON) | S3C_ADCCON_ENABLE_START, ts_base+S3C_ADCCON);
	} else {
		mod_timer(&touch_timer, jiffies+1);
		writel(WAIT4INT(1), ts_base+S3C_ADCTSC);
	}

	if(ts->s3c_adc_con==ADC_TYPE_2) {
       		__raw_writel(0x0, ts_base+S3C_ADCCLRWK);
        	__raw_writel(0x0, ts_base+S3C_ADCCLRINT);
	}
	
	return IRQ_HANDLED;
}


static struct s3c_ts_mach_info *s3c_ts_get_platdata (struct device *dev)
{
	if (dev->platform_data != NULL)
		return (struct s3c_ts_mach_info *)dev->platform_data;

	return &s3c_ts_default_cfg;
}

/* 
 * s3c_ts_probe函数的主要作用是:初始化硬件相关的资源。
 * 比如: 内存ioremap, 中断资源获取, 配置ADCCCON等寄存器
 *
 * 当中断注册好后,当我们按下触摸屏后,就会触发tc中断
 */
static int __init s3c_ts_probe(struct platform_device *pdev)
{
	struct resource *res;
	struct device *dev;
	struct input_dev *input_dev;
	struct s3c_ts_mach_info * s3c_ts_cfg;
	int ret, size;

	dev = &pdev->dev;

	/* 得到s3c_ts_resource*/
	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	if (res == NULL) {
		dev_err(dev,"no memory resource specified\n");
		return -ENOENT;
	}

	size = (res->end - res->start) + 1;
	ts_mem = request_mem_region(res->start, size, pdev->name);
	if (ts_mem == NULL) {
		dev_err(dev, "failed to get memory region\n");
		ret = -ENOENT;
		goto err_req;
	}

	ts_base = ioremap(res->start, size);
	if (ts_base == NULL) {
		dev_err(dev, "failed to ioremap() region\n");
		ret = -EINVAL;
		goto err_map;
	}
	
	ts_clock = clk_get(&pdev->dev, "adc");
	if (IS_ERR(ts_clock)) {
		dev_err(dev, "failed to find watchdog clock source\n");
		ret = PTR_ERR(ts_clock);
		goto err_clk;
	}

	clk_enable(ts_clock);

	/*得到s3c_ts_cfg配置文件*/
	s3c_ts_cfg = s3c_ts_get_platdata(&pdev->dev);
		
	/*设置ADC分频分频系数*/
	if ((s3c_ts_cfg->presc&0xff) > 0)
		writel(S3C_ADCCON_PRSCEN | S3C_ADCCON_PRSCVL(s3c_ts_cfg->presc&0xff),
				ts_base+S3C_ADCCON);
	else
		writel(0, ts_base+S3C_ADCCON);


	/* Initialise registers */
	/*设置ADC转化延迟*/
	if ((s3c_ts_cfg->delay&0xffff) > 0)
		writel(s3c_ts_cfg->delay & 0xffff, ts_base+S3C_ADCDLY);

	/*设置AD:2-bit A/D conversion*/
	if (s3c_ts_cfg->resol_bit==12) {
		switch(s3c_ts_cfg->s3c_adc_con) {
		case ADC_TYPE_2:
			writel(readl(ts_base+S3C_ADCCON)|S3C_ADCCON_RESSEL_12BIT, 
			ts_base+S3C_ADCCON);
			break;

		case ADC_TYPE_1:
			writel(readl(ts_base+S3C_ADCCON)|S3C_ADCCON_RESSEL_12BIT_1, 
			ts_base+S3C_ADCCON);
			break;
			
		default:
			dev_err(dev, "Touchscreen over this type of AP isn't supported !\n");
			break;
		}
	}

	/*等待按下中断*/
	writel(WAIT4INT(0), ts_base+S3C_ADCTSC);

	ts = kzalloc(sizeof(struct s3c_ts_info), GFP_KERNEL);
	
	input_dev = input_allocate_device();

	if (!input_dev) {
		ret = -ENOMEM;
		goto err_alloc;
	}
	
	ts->dev = input_dev;

	/*设置输入设备的事件类型: 按键事件, 同步事件, 绝对位移事件*/
	ts->dev->evbit[0] = ts->dev->evbit[0] = BIT_MASK(EV_SYN) | BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
	ts->dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);

	/*设置触摸屏的x,y坐标以及压力*/
	if (s3c_ts_cfg->resol_bit==12) {
		input_set_abs_params(ts->dev, ABS_X, 0, 0xFFF, 0, 0);
		input_set_abs_params(ts->dev, ABS_Y, 0, 0xFFF, 0, 0);
	}
	else {
		input_set_abs_params(ts->dev, ABS_X, 0, 0x3FF, 0, 0);
		input_set_abs_params(ts->dev, ABS_Y, 0, 0x3FF, 0, 0);
	}

	input_set_abs_params(ts->dev, ABS_PRESSURE, 0, 1, 0, 0);

	sprintf(ts->phys, "input(ts)");

	/*输入设备的初始化
	 * 比如: name, 总线类型, 版本号等
	 * */
	ts->dev->name = s3c_ts_name;
	ts->dev->phys = ts->phys;
	ts->dev->id.bustype = BUS_RS232;
	ts->dev->id.vendor = 0xDEAD;
	ts->dev->id.product = 0xBEEF;
	ts->dev->id.version = S3C_TSVERSION;

	ts->shift = s3c_ts_cfg->oversampling_shift;
	ts->resol_bit = s3c_ts_cfg->resol_bit;
	ts->s3c_adc_con = s3c_ts_cfg->s3c_adc_con;
	
	/* For IRQ_PENDUP */
	/*请求中断资源0, 注册tc中断*/
	ts_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
	if (ts_irq == NULL) {
		dev_err(dev, "no irq resource specified\n");
		ret = -ENOENT;
		goto err_irq;
	}

	ret = request_irq(ts_irq->start, stylus_updown, IRQF_SAMPLE_RANDOM, "s3c_updown", ts);
	if (ret != 0) {
		dev_err(dev,"s3c_ts.c: Could not allocate ts IRQ_PENDN !\n");
		ret = -EIO;
		goto err_irq;
	}

	/* For IRQ_ADC */
	/*请求中断资源1, 注册ADC中断*/
	ts_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 1);
	if (ts_irq == NULL) {
		dev_err(dev, "no irq resource specified\n");
		ret = -ENOENT;
		goto err_irq;
	}

        ret = request_irq(ts_irq->start, stylus_action, IRQF_SAMPLE_RANDOM | IRQF_SHARED,
		 "s3c_action", ts);
	if (ret != 0) {
		dev_err(dev, "s3c_ts.c: Could not allocate ts IRQ_ADC !\n");
		ret =  -EIO;
		goto err_irq;
	}

	printk(KERN_INFO "%s got loaded successfully : %d bits\n", s3c_ts_name, s3c_ts_cfg->resol_bit);

	/* All went ok, so register to the input system */
	/*注册输入型设备*/
	ret = input_register_device(ts->dev);
	
	if(ret) {
		dev_err(dev, "s3c_ts.c: Could not register input device(touchscreen)!\n");
		ret = -EIO;
		goto fail;
	}

	return 0;

fail:
	free_irq(ts_irq->start, ts->dev);
	free_irq(ts_irq->end, ts->dev);
	
err_irq:
	input_free_device(input_dev);
	kfree(ts);

err_alloc:
	clk_disable(ts_clock);
	clk_put(ts_clock);
	
err_clk:
	iounmap(ts_base);

err_map:
	release_resource(ts_mem);
	kfree(ts_mem);

err_req:
	return ret;
}

static int s3c_ts_remove(struct platform_device *dev)
{
	printk(KERN_INFO "s3c_ts_remove() of TS called !\n");

	disable_irq(IRQ_ADC);
	disable_irq(IRQ_PENDN);
	
	free_irq(IRQ_PENDN, ts->dev);
	free_irq(IRQ_ADC, ts->dev);

	if (ts_clock) {
		clk_disable(ts_clock);
		clk_put(ts_clock);
		ts_clock = NULL;
	}

	input_unregister_device(ts->dev);
	iounmap(ts_base);

	return 0;
}

#ifdef CONFIG_PM
static unsigned int adccon, adctsc, adcdly;

static int s3c_ts_suspend(struct platform_device *dev, pm_message_t state)
{
	adccon = readl(ts_base+S3C_ADCCON);
	adctsc = readl(ts_base+S3C_ADCTSC);
	adcdly = readl(ts_base+S3C_ADCDLY);

	disable_irq(IRQ_ADC);
	disable_irq(IRQ_PENDN);
	
	clk_disable(ts_clock);

	return 0;
}

static int s3c_ts_resume(struct platform_device *pdev)
{
	clk_enable(ts_clock);

	writel(adccon, ts_base+S3C_ADCCON);
	writel(adctsc, ts_base+S3C_ADCTSC);
	writel(adcdly, ts_base+S3C_ADCDLY);
	writel(WAIT4INT(0), ts_base+S3C_ADCTSC);

	enable_irq(IRQ_ADC);
	enable_irq(IRQ_PENDN);
	return 0;
}
#else
#define s3c_ts_suspend NULL
#define s3c_ts_resume  NULL
#endif

static struct platform_driver s3c_ts_driver = {
       .probe          = s3c_ts_probe,
       .remove         = s3c_ts_remove,
       .suspend        = s3c_ts_suspend,
       .resume         = s3c_ts_resume,
       .driver		= {
		.owner	= THIS_MODULE,
		.name	= "s3c-ts",
	},
};

static char banner[] __initdata = KERN_INFO "S3C Touchscreen driver, (c) 2008 Samsung Electronics\n";
/* 首先: 我们注册是platform驱动,当然有platform驱动就会有platform设备了。
 * 当platform驱动注册到系统中时,就会和platform设备去匹配。
 *
 * 那问题来了? platform是如何匹配设备与驱动的?
 * platform_match()函数中strcmp(pdev->name, drv->name)这句就会告诉你, 是通过设备的name与驱动的name匹配的。
 *
 * 而我们的s3c_ts_driver的name = "s3c-ts",那么我们的设备的名字应该也会是"s3c-ts"
 *
 * 我们搜索系统发现:Dev-ts.c文件中存在s3c_device_ts的name为"s3c-ts"。很明显这就是platform驱动对应的platform的设备了。
 *
 * 当设备与驱动的名称匹配了,就会调用s3c_ts_probe函数。
 * 
 * */
static int __init s3c_ts_init(void)
{
	printk(banner);
	return platform_driver_register(&s3c_ts_driver);
}

static void __exit s3c_ts_exit(void)
{
	platform_driver_unregister(&s3c_ts_driver);
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值