| /* Copyright (c) 2011, Code Aurora Forum. All rights reserved. | 
 |  * | 
 |  * This program is free software; you can redistribute it and/or modify | 
 |  * it under the terms of the GNU General Public License version 2 and | 
 |  * only version 2 as published by the Free Software Foundation. | 
 |  * | 
 |  * 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. | 
 |  */ | 
 |  | 
 | #include <linux/slab.h> | 
 | #include <linux/sched.h> | 
 | #include <linux/wait.h> | 
 | #include <linux/workqueue.h> | 
 | #include <linux/platform_device.h> | 
 | #include <linux/tty.h> | 
 | #include <linux/tty_flip.h> | 
 | #include <linux/module.h> | 
 | #include <linux/debugfs.h> | 
 | #include <mach/sdio_al.h> | 
 |  | 
 | #define INPUT_SPEED			4800 | 
 | #define OUTPUT_SPEED			4800 | 
 | #define SDIO_TTY_MODULE_NAME		"sdio_tty" | 
 | #define SDIO_TTY_MAX_PACKET_SIZE	4096 | 
 | #define MAX_SDIO_TTY_DRV		1 | 
 | #define MAX_SDIO_TTY_DEVS		2 | 
 | #define MAX_SDIO_TTY_DEV_NAME_SIZE	25 | 
 |  | 
 | /* Configurations per channel device */ | 
 | /* CSVT */ | 
 | #define SDIO_TTY_CSVT_DEV		"sdio_tty_csvt_0" | 
 | #define SDIO_TTY_CSVT_TEST_DEV		"sdio_tty_csvt_test_0" | 
 | #define SDIO_TTY_CH_CSVT		"SDIO_CSVT" | 
 |  | 
 | enum sdio_tty_state { | 
 | 	TTY_INITIAL = 0, | 
 | 	TTY_REGISTERED = 1, | 
 | 	TTY_OPENED = 2, | 
 | 	TTY_CLOSED = 3, | 
 | }; | 
 |  | 
 | enum sdio_tty_devices { | 
 | 	SDIO_CSVT, | 
 | 	SDIO_CSVT_TEST_APP, | 
 | }; | 
 |  | 
 | static const struct platform_device_id sdio_tty_id_table[] = { | 
 | 	{ "SDIO_CSVT",		SDIO_CSVT }, | 
 | 	{ "SDIO_CSVT_TEST_APP",	SDIO_CSVT_TEST_APP }, | 
 | 	{ }, | 
 | }; | 
 | MODULE_DEVICE_TABLE(platform, sdio_tty_id_table); | 
 |  | 
 | struct sdio_tty { | 
 | 	struct sdio_channel *ch; | 
 | 	char *sdio_ch_name; | 
 | 	char tty_dev_name[MAX_SDIO_TTY_DEV_NAME_SIZE]; | 
 | 	int device_id; | 
 | 	struct workqueue_struct *workq; | 
 | 	struct work_struct work_read; | 
 | 	wait_queue_head_t   waitq; | 
 | 	struct tty_driver *tty_drv; | 
 | 	struct tty_struct *tty_str; | 
 | 	int debug_msg_on; | 
 | 	char *read_buf; | 
 | 	enum sdio_tty_state sdio_tty_state; | 
 | 	int is_sdio_open; | 
 | 	int tty_open_count; | 
 | 	int total_rx; | 
 | 	int total_tx; | 
 | }; | 
 |  | 
 | static struct sdio_tty *sdio_tty[MAX_SDIO_TTY_DEVS]; | 
 |  | 
 | #ifdef CONFIG_DEBUG_FS | 
 | struct dentry *sdio_tty_debug_root; | 
 | struct dentry *sdio_tty_debug_info; | 
 | #endif | 
 |  | 
 | #define DEBUG_MSG(sdio_tty_drv, x...) if (sdio_tty_drv->debug_msg_on) pr_info(x) | 
 |  | 
 | /* | 
 |  * Enable sdio_tty debug messages | 
 |  * By default the sdio_tty debug messages are turned off | 
 |  */ | 
 | static int csvt_debug_msg_on; | 
 | module_param(csvt_debug_msg_on, int, 0); | 
 |  | 
 | static void sdio_tty_read(struct work_struct *work) | 
 | { | 
 | 	int ret = 0; | 
 | 	int read_avail = 0; | 
 | 	int left = 0; | 
 | 	int total_push = 0; | 
 | 	int num_push = 0; | 
 | 	struct sdio_tty *sdio_tty_drv = NULL; | 
 |  | 
 | 	sdio_tty_drv = container_of(work, struct sdio_tty, work_read); | 
 |  | 
 | 	if (!sdio_tty_drv) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty", __func__); | 
 | 		return ; | 
 | 	} | 
 |  | 
 | 	if (sdio_tty_drv->sdio_tty_state != TTY_OPENED) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_tty_state = %d", | 
 | 			__func__, sdio_tty_drv->sdio_tty_state); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	if (!sdio_tty_drv->read_buf) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL read_buf for dev %s", | 
 | 			__func__, sdio_tty_drv->tty_dev_name); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	/* Read the data from the SDIO channel as long as there is available | 
 | 	   data */ | 
 | 	while (1) { | 
 | 		if (test_bit(TTY_THROTTLED, &sdio_tty_drv->tty_str->flags)) { | 
 | 			DEBUG_MSG(sdio_tty_drv, SDIO_TTY_MODULE_NAME | 
 | 					": %s: TTY_THROTTLED bit is set for " | 
 | 					"dev %s, exit", __func__, | 
 | 					sdio_tty_drv->tty_dev_name); | 
 | 			return; | 
 | 		} | 
 |  | 
 | 		total_push = 0; | 
 | 		read_avail = sdio_read_avail(sdio_tty_drv->ch); | 
 |  | 
 | 		DEBUG_MSG(sdio_tty_drv, SDIO_TTY_MODULE_NAME | 
 | 				": %s: read_avail is %d for dev %s", __func__, | 
 | 				read_avail, sdio_tty_drv->tty_dev_name); | 
 |  | 
 | 		if (read_avail == 0) { | 
 | 			DEBUG_MSG(sdio_tty_drv, SDIO_TTY_MODULE_NAME | 
 | 					": %s: read_avail is 0 for dev %s", | 
 | 					__func__, sdio_tty_drv->tty_dev_name); | 
 | 			return; | 
 | 		} | 
 |  | 
 | 		if (read_avail > SDIO_TTY_MAX_PACKET_SIZE) { | 
 | 			pr_err(SDIO_TTY_MODULE_NAME ": %s: read_avail(%d) is " | 
 | 				"bigger than SDIO_TTY_MAX_PACKET_SIZE(%d) " | 
 | 				"for dev %s", __func__, read_avail, | 
 | 				SDIO_TTY_MAX_PACKET_SIZE, | 
 | 				sdio_tty_drv->tty_dev_name); | 
 | 			return; | 
 | 		} | 
 |  | 
 | 		ret = sdio_read(sdio_tty_drv->ch, | 
 | 				sdio_tty_drv->read_buf, | 
 | 				read_avail); | 
 | 		if (ret < 0) { | 
 | 			pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_read error(%d) " | 
 | 				"for dev %s", __func__, ret, | 
 | 				sdio_tty_drv->tty_dev_name); | 
 | 			return; | 
 | 		} | 
 |  | 
 | 		left = read_avail; | 
 | 		do { | 
 | 			num_push = tty_insert_flip_string( | 
 | 				sdio_tty_drv->tty_str, | 
 | 				sdio_tty_drv->read_buf+total_push, | 
 | 				left); | 
 | 			total_push += num_push; | 
 | 			left -= num_push; | 
 | 			tty_flip_buffer_push(sdio_tty_drv->tty_str); | 
 | 		} while (left != 0); | 
 |  | 
 | 		if (total_push != read_avail) { | 
 | 			pr_err(SDIO_TTY_MODULE_NAME ": %s: failed, total_push" | 
 | 				"(%d) != read_avail(%d) for dev %s\n", | 
 | 				__func__, total_push, read_avail, | 
 | 				sdio_tty_drv->tty_dev_name); | 
 | 		} | 
 |  | 
 | 		tty_flip_buffer_push(sdio_tty_drv->tty_str); | 
 | 		sdio_tty_drv->total_rx += read_avail; | 
 |  | 
 | 		DEBUG_MSG(sdio_tty_drv, SDIO_TTY_MODULE_NAME ": %s: Rx: %d, " | 
 | 				"Total Rx = %d bytes for dev %s", __func__, | 
 | 				read_avail, sdio_tty_drv->total_rx, | 
 | 				sdio_tty_drv->tty_dev_name); | 
 | 	} | 
 | } | 
 |  | 
 | /** | 
 |   * sdio_tty_write_room | 
 |   * | 
 |   * This is the write_room function of the tty driver. | 
 |   * | 
 |   * @tty: pointer to tty struct. | 
 |   * @return free bytes for write. | 
 |   * | 
 |   */ | 
 | static int sdio_tty_write_room(struct tty_struct *tty) | 
 | { | 
 | 	int write_avail = 0; | 
 | 	struct sdio_tty *sdio_tty_drv = NULL; | 
 |  | 
 | 	if (!tty) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL tty", __func__); | 
 | 		return -ENODEV; | 
 | 	} | 
 | 	sdio_tty_drv = tty->driver_data; | 
 | 	if (!sdio_tty_drv) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty_drv", | 
 | 			__func__); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	if (sdio_tty_drv->sdio_tty_state != TTY_OPENED) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_tty_state = %d", | 
 | 			__func__, sdio_tty_drv->sdio_tty_state); | 
 | 		return -EPERM; | 
 | 	} | 
 |  | 
 | 	write_avail = sdio_write_avail(sdio_tty_drv->ch); | 
 | 	DEBUG_MSG(sdio_tty_drv, SDIO_TTY_MODULE_NAME ": %s: write_avail=%d " | 
 | 			"for dev %s", __func__, write_avail, | 
 | 			sdio_tty_drv->tty_dev_name); | 
 |  | 
 | 	return write_avail; | 
 | } | 
 |  | 
 | /** | 
 |   * sdio_tty_write_callback | 
 |   * this is the write callback of the tty driver. | 
 |   * | 
 |   * @tty: pointer to tty struct. | 
 |   * @buf: buffer to write from. | 
 |   * @count: number of bytes to write. | 
 |   * @return bytes written or negative value on error. | 
 |   * | 
 |   * if destination buffer has not enough room for the incoming | 
 |   * data, writes the possible amount of bytes . | 
 |   */ | 
 | static int sdio_tty_write_callback(struct tty_struct *tty, | 
 | 				   const unsigned char *buf, int count) | 
 | { | 
 | 	int write_avail = 0; | 
 | 	int len = count; | 
 | 	int ret = 0; | 
 | 	struct sdio_tty *sdio_tty_drv = NULL; | 
 |  | 
 | 	if (!tty) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL tty", __func__); | 
 | 		return -ENODEV; | 
 | 	} | 
 | 	sdio_tty_drv = tty->driver_data; | 
 | 	if (!sdio_tty_drv) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty_drv", | 
 | 			__func__); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	if (sdio_tty_drv->sdio_tty_state != TTY_OPENED) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_tty_state = %d", | 
 | 			__func__, sdio_tty_drv->sdio_tty_state); | 
 | 		return -EPERM; | 
 | 	} | 
 |  | 
 | 	DEBUG_MSG(sdio_tty_drv, SDIO_TTY_MODULE_NAME ": %s: Write Callback " | 
 | 			"called with %d bytes for dev %s\n", __func__, count, | 
 | 			sdio_tty_drv->tty_dev_name); | 
 | 	write_avail = sdio_write_avail(sdio_tty_drv->ch); | 
 | 	if (write_avail == 0) { | 
 | 		DEBUG_MSG(sdio_tty_drv, SDIO_TTY_MODULE_NAME ": %s: " | 
 | 				"write_avail is 0 for dev %s\n", | 
 | 				__func__, sdio_tty_drv->tty_dev_name); | 
 | 		return 0; | 
 | 	} | 
 | 	if (write_avail > SDIO_TTY_MAX_PACKET_SIZE) { | 
 | 		DEBUG_MSG(sdio_tty_drv, SDIO_TTY_MODULE_NAME ": %s: " | 
 | 				"write_avail(%d) is bigger than max packet " | 
 | 				"size(%d) for dev %s, setting to " | 
 | 				"max_packet_size\n", __func__, write_avail, | 
 | 				SDIO_TTY_MAX_PACKET_SIZE, | 
 | 				sdio_tty_drv->tty_dev_name); | 
 | 		write_avail = SDIO_TTY_MAX_PACKET_SIZE; | 
 | 	} | 
 | 	if (write_avail < count) { | 
 | 		DEBUG_MSG(sdio_tty_drv, SDIO_TTY_MODULE_NAME ": %s: " | 
 | 				"write_avail(%d) is smaller than required(%d) " | 
 | 				"for dev %s, writing only %d bytes\n", | 
 | 				__func__, write_avail, count, | 
 | 				sdio_tty_drv->tty_dev_name, write_avail); | 
 | 		len = write_avail; | 
 | 	} | 
 | 	ret = sdio_write(sdio_tty_drv->ch, buf, len); | 
 | 	if (ret) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_write failed for " | 
 | 			"dev %s, ret=%d\n", __func__, | 
 | 			sdio_tty_drv->tty_dev_name, ret); | 
 | 		return 0; | 
 | 	} | 
 |  | 
 | 	sdio_tty_drv->total_tx += len; | 
 |  | 
 | 	DEBUG_MSG(sdio_tty_drv, SDIO_TTY_MODULE_NAME ": %s: Tx: %d, " | 
 | 			"Total Tx = %d for dev %s", __func__, len, | 
 | 			sdio_tty_drv->total_tx, sdio_tty_drv->tty_dev_name); | 
 | 	return len; | 
 | } | 
 |  | 
 | static void sdio_tty_notify(void *priv, unsigned event) | 
 | { | 
 | 	struct sdio_tty *sdio_tty_drv = priv; | 
 |  | 
 | 	if (!sdio_tty_drv) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty_drv", | 
 | 			__func__); | 
 | 	} | 
 |  | 
 | 	if (sdio_tty_drv->sdio_tty_state != TTY_OPENED) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_tty_state = %d", | 
 | 			__func__, sdio_tty_drv->sdio_tty_state); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	DEBUG_MSG(sdio_tty_drv, SDIO_TTY_MODULE_NAME ": %s: event %d " | 
 | 			"received for dev %s\n", __func__, event, | 
 | 			sdio_tty_drv->tty_dev_name); | 
 |  | 
 | 	if (event == SDIO_EVENT_DATA_READ_AVAIL) | 
 | 		queue_work(sdio_tty_drv->workq, &sdio_tty_drv->work_read); | 
 | } | 
 |  | 
 | /** | 
 |   * sdio_tty_open | 
 |   * This is the open callback of the tty driver. it opens | 
 |   * the sdio channel, and creates the workqueue. | 
 |   * | 
 |   * @tty: a pointer to the tty struct. | 
 |   * @file: file descriptor. | 
 |   * @return 0 on success or negative value on error. | 
 |   */ | 
 | static int sdio_tty_open(struct tty_struct *tty, struct file *file) | 
 | { | 
 | 	int ret = 0; | 
 | 	int i = 0; | 
 | 	struct sdio_tty *sdio_tty_drv = NULL; | 
 |  | 
 | 	if (!tty) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL tty", __func__); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	for (i = 0; i < MAX_SDIO_TTY_DEVS; i++) { | 
 | 		if (sdio_tty[i] == NULL) | 
 | 			continue; | 
 | 		if (!strncmp(sdio_tty[i]->tty_dev_name, tty->name, | 
 | 				MAX_SDIO_TTY_DEV_NAME_SIZE)) { | 
 | 			sdio_tty_drv = sdio_tty[i]; | 
 | 			break; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (!sdio_tty_drv) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty_drv", | 
 | 		       __func__); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	sdio_tty_drv->tty_open_count++; | 
 | 	if (sdio_tty_drv->sdio_tty_state == TTY_OPENED) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: tty dev(%s) is already open", | 
 | 			__func__, sdio_tty_drv->tty_dev_name); | 
 | 		return -EBUSY; | 
 | 	} | 
 |  | 
 | 	tty->driver_data = sdio_tty_drv; | 
 |  | 
 | 	sdio_tty_drv->tty_str = tty; | 
 | 	sdio_tty_drv->tty_str->low_latency = 1; | 
 | 	sdio_tty_drv->tty_str->icanon = 0; | 
 | 	set_bit(TTY_NO_WRITE_SPLIT, &sdio_tty_drv->tty_str->flags); | 
 |  | 
 | 	sdio_tty_drv->read_buf = kzalloc(SDIO_TTY_MAX_PACKET_SIZE, GFP_KERNEL); | 
 | 	if (sdio_tty_drv->read_buf == NULL) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: failed to allocate read_buf " | 
 | 			"for dev %s", __func__, sdio_tty_drv->tty_dev_name); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	sdio_tty_drv->workq = create_singlethread_workqueue("sdio_tty_read"); | 
 | 	if (!sdio_tty_drv->workq) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: failed to create workq " | 
 | 			"for dev %s", __func__, sdio_tty_drv->tty_dev_name); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	if (!sdio_tty_drv->is_sdio_open) { | 
 | 		ret = sdio_open(sdio_tty_drv->sdio_ch_name, &sdio_tty_drv->ch, | 
 | 				sdio_tty_drv, sdio_tty_notify); | 
 | 		if (ret < 0) { | 
 | 			pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_open err=%d " | 
 | 				"for dev %s\n", __func__, ret, | 
 | 				sdio_tty_drv->tty_dev_name); | 
 | 			destroy_workqueue(sdio_tty_drv->workq); | 
 | 			return ret; | 
 | 		} | 
 |  | 
 | 		pr_info(SDIO_TTY_MODULE_NAME ": %s: SDIO_TTY channel(%s) " | 
 | 			"opened\n", __func__, sdio_tty_drv->sdio_ch_name); | 
 |  | 
 | 		sdio_tty_drv->is_sdio_open = 1; | 
 | 	} else { | 
 | 		/* If SDIO channel is already open try to read the data | 
 | 		 * from the modem | 
 | 		 */ | 
 | 		queue_work(sdio_tty_drv->workq, &sdio_tty_drv->work_read); | 
 |  | 
 | 	} | 
 |  | 
 | 	sdio_tty_drv->sdio_tty_state = TTY_OPENED; | 
 |  | 
 | 	pr_info(SDIO_TTY_MODULE_NAME ": %s: TTY device(%s) opened\n", | 
 | 		__func__, sdio_tty_drv->tty_dev_name); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | /** | 
 |   * sdio_tty_close | 
 |   * This is the close callback of the tty driver. it requests | 
 |   * the main thread to exit, and waits for notification of it. | 
 |   * it also de-allocates the buffers, and unregisters the tty | 
 |   * driver and device. | 
 |   * | 
 |   * @tty: a pointer to the tty struct. | 
 |   * @file: file descriptor. | 
 |   * @return None. | 
 |   */ | 
 | static void sdio_tty_close(struct tty_struct *tty, struct file *file) | 
 | { | 
 | 	struct sdio_tty *sdio_tty_drv = NULL; | 
 |  | 
 | 	if (!tty) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL tty", __func__); | 
 | 		return; | 
 | 	} | 
 | 	sdio_tty_drv = tty->driver_data; | 
 | 	if (!sdio_tty_drv) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty_drv", | 
 | 		       __func__); | 
 | 		return; | 
 | 	} | 
 | 	if (sdio_tty_drv->sdio_tty_state != TTY_OPENED) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: trying to close a " | 
 | 			"TTY device that was not opened\n", __func__); | 
 | 		return; | 
 | 	} | 
 | 	if (--sdio_tty_drv->tty_open_count != 0) | 
 | 		return; | 
 |  | 
 | 	flush_workqueue(sdio_tty_drv->workq); | 
 | 	destroy_workqueue(sdio_tty_drv->workq); | 
 |  | 
 | 	kfree(sdio_tty_drv->read_buf); | 
 | 	sdio_tty_drv->read_buf = NULL; | 
 |  | 
 | 	sdio_tty_drv->sdio_tty_state = TTY_CLOSED; | 
 |  | 
 | 	pr_info(SDIO_TTY_MODULE_NAME ": %s: SDIO_TTY device(%s) closed\n", | 
 | 		__func__, sdio_tty_drv->tty_dev_name); | 
 | } | 
 |  | 
 | static void sdio_tty_unthrottle(struct tty_struct *tty) | 
 | { | 
 | 	struct sdio_tty *sdio_tty_drv = NULL; | 
 |  | 
 | 	if (!tty) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL tty", __func__); | 
 | 		return; | 
 | 	} | 
 | 	sdio_tty_drv = tty->driver_data; | 
 | 	if (!sdio_tty_drv) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty_drv", | 
 | 		       __func__); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	if (sdio_tty_drv->sdio_tty_state != TTY_OPENED) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_tty_state = %d", | 
 | 		       __func__, sdio_tty_drv->sdio_tty_state); | 
 | 		return; | 
 | 	} | 
 |  | 
 | 	queue_work(sdio_tty_drv->workq, &sdio_tty_drv->work_read); | 
 | 	return; | 
 | } | 
 |  | 
 | static const struct tty_operations sdio_tty_ops = { | 
 | 	.open = sdio_tty_open, | 
 | 	.close = sdio_tty_close, | 
 | 	.write = sdio_tty_write_callback, | 
 | 	.write_room = sdio_tty_write_room, | 
 | 	.unthrottle = sdio_tty_unthrottle, | 
 | }; | 
 |  | 
 | int sdio_tty_init_tty(char *tty_name, char *sdio_ch_name, | 
 | 			enum sdio_tty_devices device_id, int debug_msg_on) | 
 | { | 
 | 	int ret = 0; | 
 | 	int i = 0; | 
 | 	struct device *tty_dev = NULL; | 
 | 	struct sdio_tty *sdio_tty_drv = NULL; | 
 |  | 
 | 	sdio_tty_drv = kzalloc(sizeof(struct sdio_tty), GFP_KERNEL); | 
 | 	if (sdio_tty_drv == NULL) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: failed to allocate sdio_tty " | 
 | 			"for dev %s", __func__, tty_name); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	for (i = 0; i < MAX_SDIO_TTY_DEVS; i++) { | 
 | 		if (sdio_tty[i] == NULL) { | 
 | 			sdio_tty[i] = sdio_tty_drv; | 
 | 			break; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (i == MAX_SDIO_TTY_DEVS) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: tty dev(%s) creation failed," | 
 | 			" max limit(%d) reached.", __func__, tty_name, | 
 | 			MAX_SDIO_TTY_DEVS); | 
 | 		kfree(sdio_tty_drv); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	snprintf(sdio_tty_drv->tty_dev_name, MAX_SDIO_TTY_DEV_NAME_SIZE, | 
 | 			"%s%d", tty_name, 0); | 
 | 	sdio_tty_drv->sdio_ch_name = sdio_ch_name; | 
 | 	sdio_tty_drv->device_id = device_id; | 
 | 	pr_info(SDIO_TTY_MODULE_NAME ": %s: dev=%s, id=%d, channel=%s\n", | 
 | 		__func__, sdio_tty_drv->tty_dev_name, sdio_tty_drv->device_id, | 
 | 		sdio_tty_drv->sdio_ch_name); | 
 |  | 
 | 	INIT_WORK(&sdio_tty_drv->work_read, sdio_tty_read); | 
 |  | 
 | 	sdio_tty_drv->tty_drv = alloc_tty_driver(MAX_SDIO_TTY_DRV); | 
 |  | 
 | 	if (!sdio_tty_drv->tty_drv) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s - tty_drv is NULL for dev %s", | 
 | 			__func__, sdio_tty_drv->tty_dev_name); | 
 | 		kfree(sdio_tty_drv); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	sdio_tty_drv->tty_drv->name = tty_name; | 
 | 	sdio_tty_drv->tty_drv->owner = THIS_MODULE; | 
 | 	sdio_tty_drv->tty_drv->driver_name = "SDIO_tty"; | 
 | 	/* uses dynamically assigned dev_t values */ | 
 | 	sdio_tty_drv->tty_drv->type = TTY_DRIVER_TYPE_SERIAL; | 
 | 	sdio_tty_drv->tty_drv->subtype = SERIAL_TYPE_NORMAL; | 
 | 	sdio_tty_drv->tty_drv->flags = TTY_DRIVER_REAL_RAW | 
 | 		| TTY_DRIVER_DYNAMIC_DEV | 
 | 		| TTY_DRIVER_RESET_TERMIOS; | 
 |  | 
 | 	/* initializing the tty driver */ | 
 | 	sdio_tty_drv->tty_drv->init_termios = tty_std_termios; | 
 | 	sdio_tty_drv->tty_drv->init_termios.c_cflag = | 
 | 		B4800 | CS8 | CREAD | HUPCL | CLOCAL; | 
 | 	sdio_tty_drv->tty_drv->init_termios.c_ispeed = INPUT_SPEED; | 
 | 	sdio_tty_drv->tty_drv->init_termios.c_ospeed = OUTPUT_SPEED; | 
 |  | 
 | 	tty_set_operations(sdio_tty_drv->tty_drv, &sdio_tty_ops); | 
 |  | 
 | 	ret = tty_register_driver(sdio_tty_drv->tty_drv); | 
 | 	if (ret) { | 
 | 		put_tty_driver(sdio_tty_drv->tty_drv); | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: tty_register_driver() " | 
 | 			"failed for dev %s\n", __func__, | 
 | 			sdio_tty_drv->tty_dev_name); | 
 |  | 
 | 		sdio_tty_drv->tty_drv = NULL; | 
 | 		kfree(sdio_tty_drv); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	tty_dev = tty_register_device(sdio_tty_drv->tty_drv, 0, NULL); | 
 | 	if (IS_ERR(tty_dev)) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: tty_register_device() " | 
 | 			"failed for dev %s\n", __func__, | 
 | 			sdio_tty_drv->tty_dev_name); | 
 | 		tty_unregister_driver(sdio_tty_drv->tty_drv); | 
 | 		put_tty_driver(sdio_tty_drv->tty_drv); | 
 | 		kfree(sdio_tty_drv); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	sdio_tty_drv->sdio_tty_state = TTY_REGISTERED; | 
 | 	if (debug_msg_on) { | 
 | 		pr_info(SDIO_TTY_MODULE_NAME ": %s: turn on debug msg for %s", | 
 | 			__func__, sdio_tty_drv->tty_dev_name); | 
 | 		sdio_tty_drv->debug_msg_on = debug_msg_on; | 
 | 	} | 
 | 	return 0; | 
 | } | 
 |  | 
 | int sdio_tty_uninit_tty(void *sdio_tty_handle) | 
 | { | 
 | 	int ret = 0; | 
 | 	int i = 0; | 
 | 	struct sdio_tty *sdio_tty_drv = sdio_tty_handle; | 
 |  | 
 | 	if (!sdio_tty_drv) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty_drv", | 
 | 		       __func__); | 
 | 		return -ENODEV; | 
 | 	} | 
 | 	if (sdio_tty_drv->sdio_tty_state == TTY_OPENED) { | 
 | 		flush_workqueue(sdio_tty_drv->workq); | 
 | 		destroy_workqueue(sdio_tty_drv->workq); | 
 |  | 
 | 		kfree(sdio_tty_drv->read_buf); | 
 | 		sdio_tty_drv->read_buf = NULL; | 
 | 	} | 
 |  | 
 | 	if (sdio_tty_drv->sdio_tty_state != TTY_INITIAL) { | 
 | 		tty_unregister_device(sdio_tty_drv->tty_drv, 0); | 
 |  | 
 | 		ret = tty_unregister_driver(sdio_tty_drv->tty_drv); | 
 | 		if (ret) { | 
 | 			pr_err(SDIO_TTY_MODULE_NAME ": %s: " | 
 | 				"tty_unregister_driver() failed for dev %s\n", | 
 | 				__func__, sdio_tty_drv->tty_dev_name); | 
 | 		} | 
 | 		put_tty_driver(sdio_tty_drv->tty_drv); | 
 | 		sdio_tty_drv->sdio_tty_state = TTY_INITIAL; | 
 | 		sdio_tty_drv->tty_drv = NULL; | 
 | 	} | 
 |  | 
 | 	for (i = 0; i < MAX_SDIO_TTY_DEVS; i++) { | 
 | 		if (sdio_tty[i] == NULL) | 
 | 			continue; | 
 | 		if (sdio_tty[i]->device_id == sdio_tty_drv->device_id) { | 
 | 			sdio_tty[i] = NULL; | 
 | 			break; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	DEBUG_MSG(sdio_tty_drv, SDIO_TTY_MODULE_NAME ": %s: Freeing sdio_tty " | 
 | 			"structure, dev=%s", __func__, | 
 | 			sdio_tty_drv->tty_dev_name); | 
 | 	kfree(sdio_tty_drv); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int sdio_tty_probe(struct platform_device *pdev) | 
 | { | 
 | 	const struct platform_device_id *id = platform_get_device_id(pdev); | 
 | 	enum sdio_tty_devices device_id = id->driver_data; | 
 | 	char *device_name = NULL; | 
 | 	char *channel_name = NULL; | 
 | 	int debug_msg_on = 0; | 
 | 	int ret = 0; | 
 |  | 
 | 	pr_debug(SDIO_TTY_MODULE_NAME ": %s for %s", __func__, pdev->name); | 
 |  | 
 | 	switch (device_id) { | 
 | 	case SDIO_CSVT: | 
 | 		device_name = SDIO_TTY_CSVT_DEV; | 
 | 		channel_name = SDIO_TTY_CH_CSVT; | 
 | 		debug_msg_on = csvt_debug_msg_on; | 
 | 		break; | 
 | 	case SDIO_CSVT_TEST_APP: | 
 | 		device_name = SDIO_TTY_CSVT_TEST_DEV; | 
 | 		channel_name = SDIO_TTY_CH_CSVT; | 
 | 		debug_msg_on = csvt_debug_msg_on; | 
 | 		break; | 
 | 	default: | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s Invalid device:%s, id:%d", | 
 | 			__func__, pdev->name, device_id); | 
 | 		ret = -ENODEV; | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	if (device_name) { | 
 | 		ret = sdio_tty_init_tty(device_name, channel_name, | 
 | 					device_id, debug_msg_on); | 
 | 		if (ret) { | 
 | 			pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_tty_init_tty " | 
 | 				"failed for dev:%s", __func__, device_name); | 
 | 		} | 
 | 	} | 
 | 	return ret; | 
 | } | 
 |  | 
 | static int sdio_tty_remove(struct platform_device *pdev) | 
 | { | 
 | 	const struct platform_device_id *id = platform_get_device_id(pdev); | 
 | 	enum sdio_tty_devices device_id = id->driver_data; | 
 | 	struct sdio_tty *sdio_tty_drv = NULL; | 
 | 	int i = 0; | 
 | 	int ret = 0; | 
 |  | 
 | 	pr_debug(SDIO_TTY_MODULE_NAME ": %s for %s", __func__, pdev->name); | 
 |  | 
 | 	for (i = 0; i < MAX_SDIO_TTY_DEVS; i++) { | 
 | 		if (sdio_tty[i] == NULL) | 
 | 			continue; | 
 | 		if (sdio_tty[i]->device_id == device_id) { | 
 | 			sdio_tty_drv = sdio_tty[i]; | 
 | 			break; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	if (!sdio_tty_drv) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: NULL sdio_tty_drv", | 
 | 		       __func__); | 
 | 		return -ENODEV; | 
 | 	} | 
 |  | 
 | 	ret = sdio_tty_uninit_tty(sdio_tty_drv); | 
 | 	if (ret) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: sdio_tty_uninit_tty " | 
 | 			"failed for %s", __func__, pdev->name); | 
 | 	} | 
 | 	return ret; | 
 | } | 
 |  | 
 | static struct platform_driver sdio_tty_pdrv = { | 
 | 	.probe		= sdio_tty_probe, | 
 | 	.remove		= sdio_tty_remove, | 
 | 	.id_table	= sdio_tty_id_table, | 
 | 	.driver		= { | 
 | 		.name	= "SDIO_TTY", | 
 | 		.owner	= THIS_MODULE, | 
 | 	}, | 
 | }; | 
 |  | 
 | #ifdef CONFIG_DEBUG_FS | 
 | void sdio_tty_print_info(void) | 
 | { | 
 | 	int i = 0; | 
 |  | 
 | 	for (i = 0; i < MAX_SDIO_TTY_DEVS; i++) { | 
 | 		if (sdio_tty[i] == NULL) | 
 | 			continue; | 
 | 		pr_info(SDIO_TTY_MODULE_NAME ": %s: Total Rx=%d, Tx = %d " | 
 | 			"for dev %s", __func__, sdio_tty[i]->total_rx, | 
 | 			sdio_tty[i]->total_tx, sdio_tty[i]->tty_dev_name); | 
 | 	} | 
 | } | 
 |  | 
 | static int tty_debug_info_open(struct inode *inode, struct file *file) | 
 | { | 
 | 	file->private_data = inode->i_private; | 
 | 	return 0; | 
 | } | 
 |  | 
 | static ssize_t tty_debug_info_write(struct file *file, | 
 | 		const char __user *buf, size_t count, loff_t *ppos) | 
 | { | 
 | 	sdio_tty_print_info(); | 
 | 	return count; | 
 | } | 
 |  | 
 | const struct file_operations tty_debug_info_ops = { | 
 | 	.open = tty_debug_info_open, | 
 | 	.write = tty_debug_info_write, | 
 | }; | 
 | #endif | 
 |  | 
 | /* | 
 |  *  Module Init. | 
 |  * | 
 |  *  Register SDIO TTY driver. | 
 |  * | 
 |  */ | 
 | static int __init sdio_tty_init(void) | 
 | { | 
 | 	int ret = 0; | 
 |  | 
 | 	ret = platform_driver_register(&sdio_tty_pdrv); | 
 | 	if (ret) { | 
 | 		pr_err(SDIO_TTY_MODULE_NAME ": %s: platform_driver_register " | 
 | 					    "failed", __func__); | 
 | 	} | 
 | #ifdef CONFIG_DEBUG_FS | 
 | 	else { | 
 | 		sdio_tty_debug_root = debugfs_create_dir("sdio_tty", NULL); | 
 | 		if (sdio_tty_debug_root) { | 
 | 			sdio_tty_debug_info = debugfs_create_file( | 
 | 							"sdio_tty_debug", | 
 | 							S_IRUGO | S_IWUGO, | 
 | 							sdio_tty_debug_root, | 
 | 							NULL, | 
 | 							&tty_debug_info_ops); | 
 | 		} | 
 | 	} | 
 | #endif | 
 | 	return ret; | 
 | }; | 
 |  | 
 | /* | 
 |  *  Module Exit. | 
 |  * | 
 |  *  Unregister SDIO TTY driver. | 
 |  * | 
 |  */ | 
 | static void __exit sdio_tty_exit(void) | 
 | { | 
 | #ifdef CONFIG_DEBUG_FS | 
 | 	debugfs_remove(sdio_tty_debug_info); | 
 | 	debugfs_remove(sdio_tty_debug_root); | 
 | #endif | 
 | 	platform_driver_unregister(&sdio_tty_pdrv); | 
 | } | 
 |  | 
 | module_init(sdio_tty_init); | 
 | module_exit(sdio_tty_exit); | 
 |  | 
 | MODULE_DESCRIPTION("SDIO TTY"); | 
 | MODULE_LICENSE("GPL v2"); | 
 | MODULE_AUTHOR("Maya Erez <merez@codeaurora.org>"); |