Android 学习-Driver in kernel-2-添加GPIO控制

在上一篇的基础上添加了Device Tree 的节点,用于控制一个GPIO,实现5V的输出控制。 

上篇地址:http://blog.csdn.net/daichchch/article/details/50725497

平台是mt8163 , Android M0 ,kernel-3.18

1,增加Device Tree 的节点

kernel-3.18/arch/arm64/boot/dts/MT8163.dtsi中,按顺序在SOC{....}内添加节点:

hello: hello@1401d000 {
    compatible = "mediatek,mt8163-hello";
};

kernel-3.18/arch/arm64/boot/dts/Tb8163p1_64.dts中,按顺序添加gpio pin:

&hello {
    hello_power_control = <&pio 27 0>;
};

2,增加kernel 内的platform 注册和ioctl

目录kernel-3.18\drivers\misc\mediatek\hello\hello.c修改成如下:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <asm/uaccess.h>

#include <linux/of_platform.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>

#include "hello.h"

#define HELLO_DEVNAME "hello"

int hello_power_control_pin;


/*主设备和从设备号变量*/
static int hello_major = 0;
static int hello_minor = 0;

/*设备类别和设备变量*/
static struct class* hello_class = NULL;
static struct hello_android_dev* hello_dev = NULL;

/*传统的设备文件操作方法*/
static int hello_open(struct inode* inode, struct file* filp);
static int hello_release(struct inode* inode, struct file* filp);
static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);
static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);
static long hello_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
static long hello_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg);
static int hello_probe(struct platform_device *pdev);
static int hello_remove(struct platform_device *pdev);
static int  __hello_setup_dev(struct hello_android_dev* dev);


/*设备文件操作方法表*/
static struct file_operations hello_fops = {
	.owner = THIS_MODULE,
	.open = hello_open,
	.release = hello_release,
	.read = hello_read,
	.write = hello_write,
	.unlocked_ioctl = hello_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = hello_ioctl_compat,
#endif
};


#ifdef CONFIG_PM
int hello_pm_suspend(struct device *device)
{
	pr_debug("hello_pm_suspend()\n");

	return 0;
}

int hello_pm_resume(struct device *device)
{
	pr_debug("hello_Npm_resume()\n");

	return 0;
}

const struct dev_pm_ops hello_pm_ops = {
	.suspend = hello_pm_suspend,
	.resume = hello_pm_resume,
};
#endif

static const struct of_device_id hello_of_ids[] = {
	{.compatible = "mediatek,mt8163-hello",},
	{},
};


/*platform file ops*/
static struct platform_driver hello_driver = {
	.probe = hello_probe,
	.remove = hello_remove,
	.driver = {
		   .name = HELLO_DEVNAME,
		   .owner = THIS_MODULE,
#ifdef CONFIG_PM
		   .pm = &hello_pm_ops,
#endif
		   .of_match_table = hello_of_ids,
		   }
};


/*访问设置属性方法*/
static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr,  char* buf);
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count);

/*定义设备属性*/
static DEVICE_ATTR(val, S_IRUGO | S_IWUSR, hello_val_show, hello_val_store);



/*打开设备方法*/
static int hello_open(struct inode* inode, struct file* filp) {
	struct hello_android_dev* dev;        
	
	/*将自定义设备结构体保存在文件指针的私有数据域中,以便访问设备时拿来用*/
	dev = container_of(inode->i_cdev, struct hello_android_dev, dev);
	filp->private_data = dev;
	
	return 0;
}

/*设备文件释放时调用,空实现*/
static int hello_release(struct inode* inode, struct file* filp) {
	return 0;
}

/*读取设备的寄存器val的值*/
static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos) {
	ssize_t err = 0;
	struct hello_android_dev* dev = filp->private_data;        

	/*同步访问*/
	if(down_interruptible(&(dev->sem))) {
		return -ERESTARTSYS;
	}

	if(count < sizeof(dev->val)) {
		goto out;
	}        

	/*将寄存器val的值拷贝到用户提供的缓冲区*/
	if(copy_to_user(buf, &(dev->val), sizeof(dev->val))) {
		err = -EFAULT;
		goto out;
	}

	err = sizeof(dev->val);

out:
	up(&(dev->sem));
	return err;
}

/*写设备的寄存器值val*/
static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos) {
	struct hello_android_dev* dev = filp->private_data;
	ssize_t err = 0;        

	/*同步访问*/
	if(down_interruptible(&(dev->sem))) {
		return -ERESTARTSYS;        
	}        

	if(count != sizeof(dev->val)) {
		goto out;        
	}        

	/*将用户提供的缓冲区的值写到设备寄存器去*/
	if(copy_from_user(&(dev->val), buf, count)) {
		err = -EFAULT;
		goto out;
	}

	err = sizeof(dev->val);

out:
	up(&(dev->sem));
	return err;
}

static long hello_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	void __user *argp = (void __user *)arg;
	HELLO_ARG hello_arg;
	static int val = 0;
	int ret = 0;

	pr_err("[hello]ioctl=%d, arg = %lu\n", cmd, arg);

	switch (cmd) {
	case HELLO_READ_WRITE: {
			if (copy_from_user(&hello_arg, argp, sizeof(HELLO_ARG))) {
				pr_err("[hello]ioctl copy_from_user failed! line:%d\n", __LINE__);
				ret = -EFAULT;
			} else {
				hello_arg.times = hello_arg.times + 1;
				if(hello_arg.write_data != 0) {
					val += hello_arg.write_data;
				}
				hello_arg.read_data = val;

				if (copy_to_user(argp, &hello_arg, sizeof(HELLO_ARG))) {
						pr_err("[hello]ioctl copy_to_user failed! line:%d\n", __LINE__);
						ret = -EFAULT;
				}
			}
			pr_err("[hello]ioctl hello_arg.read_data=%d,hello_arg.write_data=%d,hello_arg.times=%d\n", hello_arg.read_data, hello_arg.write_data, hello_arg.times);
			break;
		}
	case HELLO_POWER: {
			if (copy_from_user(&hello_arg, argp, sizeof(HELLO_ARG))) {
				pr_err("[hello]ioctl copy_from_user failed! line:%d\n", __LINE__);
				ret = -EFAULT;
			} else {
				hello_arg.write_data = 0xff;
				if(hello_arg.read_data == 1) {
					if (hello_power_control_pin > 0) {
						pr_err("[hello]hello 1 control pin number is %d\n", hello_power_control_pin);
						gpio_direction_output(hello_power_control_pin, 1);
						gpio_set_value(hello_power_control_pin, 1);
						hello_arg.write_data = hello_arg.read_data;
					} else {
						pr_err("[hello]ioctl 1 hello_power_control_pin <= 0 ERROR\n");
						hello_arg.write_data = 0xf0;
					}
				} else if(hello_arg.read_data == 0) {
					if (hello_power_control_pin > 0) {
						pr_err("[hello]hello 0 control pin number is %d\n", hello_power_control_pin);
						gpio_direction_output(hello_power_control_pin, 1);
						gpio_set_value(hello_power_control_pin, 0);
						hello_arg.write_data = hello_arg.read_data;
					} else {
						pr_err("[hello]ioctl 0 hello_power_control_pin <= 0 ERROR\n");
						hello_arg.write_data = 0xf1;
					}
				}

				if (copy_to_user(argp, &hello_arg, sizeof(HELLO_ARG))) {
						pr_err("[hello]ioctl copy_to_user failed! line:%d\n", __LINE__);
						ret = -EFAULT;
				}
			}
			pr_err("[hello]ioctl hello_arg.read_data=%d,hello_arg.write_data=%d,hello_arg.times=%d\n", hello_arg.read_data, hello_arg.write_data, hello_arg.times);
			break;
		}
	default: {
			pr_err("[hello]ioctl(%d) arguments is not support\n", cmd);
			ret = -EFAULT;
			break;
		}
	}
	return ret;
}

static long hello_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) {
	return 0;
}


/*读取寄存器val的值到缓冲区buf中,内部使用*/
static ssize_t __hello_get_val(struct hello_android_dev* dev, char* buf) {
	int val = 0;        

	/*同步访问*/
	if(down_interruptible(&(dev->sem))) {                
		return -ERESTARTSYS;        
	}        

	val = dev->val;        
	up(&(dev->sem));        

	return snprintf(buf, PAGE_SIZE, "%d\n", val);
}

/*把缓冲区buf的值写到设备寄存器val中去,内部使用*/
static ssize_t __hello_set_val(struct hello_android_dev* dev, const char* buf, size_t count) {
	int val = 0;        

	/*将字符串转换成数字*/        
	val = simple_strtol(buf, NULL, 10);        

	/*同步访问*/        
	if(down_interruptible(&(dev->sem))) {                
		return -ERESTARTSYS;        
	}        

	dev->val = val;        
	up(&(dev->sem));

	return count;
}

/*读取设备属性val*/
static ssize_t hello_val_show(struct device* dev, struct device_attribute* attr, char* buf) {
	struct hello_android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);        

	return __hello_get_val(hdev, buf);
}

/*写设备属性val*/
static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count) { 
	struct hello_android_dev* hdev = (struct hello_android_dev*)dev_get_drvdata(dev);  
	
	return __hello_set_val(hdev, buf, count);
}



#if 0
/*读取设备寄存器val的值,保存在page缓冲区中*/
static ssize_t hello_proc_read(char* page, char** start, off_t off, int count, int* eof, void* data) {
	if(off > 0) {
		*eof = 1;
		return 0;
	}

	return __hello_get_val(hello_dev, page);
}

/*把缓冲区的值buff保存到设备寄存器val中去*/
static ssize_t hello_proc_write(struct file* filp, const char __user *buff, unsigned long len, void* data) {
	int err = 0;
	char* page = NULL;

	if(len > PAGE_SIZE) {
		printk(KERN_ALERT"The buff is too large: %lu.\n", len);
		return -EFAULT;
	}

	page = (char*)__get_free_page(GFP_KERNEL);
	if(!page) {                
		printk(KERN_ALERT"Failed to alloc page.\n");
		return -ENOMEM;
	}        

	/*先把用户提供的缓冲区值拷贝到内核缓冲区中去*/
	if(copy_from_user(page, buff, len)) {
		printk(KERN_ALERT"Failed to copy buff from user.\n");                
		err = -EFAULT;
		goto out;
	}

	err = __hello_set_val(hello_dev, page, len);

out:
	free_page((unsigned long)page);
	return err;
}

/*创建/proc/hello文件*/
static void hello_create_proc(void) {
	struct proc_dir_entry* entry;
	
	entry = create_proc_entry(HELLO_DEVICE_PROC_NAME, 0, NULL);
	if(entry) {
		entry->owner = THIS_MODULE;
		entry->read_proc = hello_proc_read;
		entry->write_proc = hello_proc_write;
	}
}

/*删除/proc/hello文件*/
static void hello_remove_proc(void) {
	remove_proc_entry(HELLO_DEVICE_PROC_NAME, NULL);
}
#endif

static int hello_device_tree_probe(struct platform_device *pdev)
{
	int ret = 0;

	pr_err("[hello_device_tree_probe] probe start\n");

	if (pdev->dev.of_node == NULL) {
		pr_err("[hdmi_internal_probe] Device Node Error\n");
		return -1;
	}

	/* Get Power Control Pin */
	hello_power_control_pin = of_get_named_gpio(pdev->dev.of_node, "hello_power_control", 0);
	ret = gpio_request(hello_power_control_pin, "hello power control pin");
	if (ret)
		pr_err("hello power control pin, failure of setting\n");
	return 0;
}


static int hello_probe(struct platform_device *pdev)
{
	int err = -1;
	dev_t dev = 0;
	struct device* temp = NULL;

	printk(KERN_ALERT"Initializing hello device.\n");        

	/*动态分配主设备和从设备号*/
	err = alloc_chrdev_region(&dev, 0, 1, HELLO_DEVICE_NODE_NAME);
	if(err < 0) {
		printk(KERN_ALERT"Failed to alloc char dev region.\n");
		goto fail;
	}

	hello_major = MAJOR(dev);
	hello_minor = MINOR(dev);        

	/*分配helo设备结构体变量*/
	hello_dev = kmalloc(sizeof(struct hello_android_dev), GFP_KERNEL);
	if(!hello_dev) {
		err = -ENOMEM;
		printk(KERN_ALERT"Failed to alloc hello_dev.\n");
		goto unregister;
	}        

	/*初始化设备*/
	err = __hello_setup_dev(hello_dev);
	if(err) {
		printk(KERN_ALERT"Failed to setup dev: %d.\n", err);
		goto cleanup;
	}        

	/*在/sys/class/目录下创建设备类别目录hello*/
	hello_class = class_create(THIS_MODULE, HELLO_DEVICE_CLASS_NAME);
	if(IS_ERR(hello_class)) {
		err = PTR_ERR(hello_class);
		printk(KERN_ALERT"Failed to create hello class.\n");
		goto destroy_cdev;
	}        

	/*在/dev/目录和/sys/class/hello目录下分别创建设备文件hello*/
	temp = device_create(hello_class, NULL, dev, "%s", HELLO_DEVICE_FILE_NAME);
	if(IS_ERR(temp)) {
		err = PTR_ERR(temp);
		printk(KERN_ALERT"Failed to create hello device.");
		goto destroy_class;
	}        

	/*在/sys/class/hello/hello目录下创建属性文件val*/
	err = device_create_file(temp, &dev_attr_val);
	if(err < 0) {
		printk(KERN_ALERT"Failed to create attribute val.");                
		goto destroy_device;
	}

	dev_set_drvdata(temp, hello_dev);        

	/*创建/proc/hello文件*/
//	hello_create_proc();

	hello_device_tree_probe(pdev);

	printk(KERN_ALERT"Succedded to initialize hello device.\n");
	return 0;

destroy_device:
	device_destroy(hello_class, dev);

destroy_class:
	class_destroy(hello_class);

destroy_cdev:
	cdev_del(&(hello_dev->dev));

cleanup:
	kfree(hello_dev);

unregister:
	unregister_chrdev_region(MKDEV(hello_major, hello_minor), 1);

fail:
	return err;
}

static int hello_remove(struct platform_device *pdev)
{
	return 0;
}


/*初始化设备*/
static int  __hello_setup_dev(struct hello_android_dev* dev) {
	int err;
	dev_t devno = MKDEV(hello_major, hello_minor);

	memset(dev, 0, sizeof(struct hello_android_dev));

	cdev_init(&(dev->dev), &hello_fops);
	dev->dev.owner = THIS_MODULE;
	dev->dev.ops = &hello_fops;        

	/*注册字符设备*/
	err = cdev_add(&(dev->dev),devno, 1);
	if(err) {
		return err;
	}        

	/*初始化信号量和寄存器val的值*/
	sema_init(&(dev->sem), 1);
	dev->val = 0;

	return 0;
}

/*模块加载方法*/
static int __init hello_init(void){ 
	if (platform_driver_register(&hello_driver)) {
			pr_err("[hello_init]failed to register hello driver\n");
			return -1;
		}
	return 0;
}

/*模块卸载方法*/
static void __exit hello_exit(void) {
	dev_t devno = MKDEV(hello_major, hello_minor);

	printk(KERN_ALERT"Destroy hello device.\n");        

	/*删除/proc/hello文件*/
//	hello_remove_proc();        

	/*销毁设备类别和设备*/
	if(hello_class) {
		device_destroy(hello_class, MKDEV(hello_major, hello_minor));
		class_destroy(hello_class);
	}        

	/*删除字符设备和释放设备内存*/
	if(hello_dev) {
		cdev_del(&(hello_dev->dev));
		kfree(hello_dev);
	}        

	/*释放设备号*/
	unregister_chrdev_region(devno, 1);
}

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("First Android Driver");

module_init(hello_init);
module_exit(hello_exit);

hello.h如下:

#ifndef _HELLO_ANDROID_H_
#define _HELLO_ANDROID_H_

#include <linux/cdev.h>
#include <linux/semaphore.h>

#define HELLO_DEVICE_NODE_NAME  "hello"
#define HELLO_DEVICE_FILE_NAME  "hello"
#define HELLO_DEVICE_PROC_NAME  "hello"
#define HELLO_DEVICE_CLASS_NAME "hello"

struct hello_android_dev {
	int val;
	struct semaphore sem;
	struct cdev dev;
};

typedef struct _HELLO_ARG {
	int times;
	int read_data;
	int write_data;
}HELLO_ARG;

#define HELLO_IOW(num, dtype)     _IOW('h', num, dtype)
#define HELLO_IOR(num, dtype)     _IOR('h', num, dtype)
#define HELLO_IOWR(num, dtype)    _IOWR('h', num, dtype)
#define HELLO_IO(num)             _IO('h', num)

#define HELLO_READ_WRITE                HELLO_IOWR(1, HELLO_ARG)
#define HELLO_POWER                 	HELLO_IOWR(2, HELLO_ARG)

#endif

3,修改可执行bin

external\hello\hello_program.c改成如下:

#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <string.h>

typedef unsigned char bool;

/*#include "hello.h"*/
#define HELLO_DEVICE_NODE_NAME  "hello"
#define HELLO_DEVICE_FILE_NAME  "hello"
#define HELLO_DEVICE_PROC_NAME  "hello"
#define HELLO_DEVICE_CLASS_NAME "hello"


typedef struct _HELLO_ARG {
	int times;
	int read_data;
	int write_data;
}HELLO_ARG;

#define HELLO_IOW(num, dtype)     _IOW('h', num, dtype)
#define HELLO_IOR(num, dtype)     _IOR('h', num, dtype)
#define HELLO_IOWR(num, dtype)    _IOWR('h', num, dtype)
#define HELLO_IO(num)             _IO('h', num)

#define HELLO_READ_WRITE              HELLO_IOWR(1, HELLO_ARG)
#define HELLO_POWER                 	HELLO_IOWR(2, HELLO_ARG)

static int hello_read_write(HELLO_ARG *hello_arg);
static int hello_power(HELLO_ARG *hello_arg);

static const char *pHelloDev = "/dev/hello";
static char *pFilePath;
static unsigned int u4Addr;
static unsigned int u4Data;


typedef  enum
{
   write_register_cmd=0,
   read_write_cmd,
   power_cmd,
   cmd_nubmer
}   hdmitx_debug_mode;

typedef struct _TEXT2ENUM_T
{
    char* szText;
    unsigned int i4Cmd;
	char* szHelp;
} TEXT2ENUM_T;

static TEXT2ENUM_T _arDebugModeEnumTbl[] =
{
	{ "rw",     read_write_cmd,     "(hello_read_write)res (resolution/help)1/0xff"},
	{ "power",     power_cmd,     "(hello_power)res (resolution/help)1/0xff"},
    { NULL, -1, NULL }
};

static unsigned int comparestr(const char *szText, const TEXT2ENUM_T* prText2Enum)
{
    if ((NULL == szText) ||
        (NULL == prText2Enum))
    {
        return 0;
    }

    while (prText2Enum->szText)
    {
        if((strlen(prText2Enum->szText)==strlen(szText))&&(strncmp(prText2Enum->szText, szText,  strlen(prText2Enum->szText)) == 0))
        {
            break;
        }
        else
        {
            prText2Enum++;
        }
    }

    return prText2Enum->i4Cmd;
}

int main(int argc, char *argv[])
{
    int ret = 0;
    unsigned int read, write, times, i4cmd, i;
    char *pEnd;
    TEXT2ENUM_T* prTextEnum;
    HELLO_ARG hello_arg;

		prTextEnum = _arDebugModeEnumTbl;
	
    if (argc > 2) {
		i4cmd = comparestr(argv[1], prTextEnum);
		
        switch(i4cmd) {
				case read_write_cmd:
					read = strtoul(argv[2],&pEnd,16);
					write = strtoul(argv[3],&pEnd,16);
					times = strtoul(argv[4],&pEnd,16);
					printf("[hello_read_write] read:0x%04x,write:0x%04x,times:%d\n",read,write,times);
					hello_arg.read_data = read;
					hello_arg.write_data = write;
					hello_arg.times = 0;
					for(i = 0; i < times; i++) {
						hello_read_write(&hello_arg);
						printf("[hello_read_write] done i=%d,hello_arg.read_data:0x%04x,hello_arg.write_data:0x%04x,times=%d\n",i, hello_arg.read_data,hello_arg.write_data,hello_arg.times);
					}
					break;
				case power_cmd:
					read = strtoul(argv[2],&pEnd,16);
					write = strtoul(argv[3],&pEnd,16);
					times = strtoul(argv[4],&pEnd,16);
					printf("[hello_power] read:0x%04x,write:0x%04x,times:%d\n",read,write,times);
					hello_arg.read_data = read;
					hello_arg.write_data = write;
					hello_arg.times = 0;

					hello_power(&hello_arg);
					printf("[hello_power] done ,hello_arg.read_data:0x%04x,hello_arg.write_data:0x%04x,times=%d\n", hello_arg.read_data,hello_arg.write_data,hello_arg.times);

					if(hello_arg.write_data == hello_arg.read_data && hello_arg.read_data == 1)
						printf("[hello_power] ------->>>power on ok\n");
					else if (hello_arg.write_data == hello_arg.read_data && hello_arg.read_data == 0)
						printf("[hello_power] ------->>>power off ok\n");
					else
						printf("[hello_power] ------->>>power error\n");
					break;
	    	default:
	    		printf("please input correct mode\n");
	    		ret = -1;
	    		break;			
        }
    } else {
        printf("==============================================================\n");
        ret = -1;
    } 

	return ret;
}



static int hello_read_write(HELLO_ARG *hello_arg)
{
	int fpHello;
	int ret;
	fpHello = open(pHelloDev, O_RDWR, 0);
	
	if (fpHello >= 0) {
	    ret = ioctl(fpHello, HELLO_READ_WRITE, hello_arg);
	    if (ret == -1) {
	        return 0;
	    }
	    close(fpHello);
	} else {
	    return 0;
	}
	return ret;	
}

static int hello_power(HELLO_ARG *hello_arg)
{
	int fpHello;
	int ret;
	fpHello = open(pHelloDev, O_RDWR, 0);
	
	if (fpHello >= 0) {
	    ret = ioctl(fpHello, HELLO_POWER, hello_arg);
	    if (ret == -1) {
	        return 0;
	    }
	    close(fpHello);
	} else {
	    return 0;
	}
	return ret;	
}

4,使用可执行bin测试

root@tb8163p1_64:/data # ./hello power 1 1 2
./hello power 1 1 2
[hello_power] read:0x0001,write:0x0001,times:2
[hello_power] done ,hello_arg.read_data:0x0001,hello_arg.write_data:0x0001,times=0
[hello_power] ------->>>power on ok
root@tb8163p1_64:/data # ./hello power 0 1 2
./hello power 0 1 2
[hello_power] read:0x0000,write:0x0001,times:2
[hello_power] done ,hello_arg.read_data:0x0000,hello_arg.write_data:0x0000,times=0
[hello_power] ------->>>power off ok


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值