tiny4412 设备树之i2c设备(二)

开发板:tiny4412(1611)

内核:linux4.4

编译器: arm-none-linux-gnueabi-gcc (gcc version 4.8.3 20140320


mma7660连接的是i2c3和xeint25。

在以前的内核中(arch/arm/mach-exynos/mach-tiny4412.c)中,是这样初始化i2c控制器及设备的

static struct s3c2410_platform_i2c tiny4412_i2c3_data __initdata = {		//__initdata宏用于数据定义,目的是将数据放入名叫.init.data的段。
        .flags                  = 0,
        .bus_num                = 3,
        .slave_addr             = 0x10,
        .frequency              = 200*1000,
        .sda_delay              = 100,
};
static struct mma7660_platform_data mma7660_pdata = {
        .irq                    = IRQ_EINT(25),
        .poll_interval  = 100,
        .input_fuzz             = 4,
        .input_flat             = 4,
};
static struct i2c_board_info smdk4x12_i2c_devs3[] __initdata = {
#ifdef CONFIG_MXC_MMA845X
        {
                .type = "mma845x",
                .addr = 0x1D,           /*mma845x i2c slave address*/
                .platform_data = (void *)&mma845x_data,
        },
#endif
#ifdef CONFIG_SENSORS_MMA7660
        {
                I2C_BOARD_INFO("mma7660", 0x4c),
                .platform_data = &mma7660_pdata,
        },
#endif
};
…………
s3c_i2c3_set_platdata(&tiny4412_i2c3_data); //设置i2c3控制器信息
i2c_register_board_info(3, smdk4x12_i2c_devs3,ARRAY_SIZE(smdk4x12_i2c_devs3));//注册挂接在i2c3上的设备的信息
…………

重要信息:MMA7660的器件地址是0x4c,I2C3的CLK信号新的频率为200KHz。

有了设备树之后:

在exynos4.dtsi文件下可以看到i2c_3的初始化,

i2c_3: i2c@13890000 {
                #address-cells = <1>;
                #size-cells = <0>;
                compatible = "samsung,s3c2440-i2c";
                reg = <0x13890000 0x100>;
                interrupts = <0 61 0>;
                clocks = <&clock CLK_I2C3>;
                clock-names = "i2c";
                pinctrl-names = "default";
                pinctrl-0 = <&i2c3_bus>;
                status = "disabled";
        };
i2c_3是这个节点的标号,可以通过&i2c_3的方式来调用这个节点。引用时,添加的新属性会合并,相同属性会被合并。添加MMA7660和I2C的硬件信息,可以参考内核文档,Documentation/devicetree/bindings/i2c/i2c.txt和Documentation/devicetree/bindings/i2c/i2c-s3c2410.txt,中断资源的填写可以参考内核文档Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt。
例子如下:
        i2c@13870000 {
                compatible = "samsung,s3c2440-i2c";
                reg = <0x13870000 0x100>;
                interrupts = <345>;
                samsung,i2c-sda-delay = <100>;
                samsung,i2c-max-bus-freq = <100000>;
                /* Samsung GPIO variant begins here */
                gpios = <&gpd1 2 0 /* SDA */
                         &gpd1 3 0 /* SCL */>;
                /* Samsung GPIO variant ends here */
                /* Pinctrl variant begins here */
                pinctrl-0 = <&i2c3_bus>;
                pinctrl-names = "default";
                /* Pinctrl variant ends here */
                #address-cells = <1>;
                #size-cells = <0>;


                wm8994@1a {
                        compatible = "wlf,wm8994";
                        reg = <0x1a>;
                };
        };
在exynos4412-tiny4412.dts下添加如下节点:
&i2c_3 {
	samsung,i2c-sda-delay = <100>;	
	samsung,i2c-slave-addr = <0x10>;	
	samsung,i2c-max-bus-freq = <200000>;
    	status = "okay";


   	mma7660@4c {
        compatible = "tiny4412,mma7660";
        reg = <0x4c>;
        interrupt-parent = <&gpx3>;
        interrupts = <1 2>;
        poll_interval = <100>;
        input_fuzz = <4>;
        input_flat = <4>;
        status = "okay";
	};
};
为i2c_3添加了3个自定义属性,覆写了status属性,并在其下添加了子节点,因为子节点的中断控制器不是i2c_3,所以添加interrupt-parent属性,关于如何填写这个中断资源,可以查看Documentation/devicetree/bindings/pinctrl/samsung-pinctrl.txt
对于interrupts = <1 2>,其中1表示GPX3_1,2表示的是下降沿触发。加载新的设备树后,可以在sys/firmware/devicetree/base/i2c@13890000下找到mma7660@4c节点。
grep 'samsung,s3c2440-i2c' -nr
i2c-s3c2410.c:154:      { .compatible = "samsung,s3c2440-i2c", .data = (void *)QUIRK_S3C2440 },

可以在i2c-s3c2410.c找到i2c控制器的驱动,所以我们只要写i2c设备的驱动。

在i2c驱动中添加of_device_id属性,来匹配设备树节点

  1. static const struct of_device_id dt_ids[] = {  
  2.         { .compatible = "tiny4412,mma7660", },  
  3.         {},  
  4. }; 

驱动匹配上节点之后,报出如下错误:

mma7660 3-004c: lack of platform data! 

原因是client->dev.platform_data还没包含数据,所以我们现在往probe中添加解析设备树节点信息并设置client->dev.platform_data的函数。

static struct mma7660_platform_data *mma7660_parse_dt(struct device *dev){

	struct mma7660_platform_data *pdata;
       struct device_node *np = dev->of_node;

      	if (!np)
      		return NULL;

	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
       if (!pdata) {
           dev_err(dev, "failed to allocate platform data\n");
         return NULL;
       }
    
       if (of_property_read_u32(np, "poll_interval", &pdata->poll_interval)) {
           dev_err(dev, "failed to get poll_interval property\n");
           return NULL;
       }
    
       if (of_property_read_u32(np, "input_fuzz", &pdata->input_fuzz)) {
          dev_err(dev, "failed to get input_fuzz property\n");
           return NULL;
       }
    
       if (of_property_read_u32(np, "input_flat", &pdata->input_flat)) {
           dev_err(dev, "failed to get input_flat property\n");
           return NULL;
       }

	pdata->irq=irq_of_parse_and_map(np, 0);
	printk("%d",pdata->irq);
       return pdata;
}

注册驱动后打印如下:


驱动:

/*
 * linux/drivers/hwmon/mma7660.c
 *
 * 3-Axis Orientation/Motion Detection Sensor support
 *
 * Copyright (C) 2009-2010 Freescale Semiconductor Ltd.
 *
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include <linux/module.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/err.h>
#include <linux/input-polldev.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#include <linux/mma7660.h>
#include <linux/of_irq.h>

#define MMA7660_NAME		"mma7660"

#define POLL_INTERVAL		100
#define INPUT_FUZZ			4
#define INPUT_FLAT			4


static struct i2c_client	*mma7660_client;

static struct device		*hwmon_dev;
static struct input_polled_dev	*mma7660_idev;
static struct mma7660_platform_data	*plat_data;

static int					last_tilt = 0;
//static int					oper_mode;


/*-----------------------------------------------------------------------------
 * MMA7660 operations
 */

#define __need_retry(__v)	(__v & (1 << 6))
#define __is_negative(__v)	(__v & (1 << 5))

static const char *mma7660_bafro[] = {
	"Unknown", "Front", "Back"
};
static const char *mma7660_pola[] = {
	"Unknown",
	"Left", "Right",
	"Rsvd", "Rsvd",
	"Down", "Up",
	"Rsvd",
};

static int mma7660_read_xyz(struct i2c_client *client, int idx, int *xyz)
{
	int val;

	do {
		val = i2c_smbus_read_byte_data(client, idx + MMA7660_XOUT);
		if (val < 0) {
			dev_err(&client->dev, "Read register %02x failed, %d\n",
					idx + MMA7660_XOUT, val);
			return -EIO;
		}
	} while (__need_retry(val));

	*xyz = __is_negative(val) ? (val | ~0x3f) : (val & 0x3f);

	return 0;
}

static int mma7660_read_tilt(struct i2c_client *client, int *tilt)
{
	int val;

	do {
		val = i2c_smbus_read_byte_data(client, MMA7660_TILT);
		if (val < 0) {
			dev_err(&client->dev, "Read register %02x failed, %d\n",
					MMA7660_TILT, val);
			return -EIO;
		}
	} while (__need_retry(val));

	*tilt = (val & 0xff);

	return 0;
}

static int mma7660_initialize(struct i2c_client *client)
{
	int val;

	/* Using test mode to probe chip */
	i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x00);
	mdelay(10);
	i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x04);
	mdelay(10);
	i2c_smbus_write_byte_data(client, MMA7660_XOUT, 0x3f);
	i2c_smbus_write_byte_data(client, MMA7660_YOUT, 0x01);
	i2c_smbus_write_byte_data(client, MMA7660_ZOUT, 0x15);
	val = i2c_smbus_read_byte_data(client, MMA7660_ZOUT);
	if (val != 0x15) {
		dev_err(&client->dev, "no device\n");
		return -ENODEV;
	}

	/* Goto standby mode for configuration */
	i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x00);
	mdelay(10);

	/* Sample rate: 64Hz / 16Hz; Filt: 3 samples  */
	i2c_smbus_write_byte_data(client, MMA7660_SR, ((2<<5) | (1<<3) | 1));

	/* Sleep count */
	i2c_smbus_write_byte_data(client, MMA7660_SPCNT, 0xA0);

	/* Tap detect and debounce ~4ms */
	i2c_smbus_write_byte_data(client, MMA7660_PDET, 4);
	i2c_smbus_write_byte_data(client, MMA7660_PD, 15);

	/* Enable interrupt except exiting Auto-Sleep */
	i2c_smbus_write_byte_data(client, MMA7660_INTSU, 0xe7);

	/* IPP, Auto-wake, auto-sleep and standby */
	i2c_smbus_write_byte_data(client, MMA7660_MODE, 0x59);
	mdelay(10);

	/* Save current tilt status */
	mma7660_read_tilt(client, &last_tilt);

	mma7660_client = client;
	return 0;
}


/*-----------------------------------------------------------------------------
 * sysfs group support
 */

static ssize_t mma7660_show_regs(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	int reg, val;
	int i, len = 0;

	for (reg = 0; reg < 0x0b; reg++) {
		val = i2c_smbus_read_byte_data(mma7660_client, reg);
		len += sprintf(buf + len, "REG: 0x%02x = 0x%02x ...... [ ", reg, val);
		for (i = 7; i >= 0; i--) {
			len += sprintf(buf + len, "%d", (val >> i) & 1);
			if ((i % 4) == 0) {
				len += sprintf(buf + len, " ");
			}
		}
		len += sprintf(buf + len, "]\n");
	}

	return len;
}

static ssize_t mma7660_write_reg(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	unsigned int reg, val;
	int ret;

	ret = sscanf(buf, "%x %x", &reg, &val);
	if (ret == 2) {
		if (reg >= 0 && reg <= 0x0a) {
			i2c_smbus_write_byte_data(mma7660_client, reg, val);
			val = i2c_smbus_read_byte_data(mma7660_client, reg);
			printk("REG: 0x%02x = 0x%02x\n", reg, val);
		}
	}

	return count;
}

static ssize_t mma7660_show_xyz_g(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	int axis[3];
	int i;

	for (i = 0; i < 3; i++) {
		mma7660_read_xyz(mma7660_client, i, &axis[i]);
	}

	return sprintf(buf, "%3d, %3d, %3d\n", axis[0], axis[1], axis[2]);
}

static ssize_t mma7660_show_axis_g(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	int n = to_sensor_dev_attr(attr)->index;
	int val;

	mma7660_read_xyz(mma7660_client, n, &val);

	return sprintf(buf, "%3d\n", val);
}

static ssize_t mma7660_show_tilt(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	int val = 0, len = 0;

	mma7660_read_tilt(mma7660_client, &val);

	len += sprintf(buf + len, "%s", mma7660_bafro[val & 0x03]);
	len += sprintf(buf + len, ", %s", mma7660_pola[(val >> 2) & 0x07]);
	if (val & (1 << 5)) {
		len += sprintf(buf + len, ", Tap");
	}
	if (val & (1 << 7)) {
		len += sprintf(buf + len, ", Shake");
	}
	len += sprintf(buf + len, "\n");

	return len;
}

static SENSOR_DEVICE_ATTR(registers, 0660,mma7660_show_regs, mma7660_write_reg, 0);
static SENSOR_DEVICE_ATTR(x_axis_g, S_IRUGO, mma7660_show_axis_g, NULL, 0);
static SENSOR_DEVICE_ATTR(y_axis_g, S_IRUGO, mma7660_show_axis_g, NULL, 1);
static SENSOR_DEVICE_ATTR(z_axis_g, S_IRUGO, mma7660_show_axis_g, NULL, 2);
static SENSOR_DEVICE_ATTR(all_axis_g, S_IRUGO, mma7660_show_xyz_g, NULL, 0);
static SENSOR_DEVICE_ATTR(tilt_status, S_IRUGO, mma7660_show_tilt, NULL, 0);

static struct attribute* mma7660_attrs[] = {
	&sensor_dev_attr_registers.dev_attr.attr,
	&sensor_dev_attr_x_axis_g.dev_attr.attr,
	&sensor_dev_attr_y_axis_g.dev_attr.attr,
	&sensor_dev_attr_z_axis_g.dev_attr.attr,
	&sensor_dev_attr_all_axis_g.dev_attr.attr,
	&sensor_dev_attr_tilt_status.dev_attr.attr,
	NULL
};

static const struct attribute_group mma7660_group = {
	.attrs		= mma7660_attrs,
};


/*-----------------------------------------------------------------------------
 * Input interfaces
 */
static void mma7660_report_abs(void)
{
	int axis[3];
	int i;

	for (i = 0; i < 3; i++) {
		mma7660_read_xyz(mma7660_client, i, &axis[i]);
	}

	input_report_abs(mma7660_idev->input, ABS_X, axis[0]);
	input_report_abs(mma7660_idev->input, ABS_Y, axis[1]);
	input_report_abs(mma7660_idev->input, ABS_Z, axis[2]);
	input_sync(mma7660_idev->input);

	//printk("3-Axis ... %3d, %3d, %3d\n", axis[0], axis[1], axis[2]);
}

static void mma7660_dev_poll(struct input_polled_dev *dev)
{
	mma7660_report_abs();
}


/*-----------------------------------------------------------------------------
 * Interrupt handler
 */

static void mma7660_worker(struct work_struct *work)
{
	int bafro, pola, shake, tap;
	int val = 0;

	mma7660_read_tilt(mma7660_client, &val);

	/* TODO: report it ? */

	bafro = val & 0x03;
	if (bafro != (last_tilt & 0x03)) {
		printk("%s\n", mma7660_bafro[bafro]);
	}

	pola = (val >> 2) & 0x07;
	if (pola != ((last_tilt >> 2) & 0x07)) {
		printk("%s\n", mma7660_pola[pola]);
	}

	shake = (val >> 5) & 0x01;
	if (shake && shake != ((last_tilt >> 5) & 0x01)) {
		printk("Shake\n");
	}

	tap = (val >> 7) & 0x01;
	if (tap && tap != ((last_tilt >> 7) & 0x01)) {
		printk("Tap\n");
	}

	/* Save current status */
	last_tilt = val;
}

DECLARE_WORK(mma7660_work, mma7660_worker);

static irqreturn_t mma7660_interrupt(int irq, void *dev_id)
{
	schedule_work(&mma7660_work);

	return IRQ_HANDLED;
}

static struct mma7660_platform_data *mma7660_parse_dt(struct device *dev){

	struct mma7660_platform_data *pdata;
       struct device_node *np = dev->of_node;

      	if (!np)
      		return NULL;

	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
       if (!pdata) {
           dev_err(dev, "failed to allocate platform data\n");
         return NULL;
       }
    
       if (of_property_read_u32(np, "poll_interval", &pdata->poll_interval)) {
           dev_err(dev, "failed to get poll_interval property\n");
           return NULL;
       }
    
       if (of_property_read_u32(np, "input_fuzz", &pdata->input_fuzz)) {
          dev_err(dev, "failed to get input_fuzz property\n");
           return NULL;
       }
    
       if (of_property_read_u32(np, "input_flat", &pdata->input_flat)) {
           dev_err(dev, "failed to get input_flat property\n");
           return NULL;
       }

	pdata->irq=irq_of_parse_and_map(np, 0);
	printk("%d",pdata->irq);
       return pdata;
}
/*-----------------------------------------------------------------------------
 * I2C client driver interfaces
 */

static int mma7660_probe(struct i2c_client *client,
		const struct i2c_device_id *id)
{
	struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
	struct input_dev *idev;
	int poll_interval = POLL_INTERVAL;
	int input_fuzz = INPUT_FUZZ;
	int input_flat = INPUT_FLAT;
	int ret;

	ret = i2c_check_functionality(adapter,
			I2C_FUNC_SMBUS_BYTE | I2C_FUNC_SMBUS_BYTE_DATA);
	if (!ret) {
		dev_err(&client->dev, "I2C check functionality failed\n");
		return -ENXIO;
	}
	client->dev.platform_data=mma7660_parse_dt(&client->dev);
	plat_data = (struct mma7660_platform_data *)client->dev.platform_data;
	if (plat_data == NULL) {
		dev_err(&client->dev, "lack of platform data!\n");
		return -ENODEV;
	}

	/* Get parameters from platfrom data */
	if (plat_data->poll_interval > 0)
		poll_interval = plat_data->poll_interval;
	if (plat_data->input_fuzz > 0)
		input_fuzz = plat_data->input_fuzz;
	if (plat_data->input_flat > 0)
		input_flat = plat_data->input_flat;

	if (mma7660_initialize(client) < 0) {
		goto error_init_client;
	}

	ret = sysfs_create_group(&client->dev.kobj, &mma7660_group);
	if (ret) {
		dev_err(&client->dev, "create sysfs group failed!\n");
		goto error_init_client;
	}

	/* register to hwmon device */
	hwmon_dev = hwmon_device_register(&client->dev);
	if (IS_ERR(hwmon_dev)) {
		dev_err(&client->dev, "hwmon register failed!\n");
		ret = PTR_ERR(hwmon_dev);
		goto error_rm_dev_file;
	}

	/* input poll device register */
	mma7660_idev = input_allocate_polled_device();
	if (!mma7660_idev) {
		dev_err(&client->dev, "alloc poll device failed!\n");
		ret = -ENOMEM;
		goto error_rm_hwmon_dev;
	}

	mma7660_idev->poll = mma7660_dev_poll;
	mma7660_idev->poll_interval = plat_data->poll_interval;

	idev = mma7660_idev->input;
	idev->name = MMA7660_NAME;
	idev->id.bustype = BUS_I2C;
	idev->id.vendor = 0x12FA;
	idev->id.product = 0x7660;
	idev->id.version = 0x0100;
	idev->dev.parent = &client->dev;

	set_bit(EV_ABS, idev->evbit);

	set_bit(ABS_X, idev->absbit);
	set_bit(ABS_Y, idev->absbit);
	set_bit(ABS_Z, idev->absbit);

	input_set_abs_params(idev, ABS_X, -512, 512, input_fuzz, input_flat);
	input_set_abs_params(idev, ABS_Y, -512, 512, input_fuzz, input_flat);
	input_set_abs_params(idev, ABS_Z, -512, 512, input_fuzz, input_flat);
	ret = input_register_polled_device(mma7660_idev);
	if (ret) {
		dev_err(&client->dev, "register poll device failed!\n");
		goto error_free_poll_dev;
	}

	/* register interrupt handle */
	ret = request_irq(plat_data->irq, mma7660_interrupt,
			IRQF_TRIGGER_FALLING, MMA7660_NAME, idev);
	if (ret) {
		dev_err(&client->dev, "request irq (%d) failed %d\n", plat_data->irq, ret);
		goto error_rm_poll_dev;
	}

	dev_info(&client->dev, "MMA7660 device is probed successfully.\n");

#if	0
	set_mod(1);
#endif

	return 0;

error_rm_poll_dev:
	input_unregister_polled_device(mma7660_idev);
error_free_poll_dev:
	input_free_polled_device(mma7660_idev);
error_rm_hwmon_dev:
	hwmon_device_unregister(hwmon_dev);
error_rm_dev_file:
	sysfs_remove_group(&client->dev.kobj, &mma7660_group);
error_init_client:
	mma7660_client = NULL;

	return 0;
}

static int mma7660_remove(struct i2c_client *client)
{
	free_irq(plat_data->irq, mma7660_idev->input);

	input_unregister_polled_device(mma7660_idev);
	input_free_polled_device(mma7660_idev);
	hwmon_device_unregister(hwmon_dev);
	sysfs_remove_group(&client->dev.kobj, &mma7660_group);
	mma7660_client = NULL;

	return 0;
}

/*
static int mma7660_suspend(struct i2c_client *client, pm_message_t state)
{
	int ret;

	oper_mode = i2c_smbus_read_byte_data(client, MMA7660_MODE);

	ret = i2c_smbus_write_byte_data(client, MMA7660_MODE, 0);
	if (ret) {
		printk("%s: set mode (0) for suspend failed, ret = %d\n",
				MMA7660_NAME, ret);
	}

	return 0;
}
*/

/*
static int mma7660_resume(struct i2c_client *client)
{
	int ret;

	ret = i2c_smbus_write_byte_data(client, MMA7660_MODE, oper_mode);
	if (ret) {
		printk("%s: set mode (%d) for resume failed, ret = %d\n",
				MMA7660_NAME, oper_mode, ret);
	}

	return 0;
}
*/

static const struct of_device_id mma7660[] = {  
        { .compatible = "freescale,mma7660", },  
        {},  
};  
  
MODULE_DEVICE_TABLE(of,mma7660); 

static const struct i2c_device_id mma7660_ids[] = {
        { "mma7660", 0 },
        { },
};
MODULE_DEVICE_TABLE(i2c, mma7660_ids);

static struct i2c_driver i2c_mma7660_driver = {
	.driver		= {
		.name	= MMA7660_NAME,
		.of_match_table=mma7660, 
	},

	.probe		= mma7660_probe,
	.remove		= mma7660_remove,
	//.suspend		= mma7660_suspend,
	//.resume		= mma7660_resume,
	.id_table       = mma7660_ids,
};

static int __init init_mma7660(void)
{
	int ret;

	ret = i2c_add_driver(&i2c_mma7660_driver);
	printk(KERN_INFO "MMA7660 sensor driver registered.\n");

	return ret;
}

static void __exit exit_mma7660(void)
{
	i2c_del_driver(&i2c_mma7660_driver);
	printk(KERN_INFO "MMA7660 sensor driver removed.\n");
}

module_init(init_mma7660);
module_exit(exit_mma7660);

MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("MMA7660 sensor driver");
MODULE_LICENSE("GPL");

测试:

cd /sys/devices/platform/13890000.i2c/i2c-3/3-004c/ 
可以看到如下输出:



评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值