// SPDX-License-Identifier: GPL-2.0-only
/* Driver for Realtek USB card reader
*
* Copyright(c) 2009-2013 Realtek Semiconductor Corp. All rights reserved.
*
* 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/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)");
static const struct mfd_cell rtsx_usb_cells[] = {
[RTSX_USB_SD_CARD] = {
.name = DRV_NAME_RTSX_USB_SDMMC,
.pdata_size = 0,
},
[RTSX_USB_MS_CARD] = {
.name = DRV_NAME_RTSX_USB_MS,
.pdata_size = 0,
},
};
static void rtsx_usb_sg_timed_out(struct timer_list *t)
{
struct rtsx_ucr *ucr = from_timer(ucr, t, sg_timer);
dev_dbg(&ucr->pusb_intf->dev, "%s: sg transfer timed out", __func__);
usb_sg_cancel(&ucr->current_sg);
}
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);
if (!del_timer_sync(&ucr->sg_timer))
ret = -ETIMEDOUT;
else
ret = ucr->current_sg.status;
if (act_len)
*act_len = ucr->current_sg.bytes;
return ret;
}
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