コード例 #1
0
int ist30xx_i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
			 int msg_num, u8 *cmd_buf)
{
	int ret = 0;
	int idx = msg_num - 1;
	int size = msgs[idx].len;
	u8 *msg_buf = NULL;
	u8 *pbuf = NULL;
	int trans_size, max_size = 0;

	if (msg_num == WRITE_CMD_MSG_LEN)
		max_size = I2C_MAX_WRITE_SIZE;
	else if (msg_num == READ_CMD_MSG_LEN)
		max_size = I2C_MAX_READ_SIZE;

	if (unlikely(max_size == 0)) {
		tsp_err("%s() : transaction size(%d)\n", __func__, max_size);
		return -EINVAL;
	}

	if (msg_num == WRITE_CMD_MSG_LEN) {
		msg_buf = kmalloc(max_size + IST30XX_ADDR_LEN, GFP_KERNEL);
		if (!msg_buf)
			return -ENOMEM;
		memcpy(msg_buf, cmd_buf, IST30XX_ADDR_LEN);
		pbuf = msgs[idx].buf;
	}

	while (size > 0) {
		trans_size = (size >= max_size ? max_size : size);

		msgs[idx].len = trans_size;
		if (msg_num == WRITE_CMD_MSG_LEN) {
			memcpy(&msg_buf[IST30XX_ADDR_LEN], pbuf, trans_size);
			msgs[idx].buf = msg_buf;
			msgs[idx].len += IST30XX_ADDR_LEN;
		}
		ret = i2c_transfer(adap, msgs, msg_num);
		if (unlikely(ret != msg_num)) {
			tsp_err("%s() : i2c_transfer failed(%d), num=%d\n",
				__func__, ret, msg_num);
			break;
		}

		if (msg_num == WRITE_CMD_MSG_LEN)
			pbuf += trans_size;
		else
			msgs[idx].buf += trans_size;

		size -= trans_size;
	}

	if (msg_num == WRITE_CMD_MSG_LEN)
		kfree(msg_buf);

	return ret;
}
コード例 #2
0
int ist30xx_ts_on(void)
{
#if 1
	ist30xx_ldo_power_on(ts_data, true);
	msleep(50);
	return 0;
#else
//	struct ist30xx_ts_device *dev = NULL;
	int ret = 0;

//	dev = &ist30xx_ts_dev;

	ret = ts_data->power(ON);

	tsp_info("ist30xx_ts_on\n");

	if(ret < 0) {
		tsp_err("ist30xx_ts_on power on failed\n");
		goto err_power_failed;
	}

	ts_data->status.power = 1;

	msleep(30);

err_power_failed:
	return ret;
#endif
}
コード例 #3
0
void print_tsp_event(finger_info *finger)
{
	int idx = finger->bit_field.id - 1;
	int press = finger->bit_field.udmg & PRESS_MSG_MASK;
	if ( idx < 0 ) {
		tsp_err("finger idx err! idx value : %d\n", idx);
		return;
	}

	if (press == PRESS_MSG_MASK) {
		if (tsp_touched[idx] == 0) { // touch down
			tsp_info("%s - %d (%d, %d)\n",
				 TOUCH_DOWN_MESSAGE, finger->bit_field.id,
				 finger->bit_field.x, finger->bit_field.y);
			tsp_touched[idx] = 1;
		} else {                    // touch move
			tsp_debug("%s   %d (%d, %d)\n",
				  TOUCH_MOVE_MESSAGE, finger->bit_field.id,
				  finger->bit_field.x, finger->bit_field.y);
		}
	} else {
		if (tsp_touched[idx] == 1) { // touch up
			tsp_info("%s - %d (%d, %d)\n",
				 TOUCH_UP_MESSAGE, finger->bit_field.id,
				 finger->bit_field.x, finger->bit_field.y);
			tsp_touched[idx] = 0;
		}
	}
}
コード例 #4
0
int ist30xx_ts_off(void)
{
#if 1
	ist30xx_ldo_power_on(ts_data, false);
	msleep(20);
	return 0;
#else
//	struct ist30xx_ts_device *dev = NULL;
	int ret = 0;

//	dev = &ist30xx_ts_dev;

	ret = ts_data->power(OFF);

	tsp_info("ist30xx_ts_off\n");

	if(ret < 0) {
		tsp_err("ist30xx_ts_off power off failed\n");
		goto err_power_failed;
	}

	ts_data->status.power = 0;

	msleep(10);

err_power_failed:
	return ret;
#endif
}
コード例 #5
0
int ist30xx_init_tracking_sysfs(void)
{
	/* /sys/class/touch/tracking */
	ist30xx_tracking_dev = device_create(ist30xx_class, NULL, 0, NULL, "tracking");

	/* /sys/class/touch/tracking/... */
	if (sysfs_create_group(&ist30xx_tracking_dev->kobj, &tracking_attr_group))
		tsp_err("[ TSP ] Failed to create sysfs group(%s)!\n", "tracking");

	ist30xx_tracking_init();

	return 0;
}
コード例 #6
0
int ist30xx_load_cmcs_binary(struct ist30xx_data *data)
{
	int i, ret;
	struct ist30xx_config_info *info = data->pdata->config_array;
	const struct firmware *req_cmcs_bin = NULL;

	for (i = 0; i < data->pdata->config_array_size; i++) {
		if (data->tsp_type == info[i].tsp_type)
			break;
	}

	/* If no corresponding tsp firmware is found, just use the default one */
	if (i >= data->pdata->config_array_size) {
		tsp_warn("No corresponding TSP firmware found, use the default one(%x)\n", data->tsp_type);
		i = 0;
	} else {
		tsp_info("TSP vendor: %s(%x)\n", info[i].tsp_name, data->tsp_type);
	}

	ret = request_firmware(&req_cmcs_bin, info[i].cmcs_name, &data->client->dev);
	if (unlikely(ret)) {
		tsp_err("Cannot load CMCS binary file: %s\n", info[i].cmcs_name);
		return ret;
	}

	data->cmcs_bin = kmalloc(req_cmcs_bin->size, GFP_KERNEL);
	if (unlikely(!data->cmcs_bin)) {
		tsp_err("Cannot allocate memory for CMCS binary.\n");
		release_firmware(req_cmcs_bin);
		return -ENOMEM;
	}

	memcpy(data->cmcs_bin, req_cmcs_bin->data, req_cmcs_bin->size);
	data->cmcs_bin_size = (u32)req_cmcs_bin->size;
	tsp_info("CMCS binary [%s] loaded successfully.\n", info[i].cmcs_name);
	release_firmware(req_cmcs_bin);

	return 0;
}
int ist30xx_init_cmcs_sysfs(void)
{
	/* /sys/class/touch/cmcs */
	ist30xx_cmcs_dev = device_create(ist30xx_class, NULL, 0, NULL, "cmcs");

	/* /sys/class/touch/cmcs/... */
	if (unlikely(sysfs_create_group(&ist30xx_cmcs_dev->kobj,
					&cmcs_attr_group)))
		tsp_err("Failed to create sysfs group(%s)!\n", "cmcs");

#if IST30XX_INTERNAL_CMCS_BIN
	ts_cmcs_bin = (u8 *)ist30xxb_cmcs;
	ts_cmcs_bin_size = sizeof(ist30xxb_cmcs);

	ist30xx_get_cmcs_info(ts_cmcs_bin, ts_cmcs_bin_size);
#endif

	return 0;
}
コード例 #8
0
int ist30xx_get_position(struct i2c_client *client, u32 *buf, u16 len)
{
	int ret, i;

	pos_msg[0].addr = client->addr;
	pos_msg[1].addr = client->addr;
	pos_msg[1].len = len * IST30XX_DATA_LEN,
	pos_msg[1].buf = (u8 *)buf,

	ret = i2c_transfer(client->adapter, pos_msg, READ_CMD_MSG_LEN);
	if (ret != READ_CMD_MSG_LEN) {
		tsp_err("%s: i2c failed (%d)\n", __func__, ret);
		return -EIO;
	}

	for (i = 0; i < len; i++)
		buf[i] = cpu_to_be32(buf[i]);

	return 0;
}
コード例 #9
0
int ist30xx_init_cmcs_sysfs(struct ist30xx_data *data)
{
	/* /sys/class/touch/cmcs */
	data->cmcs_dev = device_create(data->ist30xx_class, NULL, 0, data, "cmcs");

	/* /sys/class/touch/cmcs/... */
	if (unlikely(sysfs_create_group(&data->cmcs_dev->kobj,
					&cmcs_attr_group)))
		tsp_err("Failed to create sysfs group(%s)!\n", "cmcs");

	data->cmcs_bin = NULL;
	data->cmcs_bin_size = 0;

	data->cmcs = (CMCS_BIN_INFO *)&data->ist30xx_cmcs_bin;
	data->cmcs_buf = (CMCS_BUF *)&data->ist30xx_cmcs_buf;

	data->cmcs_ready = CMCS_NOT_READY;

	return 0;
}
コード例 #10
0
static irqreturn_t ist30xx_irq_thread(int irq, void *ptr)
{
	int i, ret;
	int key_cnt, finger_cnt, read_cnt;
	struct ist30xx_data *data = ptr;
	u32 msg[IST30XX_MAX_MT_FINGERS];
	bool unknown_idle = false;

#if IST30XX_TRACKING_MODE
	u32 ms;
#endif

	if (!data->irq_enabled)
		return IRQ_HANDLED;

	memset(msg, 0, sizeof(msg));

	ret = ist30xx_get_position(data->client, msg, 1);
	if (ret)
		goto irq_err;

	tsp_verb("intr msg: 0x%08x\n", *msg);

	if (msg[0] == 0)
		return IRQ_HANDLED;

#if IST30XX_EVENT_MODE
	if ((data->status.update != 1) && (data->status.calib != 1))
		ktime_get_ts(&t_event);
#endif

#if IST30XX_TRACKING_MODE
	ms = t_event.tv_sec * 1000 + t_event.tv_nsec / 1000000;
    ist30xx_put_track(ms, msg[0]);
#endif

#if IST30XX_NOISE_MODE
	if (get_event_mode) {
		if ((msg[0] & 0xFFFF0000) == IST30XX_IDLE_STATUS) {
			if (msg[0] & IDLE_ALGORITHM_MODE)
				return IRQ_HANDLED;

			for (i = 0; i < IST30XX_MAX_MT_FINGERS; i++) {
				if (data->prev_fingers[i].bit_field.id == 0)
					continue;

				if (data->prev_fingers[i].bit_field.udmg & PRESS_MSG_MASK) {
					tsp_warn("prev_fingers: %08x\n",
						 data->prev_fingers[i].full_field);
					release_finger(&data->prev_fingers[i]);
					unknown_idle = true;
				}
			}

			for (i = 0; i < data->num_keys; i++) {
				if (data->prev_keys[i].bit_field.id == 0)
					continue;

				if (data->prev_keys[i].bit_field.w == PRESS_MSG_KEY) {
					tsp_warn("prev_keys: %08x\n",
						 data->prev_keys[i].full_field);
					release_key(&data->prev_keys[i], RELEASE_KEY);
					unknown_idle = true;
				}
			}

			if (unknown_idle) {
				schedule_delayed_work(&work_reset_check, 0);
				tsp_warn("Find unknown pressure\n");
			}

			return IRQ_HANDLED;
		}
	}
#endif  // IST30XX_NOISE_MODE

	if ((msg[0] & CALIB_MSG_MASK) == CALIB_MSG_VALID) {
		data->status.calib_msg = msg[0];
		tsp_info("calib status: 0x%08x\n", data->status.calib_msg);
		return IRQ_HANDLED;
	}

	for (i = 0; i < IST30XX_MAX_MT_FINGERS; i++)
		data->fingers[i].full_field = 0;

	key_cnt = 0;
	finger_cnt = 1;
	read_cnt = 1;
	data->fingers[0].full_field = msg[0];

	if (data->fingers[0].bit_field.udmg & MULTI_MSG_MASK) {
		key_cnt = data->fingers[0].bit_field.x;
		finger_cnt = data->fingers[0].bit_field.y;
		read_cnt = finger_cnt + key_cnt;

		if (finger_cnt > ist30xx_tsp_info.finger_num ||
		    key_cnt > ist30xx_tkey_info.key_num) {
			tsp_warn("Invalid touch count - finger: %d(%d), key: %d(%d)\n",
				 finger_cnt, ist30xx_tsp_info.finger_num,
				 key_cnt, ist30xx_tkey_info.key_num);
			goto irq_err;
		}

#if I2C_BURST_MODE
		ret = ist30xx_get_position(data->client, msg, read_cnt);
		if (ret)
			goto irq_err;

		for (i = 0; i < read_cnt; i++)
			data->fingers[i].full_field = msg[i];
#else
		for (i = 0; i < read_cnt; i++) {
			ret = ist30xx_get_position(data->client, &msg[i], 1);
			if (ret)
				goto irq_err;

			data->fingers[i].full_field = msg[i];
		}
#endif          // I2C_BURST_MODE

		for (i = 0; i < read_cnt; i++)
			tsp_verb("intr msg[%d]: 0x%08x\n", i, msg[i]);

#if IST30XX_TRACKING_MODE
		for (i = 0; i < read_cnt; i++)
			ist30xx_put_track(ms, msg[i]);
#endif
	}

	if (check_report_data(data, finger_cnt, key_cnt))
		return IRQ_HANDLED;

	if (read_cnt > 0)
		report_input_data(data, finger_cnt, key_cnt);

	return IRQ_HANDLED;

irq_err:
	tsp_err("intr msg[0]: 0x%08x, ret: %d\n", msg[0], ret);
	ist30xx_request_reset();
	return IRQ_HANDLED;
}
コード例 #11
0
static int __devinit ist30xx_probe(struct i2c_client *		client,
				   const struct i2c_device_id * id)
{
	int ret;
	struct ist30xx_data *data;
	struct input_dev *input_dev;

#if 0
	/* [email protected] */
	struct touch_platform_data *ts_pdata;
//	struct ist30xx_ts_device *dev;

	ts_pdata = client->dev.platform_data;
//	dev = &ist30xx_ts_dev;
	/* [email protected] */
#endif

	tsp_info("\n%s(), the i2c addr=0x%x \n", __func__, client->addr);

/*	dev->power = ts_pdata->power;
	dev->num_irq = ts_pdata->irq;
	dev->sda_gpio = ts_pdata->sda;
	dev->scl_gpio  = ts_pdata->scl;*/

	if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
		tsp_debug("failed to i2c functionality check");
		ret = -ENODEV;
		goto err_check_functionality_failed;
	}
	
	data = kzalloc(sizeof(*data), GFP_KERNEL);
	if (!data)
		return -ENOMEM;

	data->client = client;

	if(client->dev.of_node) {
		data->pdata = devm_kzalloc(&client->dev, sizeof(struct ist30xx_tsi_platform_data), GFP_KERNEL);
		if(!data->pdata) {
			tsp_debug("failed to allocate platform_data");
			return -ENOMEM;
		}

		ret = ist30xx_parse_dt(&client->dev, data->pdata);
		if(ret) {
			tsp_debug("device tree parsing failed");
			return ret;
		}
	} else {
		data->pdata = client->dev.platform_data;
	}

	ret = ist30xx_regulator_configure(data, true);
	if (ret < 0) {
			tsp_debug("Failed to configure regulators");
	}

	ret = ist30xx_ldo_power_on(data, true);
	if (ret < 0) {
			tsp_debug("Failed to power on");
	}

	input_dev = input_allocate_device();
	if (!input_dev) {
		ret = -ENOMEM;
		tsp_err("%s(), input_allocate_device failed (%d)\n", __func__, ret);
		goto err_alloc_dev;
	}

#if 0
	DMSG("[ TSP ] irq : %d, scl : %d, sda : %d\n", client->irq, ts_pdata->scl, ts_pdata->sda);
#endif
	data->num_fingers = IST30XX_MAX_MT_FINGERS;
	data->num_keys = IST30XX_MAX_MT_FINGERS;
	data->irq_enabled = 1;
	data->client = client;
	data->input_dev = input_dev;
#if 0
	/* [email protected] */
	data->power = ts_pdata->power;
	/* [email protected] */
#endif
	i2c_set_clientdata(client, data);

#if (LINUX_VERSION_CODE > KERNEL_VERSION(3, 0, 0))
	input_mt_init_slots(input_dev, IST30XX_MAX_MT_FINGERS);
#endif

	input_dev->name = "ist30xx_ts_input";
	input_dev->id.bustype = BUS_I2C;
	input_dev->dev.parent = &client->dev;

	set_bit(EV_ABS, input_dev->evbit);
#if (LINUX_VERSION_CODE > KERNEL_VERSION(3, 0, 0))
	set_bit(INPUT_PROP_DIRECT, input_dev->propbit);
#endif

	input_set_abs_params(input_dev, ABS_MT_POSITION_X, 0, IST30XX_MAX_X, 0, 0);
	input_set_abs_params(input_dev, ABS_MT_POSITION_Y, 0, IST30XX_MAX_Y, 0, 0);
#if (LINUX_VERSION_CODE > KERNEL_VERSION(3, 0, 0))
	input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, IST30XX_MAX_W, 0, 0);
#else
	input_set_abs_params(input_dev, ABS_MT_TOUCH_MAJOR, 0, IST30XX_MAX_Z, 0, 0);
	input_set_abs_params(input_dev, ABS_MT_WIDTH_MAJOR, 0, IST30XX_MAX_W, 0, 0);
#endif

#if IST30XX_USE_KEY
	{
		int i;
		set_bit(EV_KEY, input_dev->evbit);
		set_bit(EV_SYN, input_dev->evbit);
		for (i = 1; i < ARRAY_SIZE(ist30xx_key_code); i++)
			set_bit(ist30xx_key_code[i], input_dev->keybit);
	}
#endif

	input_set_drvdata(input_dev, data);
	ret = input_register_device(input_dev);
	if (ret) {
		input_free_device(input_dev);
		goto err_reg_dev;
	}

#if defined(CONFIG_FB)
	data->fb_notif.notifier_call = fb_notifier_callback;
	ret = fb_register_client(&data->fb_notif);
	if(ret)
		tsp_debug("Unable to register fb_notifier \n");
	else
		tsp_debug("Register fb_notifier \n");
#elif defined(CONFIG_HAS_EARLYSUSPEND)
	data->early_suspend.level = EARLY_SUSPEND_LEVEL_BLANK_SCREEN + 1;
	data->early_suspend.suspend = ist30xx_early_suspend;
	data->early_suspend.resume = ist30xx_late_resume;
	register_early_suspend(&data->early_suspend);
#endif

	ts_data = data;

	ret = ist30xx_init_system();
	if (ret) {
		dev_err(&client->dev, "chip initialization failed\n");
		goto err_init_drv;
	}

	ret = ist30xx_init_update_sysfs();
	if (ret)
		goto err_init_drv;

#if IST30XX_DEBUG
	ret = ist30xx_init_misc_sysfs();
	if (ret)
		goto err_init_drv;
#endif

# if IST30XX_FACTORY_TEST
	ret = ist30xx_init_factory_sysfs();
	if (ret)
		goto err_init_drv;
#endif

#if IST30XX_TRACKING_MODE
	ret = ist30xx_init_tracking_sysfs();
	if (ret)
		goto err_init_drv;
#endif

	ret = request_threaded_irq(client->irq, NULL, ist30xx_irq_thread,
				   IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "ist30xx_ts", data);
	if (ret)
		goto err_irq;

	ist30xx_disable_irq(data);

#if IST30XX_INTERNAL_BIN
# if IST30XX_UPDATE_BY_WORKQUEUE
	INIT_DELAYED_WORK(&work_fw_update, fw_update_func);
	schedule_delayed_work(&work_fw_update, IST30XX_UPDATE_DELAY);
# else
	ret = ist30xx_auto_bin_update(data);
	if (ret < 0)
		goto err_irq;
# endif
#endif  // IST30XX_INTERNAL_BIN

	ret = ist30xx_get_info(data);
	tsp_info("Get info: %s\n", (ret == 0 ? "success" : "fail"));

	INIT_DELAYED_WORK(&work_reset_check, reset_work_func);

#if IRQ_THREAD_WORK_QUEUE
	INIT_WORK(&work_irq_thread, irq_thread_func);
#endif

#if IST30XX_DETECT_TA
	ist30xx_ta_status = 0;
#endif

#if IST30XX_EVENT_MODE
	init_timer(&idle_timer);
	idle_timer.function = timer_handler;
	idle_timer.expires = jiffies_64 + (EVENT_TIMER_INTERVAL);

	mod_timer(&idle_timer, get_jiffies_64() + EVENT_TIMER_INTERVAL);

	ktime_get_ts(&t_event);
#endif

	ist30xx_initialized = 1;

	return 0;

err_irq:
	ist30xx_disable_irq(data);
	free_irq(client->irq, data);
err_init_drv:
#if IST30XX_EVENT_MODE
	get_event_mode = false;
#endif
	tsp_err("Error, ist30xx init driver\n");
//	ist30xx_power_off();
	ist30xx_ts_off();
	input_unregister_device(input_dev);
	return 0;

err_reg_dev:
err_alloc_dev:
	tsp_err("Error, ist30xx mem free\n");
	kfree(data);
err_check_functionality_failed:
	return 0;
}
コード例 #12
0
ssize_t ist30xx_cmcs_test_all_show(struct device *dev, struct device_attribute *attr,
				char *buf)
{
	int ret;
	char* msg = NULL;

	struct ist30xx_data *data = dev_get_drvdata(dev);
	CMCS_INFO *cmcs = (CMCS_INFO *)&data->cmcs->cmcs;

	msg = kzalloc(sizeof(char) * 4096, GFP_KERNEL);
	if (!msg) {
		tsp_err("Memory allocation failed\n");
		return 0;
	}

	/* CMCS Binary */
	ret = ist30xx_load_cmcs_binary(data);
	if (unlikely(ret)) {
		kfree(msg);
		return sprintf(buf, "Binary loaded failed(%d).\n", data->cmcs_bin_size);
	}

	ist30xx_get_cmcs_info(data, data->cmcs_bin, data->cmcs_bin_size);

	mutex_lock(&data->ist30xx_mutex);
	ret = ist30xx_cmcs_test(data, data->cmcs_bin, data->cmcs_bin_size);
	mutex_unlock(&data->ist30xx_mutex);

	if (likely(data->cmcs_bin != NULL)) {
		kfree(data->cmcs_bin);
		data->cmcs_bin = NULL;
		data->cmcs_bin_size = 0;
	}

	if (ret == 0) {
		tsp_debug("CMCS Binary test passed!\n");
	} else {
		kfree(msg);
		return sprintf(buf, "CMCS Binary test failed!\n");
	}

	if ((cmcs->cmd.mode) && !(cmcs->cmd.mode & FLAG_ENABLE_CM) && !(cmcs->cmd.mode & FLAG_ENABLE_CS)) {
		kfree(msg);
		return sprintf(buf, "CMCS not enabled!\n");
	}

	/* CM Result */
	memset(msg, 0, sizeof(msg));
	ret = print_cm_result(data, msg);
	if (strncmp(msg, "OK\n", strlen("OK\n")) == 0) {
		tsp_debug("CM result test passed!\n");
	} else {
		goto out;
	}

	/* CM Slope 0 Result */
	memset(msg, 0, sizeof(msg));
	ret = print_cm_slope_result(data, CMCS_FLAG_CM_SLOPE0,
				data->cmcs_buf->slope0, msg);
	if (strncmp(msg, "OK\n", strlen("OK\n")) == 0) {
		tsp_debug("CM Slope 0 test passed!\n");
	} else {
		goto out;
	}

	/* CM Slope 1 Result */
	memset(msg, 0, sizeof(msg));
	ret = print_cm_slope_result(data, CMCS_FLAG_CM_SLOPE1,
				data->cmcs_buf->slope1, msg);
	if (strncmp(msg, "OK\n", strlen("OK\n")) == 0) {
		tsp_debug("CM Slope 1 test passed!\n");
	} else {
		goto out;
	}

	/* CS 0 Result */
	memset(msg, 0, sizeof(msg));
	ret = print_cs_result(data, data->cmcs_buf->cs0, msg, 0);
	if (strncmp(msg, "OK\n", strlen("OK\n")) == 0) {
		tsp_debug("CS 0 test passed!\n");
	} else {
		goto out;
	}

	/* CS 1 Result */
	memset(msg, 0, sizeof(msg));
	ret = print_cs_result(data, data->cmcs_buf->cs1, msg, 1);
	if (strncmp(msg, "OK\n", strlen("OK\n")) == 0) {
		tsp_debug("CS 1 test passed!\n");
	} else {
		goto out;
	}

out:
	ret = sprintf(buf, "%s", msg);
	kfree(msg);
	return ret;
}