#include <linux/blkdev.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include "rtsx.h"
unsigned int rtsx_stor_access_xfer_buf(unsigned char *buffer,
unsigned int buflen,
struct scsi_cmnd *srb,
unsigned int *index,
unsigned int *offset,
enum xfer_buf_dir dir)
{
unsigned int cnt;
if (scsi_sg_count(srb) == 0) {
unsigned char *sgbuffer;
if (*offset >= scsi_bufflen(srb))
return 0;
cnt = min(buflen, scsi_bufflen(srb) - *offset);
sgbuffer = (unsigned char *)scsi_sglist(srb) + *offset;
if (dir == TO_XFER_BUF)
memcpy(sgbuffer, buffer, cnt);
else
memcpy(buffer, sgbuffer, cnt);
*offset += cnt;
} else {
struct scatterlist *sg =
(struct scatterlist *)scsi_sglist(srb)
+ *index;
cnt = 0;
while (cnt < buflen && *index < scsi_sg_count(srb)) {
struct page *page = sg_page(sg) +
((sg->offset + *offset) >> PAGE_SHIFT);
unsigned int poff = (sg->offset + *offset) &
(PAGE_SIZE - 1);
unsigned int sglen = sg->length - *offset;
if (sglen > buflen - cnt) {
sglen = buflen - cnt;
*offset += sglen;
} else {
*offset = 0;
++*index;
++sg;
}
while (sglen > 0) {
unsigned int plen = min(sglen, (unsigned int)
PAGE_SIZE - poff);
if (dir == TO_XFER_BUF)
memcpy_to_page(page, poff, buffer + cnt, plen);
else
memcpy_from_page(buffer + cnt, page, poff, plen);
poff = 0;
++page;
cnt += plen;
sglen -= plen;
}
}
}
return cnt;
}
void rtsx_stor_set_xfer_buf(unsigned char *buffer,
unsigned int buflen, struct scsi_cmnd *srb)
{
unsigned int index = 0, offset = 0;
rtsx_stor_access_xfer_buf(buffer, buflen, srb, &index, &offset,
TO_XFER_BUF);
if (buflen < scsi_bufflen(srb))
scsi_set_resid(srb, scsi_bufflen(srb) - buflen);
}
void rtsx_stor_get_xfer_buf(unsigned char *buffer,
unsigned int buflen, struct scsi_cmnd *srb)
{
unsigned int index = 0, offset = 0;
rtsx_stor_access_xfer_buf(buffer, buflen, srb, &index, &offset,
FROM_XFER_BUF);
if (buflen < scsi_bufflen(srb))
scsi_set_resid(srb, scsi_bufflen(srb) - buflen);
}
void rtsx_invoke_transport(struct scsi_cmnd *srb, struct rtsx_chip *chip)
{
int result;
result = rtsx_scsi_handler(srb, chip);
if (rtsx_chk_stat(chip, RTSX_STAT_ABORT)) {
dev_dbg(rtsx_dev(chip), "-- command was aborted\n");
srb->result = DID_ABORT << 16;
goto handle_errors;
}
if (result == TRANSPORT_ERROR) {
dev_dbg(rtsx_dev(chip), "-- transport indicates error, resetting\n");
srb->result = DID_ERROR << 16;
goto handle_errors;
}
srb->result = SAM_STAT_GOOD;
if (result == TRANSPORT_FAILED) {
srb->result = SAM_STAT_CHECK_CONDITION;
memcpy(srb->sense_buffer,
(unsigned char *)&chip->sense_buffer[SCSI_LUN(srb)],
sizeof(struct sense_data_t));
}
return;
handle_errors:
return;
}
void rtsx_add_cmd(struct rtsx_chip *chip,
u8 cmd_type, u16 reg_addr, u8 mask, u8 data)
{
__le32 *cb = (__le32 *)(chip->host_cmds_ptr);
u32 val = 0;
val |= (u32)(cmd_type & 0x03) << 30;
val |= (u32)(reg_addr & 0x3FFF) << 16;
val |= (u32)mask << 8;
val |= (u32)data;
spin_lock_irq(&chip->rtsx->reg_lock);
if (chip->ci < (HOST_CMDS_BUF_LEN / 4))
cb[(chip->ci)++] = cpu_to_le32(val);
spin_unlock_irq(&chip->rtsx->reg_lock);
}
void rtsx_send_cmd_no_wait(struct rtsx_chip *chip)
{
u32 val = BIT(31);
rtsx_writel(chip, RTSX_HCBAR, chip->host_cmds_addr);
val |= (u32)(chip->ci * 4) & 0x00FFFFFF;
val |= 0x40000000;
rtsx_writel(chip, RTSX_HCBCTLR, val);
}
int rtsx_send_cmd(struct rtsx_chip *chip, u8 card, int timeout)
{
struct rtsx_dev *rtsx = chip->rtsx;
struct completion trans_done;
u32 val = BIT(31);
long timeleft;
int err = 0;
if (card == SD_CARD)
rtsx->check_card_cd = SD_EXIST;
else if (card == MS_CARD)
rtsx->check_card_cd = MS_EXIST;
else if (card == XD_CARD)
rtsx->check_card_cd = XD_EXIST;
else
rtsx->check_card_cd = 0;
spin_lock_irq(&rtsx->reg_lock);
rtsx->done = &trans_done;
rtsx->trans_result = TRANS_NOT_READY;
init_completion(&trans_done);
rtsx->trans_state = STATE_TRANS_CMD;
rtsx_writel(chip, RTSX_HCBAR, chip->host_cmds_addr);
val |= (u32)(chip->ci * 4) & 0x00FFFFFF;
val |= 0x40000000;
rtsx_writel(chip, RTSX_HCBCTLR, val);
spin_unlock_irq(&rtsx->reg_lock);
timeleft = wait_for_completion_interruptible_timeout(&trans_done,
msecs_to_jiffies(timeout));
if (timeleft <= 0) {
dev_dbg(rtsx_dev(chip), "chip->int_reg = 0x%x\n",
chip->int_reg);
err = -ETIMEDOUT;
goto finish_send_cmd;
}
spin_lock_irq(&rtsx->reg_lock);
if (rtsx->trans_result == TRANS_RESULT_FAIL)
err = -EIO;
else if (rtsx->trans_result == TRANS_RESULT_OK)
err = 0;
spin_unlock_irq(&rtsx->reg_lock);
finish_send_cmd:
rtsx->done = NULL;
rtsx->trans_state = STATE_TRANS_NONE;
if (err < 0)
rtsx_stop_cmd(chip, card);
return err;
}
static inline void rtsx_add_sg_tbl(struct rtsx_chip *chip,
u32 addr, u32 len, u8 option)
{
__le64 *sgb = (__le64 *)(chip->host_sg_tbl_ptr);
u64 val = 0;
u32 temp_len = 0;
u8 temp_opt = 0;
do {
if (len > 0x80000) {
temp_len = 0x80000;
temp_opt = option & (~RTSX_SG_END);
} else {
temp_len = len;
temp_opt = option;
}
val = ((u64)addr << 32) | ((u64)temp_len << 12) | temp_opt;
if (chip->sgi < (HOST_SG_TBL_BUF_LEN / 8))
sgb[(chip->sgi)++] = cpu_to_le64(val);
len -= temp_len;
addr += temp_len;
} while (len);
}
static int rtsx_transfer_sglist_adma_partial(struct rtsx_chip *chip, u8 card,
struct scatterlist *sg, int num_sg,
unsigned int *index,
unsigned int *offset, int size,
enum dma_data_direction dma_dir,
int timeout)
{
struct rtsx_dev *rtsx = chip->rtsx;
struct completion trans_done;
u8 dir;
int sg_cnt, i, resid;
int err = 0;
long timeleft;
struct scatterlist *sg_ptr;
u32 val = TRIG_DMA;
if (!sg || num_sg <= 0 || !offset || !index)
return -EIO;
if (dma_dir == DMA_TO_DEVICE)
dir = HOST_TO_DEVICE;
else if (dma_dir == DMA_FROM_DEVICE)
dir = DEVICE_TO_HOST;
else
return -ENXIO;
if (card == SD_CARD)
rtsx->check_card_cd = SD_EXIST;
else if (card == MS_CARD)
rtsx->check_card_cd = MS_EXIST;
else if (card == XD_CARD)
rtsx->check_card_cd = XD_EXIST;
else
rtsx->check_card_cd = 0;
spin_lock_irq(&rtsx->reg_lock);
rtsx->done = &trans_done;
rtsx->trans_state = STATE_TRANS_SG;
rtsx->trans_result = TRANS_NOT_READY;
spin_unlock_irq(&rtsx->reg_lock);
sg_cnt = dma_map_sg(&rtsx->pci->dev, sg, num_sg, dma_dir);
resid = size;
sg_ptr = sg;
chip->sgi = 0;
for (i = 0; i < *index; i++)
sg_ptr = sg_next(sg_ptr);
for (i = *index; i < sg_cnt; i++) {
dma_addr_t addr;
unsigned int len;
u8 option;
addr = sg_dma_address(sg_ptr);
len = sg_dma_len(sg_ptr);
dev_dbg(rtsx_dev(chip), "DMA addr: 0x%x, Len: 0x%x\n",
(unsigned int)addr, len);
dev_dbg(rtsx_dev(chip), "*index = %d, *offset = %d\n",
*index, *offset);
addr += *offset;
if ((len - *offset) > resid) {
*offset += resid;
len = resid;
resid = 0;
} else {
resid -= (len - *offset);
len -= *offset;
*offset = 0;
*index = *index + 1;
}
option = RTSX_SG_VALID | RTSX_SG_TRANS_DATA;
if ((i == sg_cnt - 1) || !resid)
option |= RTSX_SG_END;
rtsx_add_sg_tbl(chip, (u32)addr, (u32)len, option);
if (!resid)
break;
sg_ptr = sg_next(sg_ptr);
}
dev_dbg(rtsx_dev(chip), "SG table count = %d\n", chip->sgi);
val |= (u32)(dir & 0x01) << 29;
val |= ADMA_MODE;
spin_lock_irq(&rtsx->reg_lock);
init_completion(&trans_done);
rtsx_writel(chip, RTSX_HDBAR, chip->host_sg_tbl_addr);
rtsx_writel(chip, RTSX_HDBCTLR, val);
spin_unlock_irq(&rtsx->reg_lock);
timeleft = wait_for_completion_interruptible_timeout(&trans_done,
msecs_to_jiffies(timeout));
if (timeleft <= 0) {
dev_dbg(rtsx_dev(chip), "Timeout (%s %d)\n",
__func__, __LINE__);
dev_dbg(rtsx_dev(chip), "chip->int_reg = 0x%x\n",
chip->int_reg);
err = -ETIMEDOUT;
goto out;
}
spin_lock_irq(&rtsx->reg_lock);
if (rtsx->trans_result == TRANS_RESULT_FAIL) {
err = -EIO;
spin_unlock_irq(&rtsx->reg_lock);
goto out;
}
spin_unlock_irq(&rtsx->reg_lock);
spin_lock_irq(&rtsx->reg_lock);
if (rtsx->trans_result == TRANS_NOT_READY) {
init_completion(&trans_done);
spin_unlock_irq(&rtsx->reg_lock);
timeleft = wait_for_completion_interruptible_timeout(&trans_done,
msecs_to_jiffies(timeout));
if (timeleft <= 0) {
dev_dbg(rtsx_dev(chip), "Timeout (%s %d)\n",
__func__, __LINE__);
dev_dbg(rtsx_dev(chip), "chip->int_reg = 0x%x\n",
chip->int_reg);
err = -ETIMEDOUT;
goto out;
}
} else {
spin_unlock_irq(&rtsx->reg_lock);
}
spin_lock_irq(&rtsx->reg_lock);
if (rtsx->trans_result == TRANS_RESULT_FAIL)
err = -EIO;
else if (rtsx->trans_result == TRANS_RESULT_OK)
err = 0;
spin_unlock_irq(&rtsx->reg_lock);
out:
rtsx->done = NULL;
rtsx->trans_state = STATE_TRANS_NONE;
dma_unmap_sg(&rtsx->pci->dev, sg, num_sg, dma_dir);
if (err < 0)
rtsx_stop_cmd(chip, card);
return err;
}
static int rtsx_transfer_sglist_adma(struct rtsx_chip *chip, u8 card,
struct scatterlist *sg, int num_sg,
enum dma_data_direction dma_dir,
int timeout)
{
struct rtsx_dev *rtsx = chip->rtsx;
struct completion trans_done;
u8 dir;
int buf_cnt, i;
int err = 0;
long timeleft;
struct scatterlist *sg_ptr;
if (!sg || num_sg <= 0)
return -EIO;
if (dma_dir == DMA_TO_DEVICE)
dir = HOST_TO_DEVICE;
else if (dma_dir == DMA_FROM_DEVICE)
dir = DEVICE_TO_HOST;
else
return -ENXIO;
if (card == SD_CARD)
rtsx->check_card_cd = SD_EXIST;
else if (card == MS_CARD)
rtsx->check_card_cd = MS_EXIST;
else if (card == XD_CARD)
rtsx->check_card_cd = XD_EXIST;
else
rtsx->check_card_cd = 0;
spin_lock_irq(&rtsx->reg_lock);
rtsx->done = &trans_done;
rtsx->trans_state = STATE_TRANS_SG;
rtsx->trans_result = TRANS_NOT_READY;
spin_unlock_irq(&rtsx->reg_lock);
buf_cnt = dma_map_sg(&rtsx->pci->dev, sg, num_sg, dma_dir);
sg_ptr = sg;
for (i = 0; i <= buf_cnt / (HOST_SG_TBL_BUF_LEN / 8); i++) {
u32 val = TRIG_DMA;
int sg_cnt, j;
if (i == buf_cnt / (HOST_SG_TBL_BUF_LEN / 8))
sg_cnt = buf_cnt % (HOST_SG_TBL_BUF_LEN / 8);
else
sg_cnt = HOST_SG_TBL_BUF_LEN / 8;
chip->sgi = 0;
for (j = 0; j < sg_cnt; j++) {
dma_addr_t addr = sg_dma_address(sg_ptr);
unsigned int len = sg_dma_len(sg_ptr);
u8 option;
dev_dbg(rtsx_dev(chip), "DMA addr: 0x%x, Len: 0x%x\n",
(unsigned int)addr, len);
option = RTSX_SG_VALID | RTSX_SG_TRANS_DATA;
if (j == (sg_cnt - 1))
option |= RTSX_SG_END;
rtsx_add_sg_tbl(chip, (u32)addr, (u32)len, option);
sg_ptr = sg_next(sg_ptr);
}
dev_dbg(rtsx_dev(chip), "SG table count = %d\n", chip->sgi);
val |= (u32)(dir & 0x01) << 29;
val |= ADMA_MODE;
spin_lock_irq(&rtsx->reg_lock);
init_completion(&trans_done);
rtsx_writel(chip, RTSX_HDBAR, chip->host_sg_tbl_addr);
rtsx_writel(chip, RTSX_HDBCTLR, val);
spin_unlock_irq(&rtsx->reg_lock);
timeleft = wait_for_completion_interruptible_timeout(&trans_done,
msecs_to_jiffies(timeout));
if (timeleft <= 0) {
dev_dbg(rtsx_dev(chip), "Timeout (%s %d)\n",
__func__, __LINE__);
dev_dbg(rtsx_dev(chip), "chip->int_reg = 0x%x\n",
chip->int_reg);
err = -ETIMEDOUT;
goto out;
}
spin_lock_irq(&rtsx->reg_lock);
if (rtsx->trans_result == TRANS_RESULT_FAIL) {
err = -EIO;
spin_unlock_irq(&rtsx->reg_lock);
goto out;
}
spin_unlock_irq(&rtsx->reg_lock);
sg_ptr += sg_cnt;
}
spin_lock_irq(&rtsx->reg_lock);
if (rtsx->trans_result == TRANS_NOT_READY) {
init_completion(&trans_done);
spin_unlock_irq(&rtsx->reg_lock);
timeleft = wait_for_completion_interruptible_timeout(&trans_done,
msecs_to_jiffies(timeout));
if (timeleft <= 0) {
dev_dbg(rtsx_dev(chip), "Timeout (%s %d)\n",
__func__, __LINE__);
dev_dbg(rtsx_dev(chip), "chip->int_reg = 0x%x\n",
chip->int_reg);
err = -ETIMEDOUT;
goto out;
}
} else {
spin_unlock_irq(&rtsx->reg_lock);
}
spin_lock_irq(&rtsx->reg_lock);
if (rtsx->trans_result == TRANS_RESULT_FAIL)
err = -EIO;
else if (rtsx->trans_result == TRANS_RESULT_OK)
err = 0;
spin_unlock_irq(&rtsx->reg_lock);
out:
rtsx->done = NULL;
rtsx->trans_state = STATE_TRANS_NONE;
dma_unmap_sg(&rtsx->pci->dev, sg, num_sg, dma_dir);
if (err < 0)
rtsx_stop_cmd(chip, card);
return err;
}
static int rtsx_transfer_buf(struct rtsx_chip *chip, u8 card, void *buf,
size_t len, enum dma_data_direction dma_dir,
int timeout)
{
struct rtsx_dev *rtsx = chip->rtsx;
struct completion trans_done;
dma_addr_t addr;
u8 dir;
int err = 0;
u32 val = BIT(31);
long timeleft;
if (!buf || len <= 0)
return -EIO;
if (dma_dir == DMA_TO_DEVICE)
dir = HOST_TO_DEVICE;
else if (dma_dir == DMA_FROM_DEVICE)
dir = DEVICE_TO_HOST;
else
return -ENXIO;
addr = dma_map_single(&rtsx->pci->dev, buf, len, dma_dir);
if (dma_mapping_error(&rtsx->pci->dev, addr))
return -ENOMEM;
if (card == SD_CARD)
rtsx->check_card_cd = SD_EXIST;
else if (card == MS_CARD)
rtsx->check_card_cd = MS_EXIST;
else if (card == XD_CARD)
rtsx->check_card_cd = XD_EXIST;
else
rtsx->check_card_cd = 0;
val |= (u32)(dir & 0x01) << 29;
val |= (u32)(len & 0x00FFFFFF);
spin_lock_irq(&rtsx->reg_lock);
rtsx->done = &trans_done;
init_completion(&trans_done);
rtsx->trans_state = STATE_TRANS_BUF;
rtsx->trans_result = TRANS_NOT_READY;
rtsx_writel(chip, RTSX_HDBAR, addr);
rtsx_writel(chip, RTSX_HDBCTLR, val);
spin_unlock_irq(&rtsx->reg_lock);
timeleft = wait_for_completion_interruptible_timeout(&trans_done,
msecs_to_jiffies(timeout));
if (timeleft <= 0) {
dev_dbg(rtsx_dev(chip), "Timeout (%s %d)\n",
__func__, __LINE__);
dev_dbg(rtsx_dev(chip), "chip->int_reg = 0x%x\n",
chip->int_reg);
err = -ETIMEDOUT;
goto out;
}
spin_lock_irq(&rtsx->reg_lock);
if (rtsx->trans_result == TRANS_RESULT_FAIL)
err = -EIO;
else if (rtsx->trans_result == TRANS_RESULT_OK)
err = 0;
spin_unlock_irq(&rtsx->reg_lock);
out:
rtsx->done = NULL;
rtsx->trans_state = STATE_TRANS_NONE;
dma_unmap_single(&rtsx->pci->dev, addr, len, dma_dir);
if (err < 0)
rtsx_stop_cmd(chip, card);
return err;
}
int rtsx_transfer_data_partial(struct rtsx_chip *chip, u8 card,
void *buf, size_t len, int use_sg,
unsigned int *index, unsigned int *offset,
enum dma_data_direction dma_dir, int timeout)
{
int err = 0;
if (rtsx_chk_stat(chip, RTSX_STAT_ABORT))
return -EIO;
if (use_sg) {
struct scatterlist *sg = buf;
err = rtsx_transfer_sglist_adma_partial(chip, card, sg, use_sg,
index, offset, (int)len,
dma_dir, timeout);
} else {
err = rtsx_transfer_buf(chip, card,
buf, len, dma_dir, timeout);
}
if (err < 0) {
if (RTSX_TST_DELINK(chip)) {
RTSX_CLR_DELINK(chip);
chip->need_reinit = SD_CARD | MS_CARD | XD_CARD;
rtsx_reinit_cards(chip, 1);
}
}
return err;
}
int rtsx_transfer_data(struct rtsx_chip *chip, u8 card, void *buf, size_t len,
int use_sg, enum dma_data_direction dma_dir, int timeout)
{
int err = 0;
dev_dbg(rtsx_dev(chip), "use_sg = %d\n", use_sg);
if (rtsx_chk_stat(chip, RTSX_STAT_ABORT))
return -EIO;
if (use_sg) {
err = rtsx_transfer_sglist_adma(chip, card, buf,
use_sg, dma_dir, timeout);
} else {
err = rtsx_transfer_buf(chip, card, buf, len, dma_dir, timeout);
}
if (err < 0) {
if (RTSX_TST_DELINK(chip)) {
RTSX_CLR_DELINK(chip);
chip->need_reinit = SD_CARD | MS_CARD | XD_CARD;
rtsx_reinit_cards(chip, 1);
}
}
return err;
}