| 
									
										
										
										
											2014-02-12 18:00:36 +08:00
										 |  |  | /* Driver for Realtek USB card reader
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright(c) 2009-2013 Realtek Semiconductor Corp. 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 | 
					
						
							|  |  |  |  * 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. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * You should have received a copy of the GNU General Public License along | 
					
						
							|  |  |  |  * with this program; if not, see <http://www.gnu.org/licenses/>.
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Author: | 
					
						
							|  |  |  |  *   Roger Tseng <rogerable@realtek.com> | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | #include <linux/module.h>
 | 
					
						
							|  |  |  | #include <linux/slab.h>
 | 
					
						
							|  |  |  | #include <linux/mutex.h>
 | 
					
						
							|  |  |  | #include <linux/usb.h>
 | 
					
						
							|  |  |  | #include <linux/platform_device.h>
 | 
					
						
							|  |  |  | #include <linux/mfd/core.h>
 | 
					
						
							|  |  |  | #include <linux/mfd/rtsx_usb.h>
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int polling_pipe = 1; | 
					
						
							|  |  |  | module_param(polling_pipe, int, S_IRUGO | S_IWUSR); | 
					
						
							|  |  |  | MODULE_PARM_DESC(polling_pipe, "polling pipe (0: ctl, 1: bulk)"); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-13 12:58:44 +02:00
										 |  |  | static const struct mfd_cell rtsx_usb_cells[] = { | 
					
						
							| 
									
										
										
										
											2014-02-12 18:00:36 +08:00
										 |  |  | 	[RTSX_USB_SD_CARD] = { | 
					
						
							|  |  |  | 		.name = "rtsx_usb_sdmmc", | 
					
						
							|  |  |  | 		.pdata_size = 0, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | 	[RTSX_USB_MS_CARD] = { | 
					
						
							|  |  |  | 		.name = "rtsx_usb_ms", | 
					
						
							|  |  |  | 		.pdata_size = 0, | 
					
						
							|  |  |  | 	}, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void rtsx_usb_sg_timed_out(unsigned long data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct rtsx_ucr *ucr = (struct rtsx_ucr *)data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dev_dbg(&ucr->pusb_intf->dev, "%s: sg transfer timed out", __func__); | 
					
						
							|  |  |  | 	usb_sg_cancel(&ucr->current_sg); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* we know the cancellation is caused by time-out */ | 
					
						
							|  |  |  | 	ucr->current_sg.status = -ETIMEDOUT; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int rtsx_usb_bulk_transfer_sglist(struct rtsx_ucr *ucr, | 
					
						
							|  |  |  | 		unsigned int pipe, struct scatterlist *sg, int num_sg, | 
					
						
							|  |  |  | 		unsigned int length, unsigned int *act_len, int timeout) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dev_dbg(&ucr->pusb_intf->dev, "%s: xfer %u bytes, %d entries\n", | 
					
						
							|  |  |  | 			__func__, length, num_sg); | 
					
						
							|  |  |  | 	ret = usb_sg_init(&ucr->current_sg, ucr->pusb_dev, pipe, 0, | 
					
						
							|  |  |  | 			sg, num_sg, length, GFP_NOIO); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ucr->sg_timer.expires = jiffies + msecs_to_jiffies(timeout); | 
					
						
							|  |  |  | 	add_timer(&ucr->sg_timer); | 
					
						
							|  |  |  | 	usb_sg_wait(&ucr->current_sg); | 
					
						
							| 
									
										
										
										
											2014-04-11 14:53:20 +08:00
										 |  |  | 	del_timer_sync(&ucr->sg_timer); | 
					
						
							| 
									
										
										
										
											2014-02-12 18:00:36 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	if (act_len) | 
					
						
							|  |  |  | 		*act_len = ucr->current_sg.bytes; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ucr->current_sg.status; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int rtsx_usb_transfer_data(struct rtsx_ucr *ucr, unsigned int pipe, | 
					
						
							|  |  |  | 			      void *buf, unsigned int len, int num_sg, | 
					
						
							|  |  |  | 			      unsigned int *act_len, int timeout) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (timeout < 600) | 
					
						
							|  |  |  | 		timeout = 600; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (num_sg) | 
					
						
							|  |  |  | 		return rtsx_usb_bulk_transfer_sglist(ucr, pipe, | 
					
						
							|  |  |  | 				(struct scatterlist *)buf, num_sg, len, act_len, | 
					
						
							|  |  |  | 				timeout); | 
					
						
							|  |  |  | 	else | 
					
						
							|  |  |  | 		return usb_bulk_msg(ucr->pusb_dev, pipe, buf, len, act_len, | 
					
						
							|  |  |  | 				timeout); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(rtsx_usb_transfer_data); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline void rtsx_usb_seq_cmd_hdr(struct rtsx_ucr *ucr, | 
					
						
							|  |  |  | 		u16 addr, u16 len, u8 seq_type) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	rtsx_usb_cmd_hdr_tag(ucr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ucr->cmd_buf[PACKET_TYPE] = seq_type; | 
					
						
							|  |  |  | 	ucr->cmd_buf[5] = (u8)(len >> 8); | 
					
						
							|  |  |  | 	ucr->cmd_buf[6] = (u8)len; | 
					
						
							|  |  |  | 	ucr->cmd_buf[8] = (u8)(addr >> 8); | 
					
						
							|  |  |  | 	ucr->cmd_buf[9] = (u8)addr; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (seq_type == SEQ_WRITE) | 
					
						
							|  |  |  | 		ucr->cmd_buf[STAGE_FLAG] = 0; | 
					
						
							|  |  |  | 	else | 
					
						
							|  |  |  | 		ucr->cmd_buf[STAGE_FLAG] = STAGE_R; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int rtsx_usb_seq_write_register(struct rtsx_ucr *ucr, | 
					
						
							|  |  |  | 		u16 addr, u16 len, u8 *data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	u16 cmd_len = ALIGN(SEQ_WRITE_DATA_OFFSET + len, 4); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!data) | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (cmd_len > IOBUF_SIZE) | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rtsx_usb_seq_cmd_hdr(ucr, addr, len, SEQ_WRITE); | 
					
						
							|  |  |  | 	memcpy(ucr->cmd_buf + SEQ_WRITE_DATA_OFFSET, data, len); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return rtsx_usb_transfer_data(ucr, | 
					
						
							|  |  |  | 			usb_sndbulkpipe(ucr->pusb_dev, EP_BULK_OUT), | 
					
						
							|  |  |  | 			ucr->cmd_buf, cmd_len, 0, NULL, 100); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int rtsx_usb_seq_read_register(struct rtsx_ucr *ucr, | 
					
						
							|  |  |  | 		u16 addr, u16 len, u8 *data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int i, ret; | 
					
						
							|  |  |  | 	u16 rsp_len = round_down(len, 4); | 
					
						
							|  |  |  | 	u16 res_len = len - rsp_len; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!data) | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* 4-byte aligned part */ | 
					
						
							|  |  |  | 	if (rsp_len) { | 
					
						
							|  |  |  | 		rtsx_usb_seq_cmd_hdr(ucr, addr, len, SEQ_READ); | 
					
						
							|  |  |  | 		ret = rtsx_usb_transfer_data(ucr, | 
					
						
							|  |  |  | 				usb_sndbulkpipe(ucr->pusb_dev, EP_BULK_OUT), | 
					
						
							|  |  |  | 				ucr->cmd_buf, 12, 0, NULL, 100); | 
					
						
							|  |  |  | 		if (ret) | 
					
						
							|  |  |  | 			return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ret = rtsx_usb_transfer_data(ucr, | 
					
						
							|  |  |  | 				usb_rcvbulkpipe(ucr->pusb_dev, EP_BULK_IN), | 
					
						
							|  |  |  | 				data, rsp_len, 0, NULL, 100); | 
					
						
							|  |  |  | 		if (ret) | 
					
						
							|  |  |  | 			return ret; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* unaligned part */ | 
					
						
							|  |  |  | 	for (i = 0; i < res_len; i++) { | 
					
						
							|  |  |  | 		ret = rtsx_usb_read_register(ucr, addr + rsp_len + i, | 
					
						
							|  |  |  | 				data + rsp_len + i); | 
					
						
							|  |  |  | 		if (ret) | 
					
						
							|  |  |  | 			return ret; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int rtsx_usb_read_ppbuf(struct rtsx_ucr *ucr, u8 *buf, int buf_len) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return rtsx_usb_seq_read_register(ucr, PPBUF_BASE2, (u16)buf_len, buf); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(rtsx_usb_read_ppbuf); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int rtsx_usb_write_ppbuf(struct rtsx_ucr *ucr, u8 *buf, int buf_len) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return rtsx_usb_seq_write_register(ucr, PPBUF_BASE2, (u16)buf_len, buf); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(rtsx_usb_write_ppbuf); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int rtsx_usb_ep0_write_register(struct rtsx_ucr *ucr, u16 addr, | 
					
						
							|  |  |  | 		u8 mask, u8 data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	u16 value, index; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	addr |= EP0_WRITE_REG_CMD << EP0_OP_SHIFT; | 
					
						
							|  |  |  | 	value = swab16(addr); | 
					
						
							|  |  |  | 	index = mask | data << 8; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return usb_control_msg(ucr->pusb_dev, | 
					
						
							|  |  |  | 			usb_sndctrlpipe(ucr->pusb_dev, 0), RTSX_USB_REQ_REG_OP, | 
					
						
							|  |  |  | 			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, | 
					
						
							|  |  |  | 			value, index, NULL, 0, 100); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(rtsx_usb_ep0_write_register); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int rtsx_usb_ep0_read_register(struct rtsx_ucr *ucr, u16 addr, u8 *data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	u16 value; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!data) | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 	*data = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	addr |= EP0_READ_REG_CMD << EP0_OP_SHIFT; | 
					
						
							|  |  |  | 	value = swab16(addr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return usb_control_msg(ucr->pusb_dev, | 
					
						
							|  |  |  | 			usb_rcvctrlpipe(ucr->pusb_dev, 0), RTSX_USB_REQ_REG_OP, | 
					
						
							|  |  |  | 			USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, | 
					
						
							|  |  |  | 			value, 0, data, 1, 100); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(rtsx_usb_ep0_read_register); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void rtsx_usb_add_cmd(struct rtsx_ucr *ucr, u8 cmd_type, u16 reg_addr, | 
					
						
							|  |  |  | 		u8 mask, u8 data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int i; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ucr->cmd_idx < (IOBUF_SIZE - CMD_OFFSET) / 4) { | 
					
						
							|  |  |  | 		i = CMD_OFFSET + ucr->cmd_idx * 4; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ucr->cmd_buf[i++] = ((cmd_type & 0x03) << 6) | | 
					
						
							|  |  |  | 			(u8)((reg_addr >> 8) & 0x3F); | 
					
						
							|  |  |  | 		ucr->cmd_buf[i++] = (u8)reg_addr; | 
					
						
							|  |  |  | 		ucr->cmd_buf[i++] = mask; | 
					
						
							|  |  |  | 		ucr->cmd_buf[i++] = data; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		ucr->cmd_idx++; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(rtsx_usb_add_cmd); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int rtsx_usb_send_cmd(struct rtsx_ucr *ucr, u8 flag, int timeout) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ucr->cmd_buf[CNT_H] = (u8)(ucr->cmd_idx >> 8); | 
					
						
							|  |  |  | 	ucr->cmd_buf[CNT_L] = (u8)(ucr->cmd_idx); | 
					
						
							|  |  |  | 	ucr->cmd_buf[STAGE_FLAG] = flag; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = rtsx_usb_transfer_data(ucr, | 
					
						
							|  |  |  | 			usb_sndbulkpipe(ucr->pusb_dev, EP_BULK_OUT), | 
					
						
							|  |  |  | 			ucr->cmd_buf, ucr->cmd_idx * 4 + CMD_OFFSET, | 
					
						
							|  |  |  | 			0, NULL, timeout); | 
					
						
							|  |  |  | 	if (ret) { | 
					
						
							|  |  |  | 		rtsx_usb_clear_fsm_err(ucr); | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(rtsx_usb_send_cmd); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int rtsx_usb_get_rsp(struct rtsx_ucr *ucr, int rsp_len, int timeout) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (rsp_len <= 0) | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rsp_len = ALIGN(rsp_len, 4); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return rtsx_usb_transfer_data(ucr, | 
					
						
							|  |  |  | 			usb_rcvbulkpipe(ucr->pusb_dev, EP_BULK_IN), | 
					
						
							|  |  |  | 			ucr->rsp_buf, rsp_len, 0, NULL, timeout); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(rtsx_usb_get_rsp); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int rtsx_usb_get_status_with_bulk(struct rtsx_ucr *ucr, u16 *status) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rtsx_usb_init_cmd(ucr); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, READ_REG_CMD, CARD_EXIST, 0x00, 0x00); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, READ_REG_CMD, OCPSTAT, 0x00, 0x00); | 
					
						
							|  |  |  | 	ret = rtsx_usb_send_cmd(ucr, MODE_CR, 100); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = rtsx_usb_get_rsp(ucr, 2, 100); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	*status = ((ucr->rsp_buf[0] >> 2) & 0x0f) | | 
					
						
							|  |  |  | 		  ((ucr->rsp_buf[1] & 0x03) << 4); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int rtsx_usb_get_card_status(struct rtsx_ucr *ucr, u16 *status) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!status) | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (polling_pipe == 0) | 
					
						
							|  |  |  | 		ret = usb_control_msg(ucr->pusb_dev, | 
					
						
							|  |  |  | 				usb_rcvctrlpipe(ucr->pusb_dev, 0), | 
					
						
							|  |  |  | 				RTSX_USB_REQ_POLL, | 
					
						
							|  |  |  | 				USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, | 
					
						
							|  |  |  | 				0, 0, status, 2, 100); | 
					
						
							|  |  |  | 	else | 
					
						
							|  |  |  | 		ret = rtsx_usb_get_status_with_bulk(ucr, status); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* usb_control_msg may return positive when success */ | 
					
						
							|  |  |  | 	if (ret < 0) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(rtsx_usb_get_card_status); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int rtsx_usb_write_phy_register(struct rtsx_ucr *ucr, u8 addr, u8 val) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	dev_dbg(&ucr->pusb_intf->dev, "Write 0x%x to phy register 0x%x\n", | 
					
						
							|  |  |  | 			val, addr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rtsx_usb_init_cmd(ucr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, HS_VSTAIN, 0xFF, val); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, HS_VCONTROL, 0xFF, addr & 0x0F); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, HS_VLOADM, 0xFF, 0x00); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, HS_VLOADM, 0xFF, 0x00); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, HS_VLOADM, 0xFF, 0x01); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, HS_VCONTROL, | 
					
						
							|  |  |  | 			0xFF, (addr >> 4) & 0x0F); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, HS_VLOADM, 0xFF, 0x00); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, HS_VLOADM, 0xFF, 0x00); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, HS_VLOADM, 0xFF, 0x01); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return rtsx_usb_send_cmd(ucr, MODE_C, 100); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int rtsx_usb_write_register(struct rtsx_ucr *ucr, u16 addr, u8 mask, u8 data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	rtsx_usb_init_cmd(ucr); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, addr, mask, data); | 
					
						
							|  |  |  | 	return rtsx_usb_send_cmd(ucr, MODE_C, 100); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(rtsx_usb_write_register); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int rtsx_usb_read_register(struct rtsx_ucr *ucr, u16 addr, u8 *data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (data != NULL) | 
					
						
							|  |  |  | 		*data = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rtsx_usb_init_cmd(ucr); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, READ_REG_CMD, addr, 0, 0); | 
					
						
							|  |  |  | 	ret = rtsx_usb_send_cmd(ucr, MODE_CR, 100); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = rtsx_usb_get_rsp(ucr, 1, 100); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (data != NULL) | 
					
						
							|  |  |  | 		*data = ucr->rsp_buf[0]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(rtsx_usb_read_register); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline u8 double_ssc_depth(u8 depth) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return (depth > 1) ? (depth - 1) : depth; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static u8 revise_ssc_depth(u8 ssc_depth, u8 div) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (div > CLK_DIV_1) { | 
					
						
							|  |  |  | 		if (ssc_depth > div - 1) | 
					
						
							|  |  |  | 			ssc_depth -= (div - 1); | 
					
						
							|  |  |  | 		else | 
					
						
							|  |  |  | 			ssc_depth = SSC_DEPTH_2M; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return ssc_depth; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int rtsx_usb_switch_clock(struct rtsx_ucr *ucr, unsigned int card_clock, | 
					
						
							|  |  |  | 		u8 ssc_depth, bool initial_mode, bool double_clk, bool vpclk) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 	u8 n, clk_divider, mcu_cnt, div; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!card_clock) { | 
					
						
							|  |  |  | 		ucr->cur_clk = 0; | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (initial_mode) { | 
					
						
							|  |  |  | 		/* We use 250k(around) here, in initial stage */ | 
					
						
							|  |  |  | 		clk_divider = SD_CLK_DIVIDE_128; | 
					
						
							|  |  |  | 		card_clock = 30000000; | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		clk_divider = SD_CLK_DIVIDE_0; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = rtsx_usb_write_register(ucr, SD_CFG1, | 
					
						
							|  |  |  | 			SD_CLK_DIVIDE_MASK, clk_divider); | 
					
						
							|  |  |  | 	if (ret < 0) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	card_clock /= 1000000; | 
					
						
							|  |  |  | 	dev_dbg(&ucr->pusb_intf->dev, | 
					
						
							|  |  |  | 			"Switch card clock to %dMHz\n", card_clock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!initial_mode && double_clk) | 
					
						
							|  |  |  | 		card_clock *= 2; | 
					
						
							|  |  |  | 	dev_dbg(&ucr->pusb_intf->dev, | 
					
						
							|  |  |  | 			"Internal SSC clock: %dMHz (cur_clk = %d)\n", | 
					
						
							|  |  |  | 			card_clock, ucr->cur_clk); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (card_clock == ucr->cur_clk) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Converting clock value into internal settings: n and div */ | 
					
						
							|  |  |  | 	n = card_clock - 2; | 
					
						
							|  |  |  | 	if ((card_clock <= 2) || (n > MAX_DIV_N)) | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mcu_cnt = 60/card_clock + 3; | 
					
						
							|  |  |  | 	if (mcu_cnt > 15) | 
					
						
							|  |  |  | 		mcu_cnt = 15; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Make sure that the SSC clock div_n is not less than MIN_DIV_N */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	div = CLK_DIV_1; | 
					
						
							|  |  |  | 	while (n < MIN_DIV_N && div < CLK_DIV_4) { | 
					
						
							|  |  |  | 		n = (n + 2) * 2 - 2; | 
					
						
							|  |  |  | 		div++; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	dev_dbg(&ucr->pusb_intf->dev, "n = %d, div = %d\n", n, div); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (double_clk) | 
					
						
							|  |  |  | 		ssc_depth = double_ssc_depth(ssc_depth); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ssc_depth = revise_ssc_depth(ssc_depth, div); | 
					
						
							|  |  |  | 	dev_dbg(&ucr->pusb_intf->dev, "ssc_depth = %d\n", ssc_depth); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rtsx_usb_init_cmd(ucr); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CLK_DIV, CLK_CHANGE, CLK_CHANGE); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CLK_DIV, | 
					
						
							|  |  |  | 			0x3F, (div << 4) | mcu_cnt); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SSC_CTL1, SSC_RSTB, 0); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SSC_CTL2, | 
					
						
							|  |  |  | 			SSC_DEPTH_MASK, ssc_depth); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SSC_DIV_N_0, 0xFF, n); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SSC_CTL1, SSC_RSTB, SSC_RSTB); | 
					
						
							|  |  |  | 	if (vpclk) { | 
					
						
							|  |  |  | 		rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_VPCLK0_CTL, | 
					
						
							|  |  |  | 				PHASE_NOT_RESET, 0); | 
					
						
							|  |  |  | 		rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD_VPCLK0_CTL, | 
					
						
							|  |  |  | 				PHASE_NOT_RESET, PHASE_NOT_RESET); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = rtsx_usb_send_cmd(ucr, MODE_C, 2000); | 
					
						
							|  |  |  | 	if (ret < 0) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = rtsx_usb_write_register(ucr, SSC_CTL1, 0xff, | 
					
						
							|  |  |  | 			SSC_RSTB | SSC_8X_EN | SSC_SEL_4M); | 
					
						
							|  |  |  | 	if (ret < 0) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Wait SSC clock stable */ | 
					
						
							|  |  |  | 	usleep_range(100, 1000); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = rtsx_usb_write_register(ucr, CLK_DIV, CLK_CHANGE, 0); | 
					
						
							|  |  |  | 	if (ret < 0) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ucr->cur_clk = card_clock; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(rtsx_usb_switch_clock); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int rtsx_usb_card_exclusive_check(struct rtsx_ucr *ucr, int card) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 	u16 val; | 
					
						
							|  |  |  | 	u16 cd_mask[] = { | 
					
						
							|  |  |  | 		[RTSX_USB_SD_CARD] = (CD_MASK & ~SD_CD), | 
					
						
							|  |  |  | 		[RTSX_USB_MS_CARD] = (CD_MASK & ~MS_CD) | 
					
						
							|  |  |  | 	}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = rtsx_usb_get_card_status(ucr, &val); | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * If get status fails, return 0 (ok) for the exclusive check | 
					
						
							|  |  |  | 	 * and let the flow fail at somewhere else. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (val & cd_mask[card]) | 
					
						
							|  |  |  | 		return -EIO; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | EXPORT_SYMBOL_GPL(rtsx_usb_card_exclusive_check); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int rtsx_usb_reset_chip(struct rtsx_ucr *ucr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 	u8 val; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rtsx_usb_init_cmd(ucr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (CHECK_PKG(ucr, LQFP48)) { | 
					
						
							|  |  |  | 		rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PWR_CTL, | 
					
						
							|  |  |  | 				LDO3318_PWR_MASK, LDO_SUSPEND); | 
					
						
							|  |  |  | 		rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PWR_CTL, | 
					
						
							|  |  |  | 				FORCE_LDO_POWERB, FORCE_LDO_POWERB); | 
					
						
							|  |  |  | 		rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL1, | 
					
						
							|  |  |  | 				0x30, 0x10); | 
					
						
							|  |  |  | 		rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL5, | 
					
						
							|  |  |  | 				0x03, 0x01); | 
					
						
							|  |  |  | 		rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_PULL_CTL6, | 
					
						
							|  |  |  | 				0x0C, 0x04); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SYS_DUMMY0, NYET_MSAK, NYET_EN); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CD_DEGLITCH_WIDTH, 0xFF, 0x08); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, | 
					
						
							|  |  |  | 			CD_DEGLITCH_EN, XD_CD_DEGLITCH_EN, 0x0); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, SD30_DRIVE_SEL, | 
					
						
							|  |  |  | 			SD30_DRIVE_MASK, DRIVER_TYPE_D); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, | 
					
						
							|  |  |  | 			CARD_DRIVE_SEL, SD20_DRIVE_MASK, 0x0); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, LDO_POWER_CFG, 0xE0, 0x0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (ucr->is_rts5179) | 
					
						
							|  |  |  | 		rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, | 
					
						
							|  |  |  | 				CARD_PULL_CTL5, 0x03, 0x01); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_DMA1_CTL, | 
					
						
							|  |  |  | 		       EXTEND_DMA1_ASYNC_SIGNAL, EXTEND_DMA1_ASYNC_SIGNAL); | 
					
						
							|  |  |  | 	rtsx_usb_add_cmd(ucr, WRITE_REG_CMD, CARD_INT_PEND, | 
					
						
							|  |  |  | 			XD_INT | MS_INT | SD_INT, | 
					
						
							|  |  |  | 			XD_INT | MS_INT | SD_INT); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = rtsx_usb_send_cmd(ucr, MODE_C, 100); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* config non-crystal mode */ | 
					
						
							|  |  |  | 	rtsx_usb_read_register(ucr, CFG_MODE, &val); | 
					
						
							|  |  |  | 	if ((val & XTAL_FREE) || ((val & CLK_MODE_MASK) == CLK_MODE_NON_XTAL)) { | 
					
						
							|  |  |  | 		ret = rtsx_usb_write_phy_register(ucr, 0xC2, 0x7C); | 
					
						
							|  |  |  | 		if (ret) | 
					
						
							|  |  |  | 			return ret; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int rtsx_usb_init_chip(struct rtsx_ucr *ucr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 	u8 val; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rtsx_usb_clear_fsm_err(ucr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* power on SSC */ | 
					
						
							|  |  |  | 	ret = rtsx_usb_write_register(ucr, | 
					
						
							|  |  |  | 			FPDCTL, SSC_POWER_MASK, SSC_POWER_ON); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	usleep_range(100, 1000); | 
					
						
							|  |  |  | 	ret = rtsx_usb_write_register(ucr, CLK_DIV, CLK_CHANGE, 0x00); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* determine IC version */ | 
					
						
							|  |  |  | 	ret = rtsx_usb_read_register(ucr, HW_VERSION, &val); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ucr->ic_version = val & HW_VER_MASK; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* determine package */ | 
					
						
							|  |  |  | 	ret = rtsx_usb_read_register(ucr, CARD_SHARE_MODE, &val); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (val & CARD_SHARE_LQFP_SEL) { | 
					
						
							|  |  |  | 		ucr->package = LQFP48; | 
					
						
							|  |  |  | 		dev_dbg(&ucr->pusb_intf->dev, "Package: LQFP48\n"); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		ucr->package = QFN24; | 
					
						
							|  |  |  | 		dev_dbg(&ucr->pusb_intf->dev, "Package: QFN24\n"); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* determine IC variations */ | 
					
						
							|  |  |  | 	rtsx_usb_read_register(ucr, CFG_MODE_1, &val); | 
					
						
							|  |  |  | 	if (val & RTS5179) { | 
					
						
							|  |  |  | 		ucr->is_rts5179 = true; | 
					
						
							|  |  |  | 		dev_dbg(&ucr->pusb_intf->dev, "Device is rts5179\n"); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		ucr->is_rts5179 = false; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return rtsx_usb_reset_chip(ucr); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int rtsx_usb_probe(struct usb_interface *intf, | 
					
						
							|  |  |  | 			 const struct usb_device_id *id) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct usb_device *usb_dev = interface_to_usbdev(intf); | 
					
						
							|  |  |  | 	struct rtsx_ucr *ucr; | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dev_dbg(&intf->dev, | 
					
						
							|  |  |  | 		": Realtek USB Card Reader found at bus %03d address %03d\n", | 
					
						
							|  |  |  | 		 usb_dev->bus->busnum, usb_dev->devnum); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ucr = devm_kzalloc(&intf->dev, sizeof(*ucr), GFP_KERNEL); | 
					
						
							|  |  |  | 	if (!ucr) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ucr->pusb_dev = usb_dev; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ucr->iobuf = usb_alloc_coherent(ucr->pusb_dev, IOBUF_SIZE, | 
					
						
							|  |  |  | 			GFP_KERNEL, &ucr->iobuf_dma); | 
					
						
							|  |  |  | 	if (!ucr->iobuf) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	usb_set_intfdata(intf, ucr); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ucr->vendor_id = id->idVendor; | 
					
						
							|  |  |  | 	ucr->product_id = id->idProduct; | 
					
						
							|  |  |  | 	ucr->cmd_buf = ucr->rsp_buf = ucr->iobuf; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex_init(&ucr->dev_mutex); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ucr->pusb_intf = intf; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* initialize */ | 
					
						
							|  |  |  | 	ret = rtsx_usb_init_chip(ucr); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		goto out_init_fail; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-04-11 14:53:20 +08:00
										 |  |  | 	/* initialize USB SG transfer timer */ | 
					
						
							|  |  |  | 	setup_timer(&ucr->sg_timer, rtsx_usb_sg_timed_out, (unsigned long) ucr); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-09-26 12:55:31 +02:00
										 |  |  | 	ret = mfd_add_hotplug_devices(&intf->dev, rtsx_usb_cells, | 
					
						
							|  |  |  | 				      ARRAY_SIZE(rtsx_usb_cells)); | 
					
						
							| 
									
										
										
										
											2014-02-12 18:00:36 +08:00
										 |  |  | 	if (ret) | 
					
						
							|  |  |  | 		goto out_init_fail; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef CONFIG_PM
 | 
					
						
							|  |  |  | 	intf->needs_remote_wakeup = 1; | 
					
						
							|  |  |  | 	usb_enable_autosuspend(usb_dev); | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | out_init_fail: | 
					
						
							|  |  |  | 	usb_free_coherent(ucr->pusb_dev, IOBUF_SIZE, ucr->iobuf, | 
					
						
							|  |  |  | 			ucr->iobuf_dma); | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void rtsx_usb_disconnect(struct usb_interface *intf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct rtsx_ucr *ucr = (struct rtsx_ucr *)usb_get_intfdata(intf); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dev_dbg(&intf->dev, "%s called\n", __func__); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mfd_remove_devices(&intf->dev); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	usb_set_intfdata(ucr->pusb_intf, NULL); | 
					
						
							|  |  |  | 	usb_free_coherent(ucr->pusb_dev, IOBUF_SIZE, ucr->iobuf, | 
					
						
							|  |  |  | 			ucr->iobuf_dma); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #ifdef CONFIG_PM
 | 
					
						
							|  |  |  | static int rtsx_usb_suspend(struct usb_interface *intf, pm_message_t message) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2014-08-05 21:42:28 -07:00
										 |  |  | 	dev_dbg(&intf->dev, "%s called with pm message 0x%04x\n", | 
					
						
							| 
									
										
										
										
											2014-02-12 18:00:36 +08:00
										 |  |  | 			__func__, message.event); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int rtsx_usb_resume(struct usb_interface *intf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int rtsx_usb_reset_resume(struct usb_interface *intf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct rtsx_ucr *ucr = | 
					
						
							|  |  |  | 		(struct rtsx_ucr *)usb_get_intfdata(intf); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	rtsx_usb_reset_chip(ucr); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #else /* CONFIG_PM */
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define rtsx_usb_suspend NULL
 | 
					
						
							|  |  |  | #define rtsx_usb_resume NULL
 | 
					
						
							|  |  |  | #define rtsx_usb_reset_resume NULL
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #endif /* CONFIG_PM */
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int rtsx_usb_pre_reset(struct usb_interface *intf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct rtsx_ucr *ucr = (struct rtsx_ucr *)usb_get_intfdata(intf); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex_lock(&ucr->dev_mutex); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int rtsx_usb_post_reset(struct usb_interface *intf) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct rtsx_ucr *ucr = (struct rtsx_ucr *)usb_get_intfdata(intf); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mutex_unlock(&ucr->dev_mutex); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct usb_device_id rtsx_usb_usb_ids[] = { | 
					
						
							|  |  |  | 	{ USB_DEVICE(0x0BDA, 0x0129) }, | 
					
						
							|  |  |  | 	{ USB_DEVICE(0x0BDA, 0x0139) }, | 
					
						
							|  |  |  | 	{ USB_DEVICE(0x0BDA, 0x0140) }, | 
					
						
							|  |  |  | 	{ } | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2014-08-12 13:46:09 -07:00
										 |  |  | MODULE_DEVICE_TABLE(usb, rtsx_usb_usb_ids); | 
					
						
							| 
									
										
										
										
											2014-02-12 18:00:36 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | static struct usb_driver rtsx_usb_driver = { | 
					
						
							|  |  |  | 	.name			= "rtsx_usb", | 
					
						
							|  |  |  | 	.probe			= rtsx_usb_probe, | 
					
						
							|  |  |  | 	.disconnect		= rtsx_usb_disconnect, | 
					
						
							|  |  |  | 	.suspend		= rtsx_usb_suspend, | 
					
						
							|  |  |  | 	.resume			= rtsx_usb_resume, | 
					
						
							|  |  |  | 	.reset_resume		= rtsx_usb_reset_resume, | 
					
						
							|  |  |  | 	.pre_reset		= rtsx_usb_pre_reset, | 
					
						
							|  |  |  | 	.post_reset		= rtsx_usb_post_reset, | 
					
						
							|  |  |  | 	.id_table		= rtsx_usb_usb_ids, | 
					
						
							|  |  |  | 	.supports_autosuspend	= 1, | 
					
						
							|  |  |  | 	.soft_unbind		= 1, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module_usb_driver(rtsx_usb_driver); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | MODULE_LICENSE("GPL v2"); | 
					
						
							|  |  |  | MODULE_AUTHOR("Roger Tseng <rogerable@realtek.com>"); | 
					
						
							|  |  |  | MODULE_DESCRIPTION("Realtek USB Card Reader Driver"); |