From: Wei WANG <wei_wang@xxxxxxxxxxxxxx> Realtek PCI-E card reader driver adapts requests from upper-level sdmmc/memstick layer to the real physical card reader. Signed-off-by: Wei WANG <wei_wang@xxxxxxxxxxxxxx> --- drivers/misc/realtek_cr/Kconfig | 1 + drivers/misc/realtek_cr/Makefile | 1 + drivers/misc/realtek_cr/pci/Kconfig | 6 + drivers/misc/realtek_cr/pci/Makefile | 3 + drivers/misc/realtek_cr/pci/rts5209.c | 88 ++ drivers/misc/realtek_cr/pci/rts5209.h | 36 + drivers/misc/realtek_cr/pci/rts5229.c | 115 +++ drivers/misc/realtek_cr/pci/rts5229.h | 36 + drivers/misc/realtek_cr/pci/rtsx_pci.c | 1518 ++++++++++++++++++++++++++++++++ drivers/misc/realtek_cr/pci/rtsx_pci.h | 695 +++++++++++++++ drivers/misc/realtek_cr/pci/sdmmc.c | 1070 ++++++++++++++++++++++ drivers/misc/realtek_cr/pci/sdmmc.h | 48 + 12 files changed, 3617 insertions(+) create mode 100644 drivers/misc/realtek_cr/pci/Kconfig create mode 100644 drivers/misc/realtek_cr/pci/Makefile create mode 100644 drivers/misc/realtek_cr/pci/rts5209.c create mode 100644 drivers/misc/realtek_cr/pci/rts5209.h create mode 100644 drivers/misc/realtek_cr/pci/rts5229.c create mode 100644 drivers/misc/realtek_cr/pci/rts5229.h create mode 100644 drivers/misc/realtek_cr/pci/rtsx_pci.c create mode 100644 drivers/misc/realtek_cr/pci/rtsx_pci.h create mode 100644 drivers/misc/realtek_cr/pci/sdmmc.c create mode 100644 drivers/misc/realtek_cr/pci/sdmmc.h diff --git a/drivers/misc/realtek_cr/Kconfig b/drivers/misc/realtek_cr/Kconfig index 303d98a..27a5431a 100644 --- a/drivers/misc/realtek_cr/Kconfig +++ b/drivers/misc/realtek_cr/Kconfig @@ -22,5 +22,6 @@ config REALTEK_CR_DEBUG if REALTEK_CR_SUPPORT source "drivers/misc/realtek_cr/core/Kconfig" +source "drivers/misc/realtek_cr/pci/Kconfig" endif diff --git a/drivers/misc/realtek_cr/Makefile b/drivers/misc/realtek_cr/Makefile index f4e16ba..faae4d1 100644 --- a/drivers/misc/realtek_cr/Makefile +++ b/drivers/misc/realtek_cr/Makefile @@ -5,3 +5,4 @@ subdir-ccflags-$(CONFIG_REALTEK_CR_DEBUG) := -DDEBUG obj-$(CONFIG_REALTEK_CR_SUPPORT) += core/ +obj-$(CONFIG_REALTEK_CR_SUPPORT) += pci/ diff --git a/drivers/misc/realtek_cr/pci/Kconfig b/drivers/misc/realtek_cr/pci/Kconfig new file mode 100644 index 0000000..5efbc90 --- /dev/null +++ b/drivers/misc/realtek_cr/pci/Kconfig @@ -0,0 +1,6 @@ +config REALTEK_CR_PCI + tristate "Realtek PCI-E Card Reader Adapter Driver" + depends on PCI && REALTEK_CR_CORE + help + Say Y here to include driver code to support the Realtek + PCI-E card reader. diff --git a/drivers/misc/realtek_cr/pci/Makefile b/drivers/misc/realtek_cr/pci/Makefile new file mode 100644 index 0000000..667bdac --- /dev/null +++ b/drivers/misc/realtek_cr/pci/Makefile @@ -0,0 +1,3 @@ +obj-$(CONFIG_REALTEK_CR_PCI) += rtsx_pdev.o + +rtsx_pdev-y := rtsx_pci.o sdmmc.o rts5209.o rts5229.o diff --git a/drivers/misc/realtek_cr/pci/rts5209.c b/drivers/misc/realtek_cr/pci/rts5209.c new file mode 100644 index 0000000..16dd97e --- /dev/null +++ b/drivers/misc/realtek_cr/pci/rts5209.c @@ -0,0 +1,88 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009 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 as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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: + * Wei WANG <wei_wang@xxxxxxxxxxxxxx> + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#include <linux/pci.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/highmem.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/rtsx_core.h> + +#include "rtsx_pci.h" +#include "rts5209.h" + +u8 rts5209_get_ic_version(struct rtsx_pdev *pdev) +{ + u8 val; + + val = rtsx_pci_readb(pdev, 0x1C); + return val & 0x0F; +} + +int rts5209_extra_init_hw(struct rtsx_pdev *pdev) +{ + return 0; +} + +int rts5209_optimize_phy(struct rtsx_pdev *pdev) +{ + int err; + + err = rtsx_pci_write_phy_register(pdev, 0x00, 0xB966); + if (err < 0) + return err; + + return 0; +} + +int rts5209_turn_on_led(struct rtsx_pdev *pdev) +{ + int err; + + err = rtsx_pci_write_register(pdev, 0xFD58, 0x01, 0x00); + if (err < 0) + return err; + + return 0; +} + +int rts5209_turn_off_led(struct rtsx_pdev *pdev) +{ + int err; + + err = rtsx_pci_write_register(pdev, 0xFD58, 0x01, 0x01); + if (err < 0) + return err; + + return 0; +} + +int rts5209_enable_auto_blink(struct rtsx_pdev *pdev) +{ + return 0; +} + +int rts5209_disable_auto_blink(struct rtsx_pdev *pdev) +{ + return 0; +} diff --git a/drivers/misc/realtek_cr/pci/rts5209.h b/drivers/misc/realtek_cr/pci/rts5209.h new file mode 100644 index 0000000..b4f0e11 --- /dev/null +++ b/drivers/misc/realtek_cr/pci/rts5209.h @@ -0,0 +1,36 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009 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 as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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: + * Wei WANG <wei_wang@xxxxxxxxxxxxxx> + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#ifndef __RTSX_PCI_RTS5209_H +#define __RTSX_PCI_RTS5209_H + +struct rtsx_pdev; + +u8 rts5209_get_ic_version(struct rtsx_pdev *pdev); +int rts5209_extra_init_hw(struct rtsx_pdev *pdev); +int rts5209_optimize_phy(struct rtsx_pdev *pdev); +int rts5209_turn_on_led(struct rtsx_pdev *pdev); +int rts5209_turn_off_led(struct rtsx_pdev *pdev); +int rts5209_enable_auto_blink(struct rtsx_pdev *pdev); +int rts5209_disable_auto_blink(struct rtsx_pdev *pdev); + +#endif diff --git a/drivers/misc/realtek_cr/pci/rts5229.c b/drivers/misc/realtek_cr/pci/rts5229.c new file mode 100644 index 0000000..09f3bc4 --- /dev/null +++ b/drivers/misc/realtek_cr/pci/rts5229.c @@ -0,0 +1,115 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009 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 as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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: + * Wei WANG <wei_wang@xxxxxxxxxxxxxx> + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#include <linux/pci.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/highmem.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/rtsx_core.h> + +#include "rtsx_pci.h" +#include "rts5229.h" + +u8 rts5229_get_ic_version(struct rtsx_pdev *pdev) +{ + u8 val; + + rtsx_pci_read_register(pdev, 0xFE90, &val); + return val & 0x0F; +} + +int rts5229_extra_init_hw(struct rtsx_pdev *pdev) +{ + int err; + + rtsx_pci_init_cmd(pdev); + + /* Switch LDO3318 source from DV33 to card_3v3 */ + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, 0xFE78, 0x03, 0x00); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, 0xFE78, 0x03, 0x01); + /* LED shine disabled, set initial shine cycle period */ + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, 0xFC1E, 0x0F, 0x02); + + err = rtsx_pci_send_cmd(pdev, 100); + if (err < 0) + return err; + + return 0; +} + +int rts5229_optimize_phy(struct rtsx_pdev *pdev) +{ + int err; + + /* Optimize RX sensitivity */ + err = rtsx_pci_write_phy_register(pdev, 0x00, 0xBA42); + if (err < 0) + return err; + + return 0; +} + +int rts5229_turn_on_led(struct rtsx_pdev *pdev) +{ + int err; + + err = rtsx_pci_write_register(pdev, 0xFC1F, 0x02, 0x02); + if (err < 0) + return err; + + return 0; +} + +int rts5229_turn_off_led(struct rtsx_pdev *pdev) +{ + int err; + + err = rtsx_pci_write_register(pdev, 0xFC1F, 0x02, 0x00); + if (err < 0) + return err; + + return 0; +} + +int rts5229_enable_auto_blink(struct rtsx_pdev *pdev) +{ + int err; + + err = rtsx_pci_write_register(pdev, 0xFC1E, 0x08, 0x08); + if (err < 0) + return err; + + return 0; +} + +int rts5229_disable_auto_blink(struct rtsx_pdev *pdev) +{ + int err; + + err = rtsx_pci_write_register(pdev, 0xFC1E, 0x08, 0x00); + if (err < 0) + return err; + + return 0; +} diff --git a/drivers/misc/realtek_cr/pci/rts5229.h b/drivers/misc/realtek_cr/pci/rts5229.h new file mode 100644 index 0000000..efec550 --- /dev/null +++ b/drivers/misc/realtek_cr/pci/rts5229.h @@ -0,0 +1,36 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009 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 as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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: + * Wei WANG <wei_wang@xxxxxxxxxxxxxx> + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#ifndef __RTSX_PCI_RTS5229_H +#define __RTSX_PCI_RTS5229_H + +struct rtsx_pdev; + +u8 rts5229_get_ic_version(struct rtsx_pdev *pdev); +int rts5229_extra_init_hw(struct rtsx_pdev *pdev); +int rts5229_optimize_phy(struct rtsx_pdev *pdev); +int rts5229_turn_on_led(struct rtsx_pdev *pdev); +int rts5229_turn_off_led(struct rtsx_pdev *pdev); +int rts5229_enable_auto_blink(struct rtsx_pdev *pdev); +int rts5229_disable_auto_blink(struct rtsx_pdev *pdev); + +#endif diff --git a/drivers/misc/realtek_cr/pci/rtsx_pci.c b/drivers/misc/realtek_cr/pci/rtsx_pci.c new file mode 100644 index 0000000..2ec323d --- /dev/null +++ b/drivers/misc/realtek_cr/pci/rtsx_pci.c @@ -0,0 +1,1518 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009 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 as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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: + * Wei WANG <wei_wang@xxxxxxxxxxxxxx> + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#include <linux/pci.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/highmem.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/rtsx_core.h> + +#include <asm/unaligned.h> + +#include "rtsx_pci.h" +#include "sdmmc.h" +#include "rts5209.h" +#include "rts5229.h" + +static bool msi_en = true; +module_param(msi_en, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(msi_en, "Enable MSI"); + +static DEFINE_PCI_DEVICE_TABLE(rtsx_pci_ids) = { + {0x10ec, 0x5209, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0,}, + {0x10ec, 0x5229, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0,}, + {0,} +}; + +MODULE_DEVICE_TABLE(pci, rtsx_pci_ids); + +void rtsx_pci_start_run(struct rtsx_pdev *pdev) +{ + /* if pci device removed, don't queue idle work any more */ + if (pdev->remove_pci) + return; + + if (pdev->state != PDEV_STAT_RUN) { + pdev->state = PDEV_STAT_RUN; + pdev->ops->enable_auto_blink(pdev); + } + + cancel_delayed_work(&pdev->idle_work); + rtsx_queue_delayed_work(&pdev->idle_work, msecs_to_jiffies(200)); +} + +int rtsx_pci_write_register(struct rtsx_pdev *pdev, u16 addr, u8 mask, u8 data) +{ + int i; + u32 val = 3 << 30; + + val |= (u32)(addr & 0x3FFF) << 16; + val |= (u32)mask << 8; + val |= (u32)data; + + rtsx_pci_writel(pdev, RTSX_HAIMR, val); + + for (i = 0; i < MAX_RW_REG_CNT; i++) { + val = rtsx_pci_readl(pdev, RTSX_HAIMR); + if ((val & (1 << 31)) == 0) { + if (data != (u8)val) + return -EIO; + return 0; + } + } + + return -ETIMEDOUT; +} + +int rtsx_pci_read_register(struct rtsx_pdev *pdev, u16 addr, u8 *data) +{ + u32 val = 2 << 30; + int i; + + val |= (u32)(addr & 0x3FFF) << 16; + rtsx_pci_writel(pdev, RTSX_HAIMR, val); + + for (i = 0; i < MAX_RW_REG_CNT; i++) { + val = rtsx_pci_readl(pdev, RTSX_HAIMR); + if ((val & (1 << 31)) == 0) + break; + } + + if (i >= MAX_RW_REG_CNT) + return -ETIMEDOUT; + + if (data) + *data = (u8)(val & 0xFF); + + return 0; +} + +int rtsx_pci_write_phy_register(struct rtsx_pdev *pdev, u8 addr, u16 val) +{ + int err, i, finished = 0; + u8 tmp; + + rtsx_pci_init_cmd(pdev); + + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, PHYDATA0, 0xFF, (u8)val); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, PHYDATA1, 0xFF, (u8)(val >> 8)); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, PHYADDR, 0xFF, addr); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, PHYRWCTL, 0xFF, 0x81); + + err = rtsx_pci_send_cmd(pdev, 100); + if (err < 0) + return err; + + for (i = 0; i < 100000; i++) { + err = rtsx_pci_read_register(pdev, PHYRWCTL, &tmp); + if (err < 0) + return err; + + if (!(tmp & 0x80)) { + finished = 1; + break; + } + } + + if (!finished) + return -ETIMEDOUT; + + return 0; +} + +int rtsx_pci_read_phy_register(struct rtsx_pdev *pdev, u8 addr, u16 *val) +{ + int err, i, finished = 0; + u16 data = 0; + u8 *ptr, tmp; + + rtsx_pci_init_cmd(pdev); + + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, PHYADDR, 0xFF, addr); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, PHYRWCTL, 0xFF, 0x80); + + err = rtsx_pci_send_cmd(pdev, 100); + if (err < 0) + return err; + + for (i = 0; i < 100000; i++) { + err = rtsx_pci_read_register(pdev, PHYRWCTL, &tmp); + if (err < 0) + return err; + + if (!(tmp & 0x80)) { + finished = 1; + break; + } + } + + if (!finished) + return -ETIMEDOUT; + + rtsx_pci_init_cmd(pdev); + + rtsx_pci_add_cmd(pdev, READ_REG_CMD, PHYDATA0, 0, 0); + rtsx_pci_add_cmd(pdev, READ_REG_CMD, PHYDATA1, 0, 0); + + err = rtsx_pci_send_cmd(pdev, 100); + if (err < 0) + return err; + + ptr = rtsx_pci_get_cmd_data(pdev); + data = ((u16)ptr[1] << 8) || ptr[0]; + + if (val) + *val = data; + + return 0; +} + +void rtsx_pci_stop_cmd(struct rtsx_pdev *pdev) +{ + rtsx_pci_writel(pdev, RTSX_HCBCTLR, STOP_CMD); + rtsx_pci_writel(pdev, RTSX_HDBCTLR, STOP_DMA); + + rtsx_pci_write_register(pdev, DMACTL, 0x80, 0x80); + rtsx_pci_write_register(pdev, RBCTL, 0x80, 0x80); +} + +void rtsx_pci_add_cmd(struct rtsx_pdev *pdev, + u8 cmd_type, u16 reg_addr, u8 mask, u8 data) +{ + unsigned long flags; + u32 val = 0; + u32 *ptr = (u32 *)(pdev->host_cmds_ptr); + + val |= (u32)(cmd_type & 0x03) << 30; + val |= (u32)(reg_addr & 0x3FFF) << 16; + val |= (u32)mask << 8; + val |= (u32)data; + + spin_lock_irqsave(&pdev->lock, flags); + ptr += pdev->ci; + if (pdev->ci < (HOST_CMDS_BUF_LEN / 4)) { + put_unaligned_le32(val, ptr); + ptr++; + pdev->ci++; + } + spin_unlock_irqrestore(&pdev->lock, flags); +} + +void rtsx_pci_send_cmd_no_wait(struct rtsx_pdev *pdev) +{ + u32 val = 1 << 31; + + rtsx_pci_writel(pdev, RTSX_HCBAR, pdev->host_cmds_addr); + + val |= (u32)(pdev->ci * 4) & 0x00FFFFFF; + /* Hardware Auto Response */ + val |= 0x40000000; + rtsx_pci_writel(pdev, RTSX_HCBCTLR, val); +} + +int rtsx_pci_send_cmd(struct rtsx_pdev *pdev, int timeout) +{ + struct completion trans_done; + u32 val = 1 << 31; + long timeleft; + unsigned long flags; + int err = 0; + + spin_lock_irqsave(&pdev->lock, flags); + + /* set up data structures for the wakeup system */ + pdev->done = &trans_done; + pdev->trans_result = TRANS_NOT_READY; + init_completion(&trans_done); + pdev->trans_state = STATE_TRANS_CMD; + + rtsx_pci_writel(pdev, RTSX_HCBAR, pdev->host_cmds_addr); + + val |= (u32)(pdev->ci * 4) & 0x00FFFFFF; + /* Hardware Auto Response */ + val |= 0x40000000; + rtsx_pci_writel(pdev, RTSX_HCBCTLR, val); + + spin_unlock_irqrestore(&pdev->lock, flags); + + /* Wait for TRANS_OK_INT */ + timeleft = wait_for_completion_interruptible_timeout( + &trans_done, msecs_to_jiffies(timeout)); + if (timeleft <= 0) { + dev_dbg(&(pdev->pci->dev), "pdev->int_reg = 0x%x\n", + pdev->int_reg); + err = -ETIMEDOUT; + goto finish_send_cmd; + } + + spin_lock_irqsave(&pdev->lock, flags); + if (pdev->trans_result == TRANS_RESULT_FAIL) + err = -EINVAL; + else if (pdev->trans_result == TRANS_RESULT_OK) + err = 0; + else if (pdev->trans_result == TRANS_NO_DEVICE) + err = -ENODEV; + spin_unlock_irqrestore(&pdev->lock, flags); + +finish_send_cmd: + spin_lock_irqsave(&pdev->lock, flags); + pdev->done = NULL; + pdev->trans_state = STATE_TRANS_NONE; + spin_unlock_irqrestore(&pdev->lock, flags); + + if ((err < 0) && (err != -ENODEV)) + rtsx_pci_stop_cmd(pdev); + + if (pdev->finish_me) + complete(pdev->finish_me); + + return err; +} + +/** + * The length field is 20-bit long. So if the buffer length is + * longer than 0x80000, this function will divide the buffer into + * several small buffers to ensure the length field won't overflow. + */ +void rtsx_pci_add_sg_tbl(struct rtsx_pdev *pdev, u32 addr, u32 len, u8 option) +{ + u64 *ptr = (u64 *)(pdev->host_sg_tbl_ptr); + u64 val = 0; + u32 temp_len = 0; + u8 temp_opt = 0; + + ptr += pdev->sgi; + do { + if (len > 0x80000) { + temp_len = 0x80000; + temp_opt = option & (~SG_END); + } else { + temp_len = len; + temp_opt = option; + } + val = ((u64)addr << 32) | ((u64)temp_len << 12) | temp_opt; + + if (pdev->sgi < (HOST_SG_TBL_BUF_LEN / 8)) { + put_unaligned_le64(val, ptr); + ptr++; + pdev->sgi++; + } + + len -= temp_len; + addr += temp_len; + } while (len); +} + +static int rtsx_pci_transfer_sglist_adma(struct rtsx_pdev *pdev, + struct scatterlist *sg, int num_sg, int read, int timeout) +{ + struct completion trans_done; + u8 dir; + int buf_cnt, i; + int err = 0; + long timeleft; + unsigned long flags; + struct scatterlist *sg_ptr; + enum dma_data_direction dma_dir; + + if ((sg == NULL) || (num_sg <= 0)) + return -EINVAL; + + if (read) { + dir = DEVICE_TO_HOST; + dma_dir = DMA_FROM_DEVICE; + } else { + dir = HOST_TO_DEVICE; + dma_dir = DMA_TO_DEVICE; + } + + spin_lock_irqsave(&pdev->lock, flags); + + /* set up data structures for the wakeup system */ + pdev->done = &trans_done; + pdev->trans_state = STATE_TRANS_SG; + pdev->trans_result = TRANS_NOT_READY; + + spin_unlock_irqrestore(&pdev->lock, flags); + + buf_cnt = dma_map_sg(&(pdev->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); + + pdev->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(&(pdev->pci->dev), + "DMA addr: 0x%x, Len: 0x%x\n", + (unsigned int)addr, len); + + if (j == (sg_cnt - 1)) + option = SG_VALID | SG_END | SG_TRANS_DATA; + else + option = SG_VALID | SG_TRANS_DATA; + + rtsx_pci_add_sg_tbl(pdev, (u32)addr, (u32)len, option); + sg_ptr = sg_next(sg_ptr); + } + + dev_dbg(&(pdev->pci->dev), "SG table count = %d\n", pdev->sgi); + + val |= (u32)(dir & 0x01) << 29; + val |= ADMA_MODE; + + spin_lock_irqsave(&pdev->lock, flags); + + init_completion(&trans_done); + + rtsx_pci_writel(pdev, RTSX_HDBAR, pdev->host_sg_tbl_addr); + rtsx_pci_writel(pdev, RTSX_HDBCTLR, val); + + spin_unlock_irqrestore(&pdev->lock, flags); + + timeleft = wait_for_completion_interruptible_timeout( + &trans_done, msecs_to_jiffies(timeout)); + if (timeleft <= 0) { + dev_dbg(&(pdev->pci->dev), "Timeout (%s %d)\n", + __func__, __LINE__); + err = -ETIMEDOUT; + goto out; + } + + spin_lock_irqsave(&pdev->lock, flags); + if (pdev->trans_result == TRANS_RESULT_FAIL) { + err = -EINVAL; + spin_unlock_irqrestore(&pdev->lock, flags); + goto out; + } else if (pdev->trans_result == TRANS_NO_DEVICE) { + err = -ENODEV; + spin_unlock_irqrestore(&pdev->lock, flags); + goto out; + } + spin_unlock_irqrestore(&pdev->lock, flags); + + sg_ptr += sg_cnt; + } + + /* Wait for TRANS_OK_INT */ + spin_lock_irqsave(&pdev->lock, flags); + if (pdev->trans_result == TRANS_NOT_READY) { + init_completion(&trans_done); + spin_unlock_irqrestore(&pdev->lock, flags); + timeleft = wait_for_completion_interruptible_timeout( + &trans_done, msecs_to_jiffies(timeout)); + if (timeleft <= 0) { + dev_dbg(&(pdev->pci->dev), "Timeout (%s %d)\n", + __func__, __LINE__); + err = -ETIMEDOUT; + goto out; + } + } else { + spin_unlock_irqrestore(&pdev->lock, flags); + } + + spin_lock_irqsave(&pdev->lock, flags); + if (pdev->trans_result == TRANS_RESULT_FAIL) + err = -EINVAL; + else if (pdev->trans_result == TRANS_RESULT_OK) + err = 0; + else if (pdev->trans_result == TRANS_NO_DEVICE) + err = -ENODEV; + spin_unlock_irqrestore(&pdev->lock, flags); + +out: + spin_lock_irqsave(&pdev->lock, flags); + pdev->done = NULL; + pdev->trans_state = STATE_TRANS_NONE; + spin_unlock_irqrestore(&pdev->lock, flags); + + dma_unmap_sg(&(pdev->pci->dev), sg, num_sg, dma_dir); + + if ((err < 0) && (err != -ENODEV)) + rtsx_pci_stop_cmd(pdev); + + if (pdev->finish_me) + complete(pdev->finish_me); + + return err; +} + +static int rtsx_pci_transfer_buf(struct rtsx_pdev *pdev, + void *buf, size_t len, int read, int timeout) +{ + struct completion trans_done; + dma_addr_t addr; + u8 dir; + int err = 0; + u32 val = (1 << 31); + long timeleft; + unsigned long flags; + enum dma_data_direction dma_dir; + + if ((buf == NULL) || (len <= 0)) + return -EINVAL; + + if (read) { + dir = DEVICE_TO_HOST; + dma_dir = DMA_FROM_DEVICE; + } else { + dir = HOST_TO_DEVICE; + dma_dir = DMA_TO_DEVICE; + } + + addr = dma_map_single(&(pdev->pci->dev), buf, len, dma_dir); + if (!addr) + return -EINVAL; + + val |= (u32)(dir & 0x01) << 29; + val |= (u32)(len & 0x00FFFFFF); + + spin_lock_irqsave(&pdev->lock, flags); + + /* set up data structures for the wakeup system */ + pdev->done = &trans_done; + + init_completion(&trans_done); + + pdev->trans_state = STATE_TRANS_BUF; + pdev->trans_result = TRANS_NOT_READY; + + rtsx_pci_writel(pdev, RTSX_HDBAR, addr); + rtsx_pci_writel(pdev, RTSX_HDBCTLR, val); + + spin_unlock_irqrestore(&pdev->lock, flags); + + /* Wait for TRANS_OK_INT */ + timeleft = wait_for_completion_interruptible_timeout(&trans_done, + msecs_to_jiffies(timeout)); + if (timeleft <= 0) { + dev_dbg(&(pdev->pci->dev), "Timeout (%s %d)\n", + __func__, __LINE__); + err = -ETIMEDOUT; + goto out; + } + + spin_lock_irqsave(&pdev->lock, flags); + if (pdev->trans_result == TRANS_RESULT_FAIL) + err = -EINVAL; + else if (pdev->trans_result == TRANS_RESULT_OK) + err = 0; + else if (pdev->trans_result == TRANS_NO_DEVICE) + err = -ENODEV; + spin_unlock_irqrestore(&pdev->lock, flags); + +out: + spin_lock_irqsave(&pdev->lock, flags); + pdev->done = NULL; + pdev->trans_state = STATE_TRANS_NONE; + spin_unlock_irqrestore(&pdev->lock, flags); + + dma_unmap_single(&(pdev->pci->dev), addr, len, dma_dir); + + if ((err < 0) && (err != -ENODEV)) + rtsx_pci_stop_cmd(pdev); + + if (pdev->finish_me) + complete(pdev->finish_me); + + return err; +} + +int rtsx_pci_transfer_data(struct rtsx_pdev *pdev, + void *buf, size_t len, int use_sg, int read, int timeout) +{ + int err = 0; + + dev_dbg(&(pdev->pci->dev), "use_sg = %d\n", use_sg); + + /* don't transfer data during abort processing */ + if (pdev->remove_pci) + return -EINVAL; + + if (use_sg) { + err = rtsx_pci_transfer_sglist_adma(pdev, + (struct scatterlist *)buf, use_sg, + read, timeout); + } else { + err = rtsx_pci_transfer_buf(pdev, buf, len, read, timeout); + } + + return err; +} + +int rtsx_pci_read_ppbuf(struct rtsx_pdev *pdev, u8 *buf, int buf_len) +{ + int err; + int i, j; + u16 reg; + u8 *ptr; + + if (buf_len > 512) + buf_len = 512; + + ptr = buf; + reg = PPBUF_BASE2; + for (i = 0; i < buf_len / 256; i++) { + rtsx_pci_init_cmd(pdev); + + for (j = 0; j < 256; j++) + rtsx_pci_add_cmd(pdev, READ_REG_CMD, reg++, 0, 0); + + err = rtsx_pci_send_cmd(pdev, 250); + if (err < 0) + return err; + + memcpy(ptr, rtsx_pci_get_cmd_data(pdev), 256); + ptr += 256; + } + + if (buf_len % 256) { + rtsx_pci_init_cmd(pdev); + + for (j = 0; j < buf_len % 256; j++) + rtsx_pci_add_cmd(pdev, READ_REG_CMD, reg++, 0, 0); + + err = rtsx_pci_send_cmd(pdev, 250); + if (err < 0) + return err; + } + + memcpy(ptr, rtsx_pci_get_cmd_data(pdev), buf_len % 256); + + return 0; +} + +int rtsx_pci_write_ppbuf(struct rtsx_pdev *pdev, u8 *buf, int buf_len) +{ + int err; + int i, j; + u16 reg; + u8 *ptr; + + if (buf_len > 512) + buf_len = 512; + + ptr = buf; + reg = PPBUF_BASE2; + for (i = 0; i < buf_len / 256; i++) { + rtsx_pci_init_cmd(pdev); + + for (j = 0; j < 256; j++) { + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, + reg++, 0xFF, *ptr); + ptr++; + } + + err = rtsx_pci_send_cmd(pdev, 250); + if (err < 0) + return err; + } + + if (buf_len % 256) { + rtsx_pci_init_cmd(pdev); + + for (j = 0; j < buf_len % 256; j++) { + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, + reg++, 0xFF, *ptr); + ptr++; + } + + err = rtsx_pci_send_cmd(pdev, 250); + if (err < 0) + return err; + } + + return 0; +} + +int rtsx_pci_set_pull_ctl(struct rtsx_pdev *pdev, + const struct rtsx_reg_pair *tbl) +{ + int err; + + rtsx_pci_init_cmd(pdev); + + while (tbl->addr) { + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, + tbl->addr, 0xFF, tbl->val); + tbl++; + } + + err = rtsx_pci_send_cmd(pdev, 100); + if (err < 0) + return err; + + return 0; +} + +static void rtsx_pci_enable_bus_int(struct rtsx_pdev *pdev) +{ + u32 reg = 0; + + reg = TRANS_OK_INT_EN | TRANS_FAIL_INT_EN; + + reg |= SD_INT_EN; + + if (pdev->ic_version >= IC_VER_C) + reg |= DELINK_INT_EN; + reg |= SD_OC_INT_EN; + + /* Enable Bus Interrupt */ + rtsx_pci_writel(pdev, RTSX_BIER, reg); + + dev_dbg(&(pdev->pci->dev), "RTSX_BIER: 0x%08x\n", reg); +} + +static inline u8 double_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_4M; + } + + return ssc_depth; +} + +static int switch_ssc_clock(struct rtsx_pdev *pdev, unsigned int card_clock, + u8 ssc_depth, int double_clk, int vpclk) +{ + struct sd_info *sd_card = &(pdev->sd_card); + int err, clk; + u8 N, min_N, max_N, clk_divider; + u8 mcu_cnt, div, max_div; + u8 depth[] = { + [RTSX_SSC_DEPTH_4M] = SSC_DEPTH_4M, + [RTSX_SSC_DEPTH_2M] = SSC_DEPTH_2M, + [RTSX_SSC_DEPTH_1M] = SSC_DEPTH_1M, + [RTSX_SSC_DEPTH_500K] = SSC_DEPTH_500K, + [RTSX_SSC_DEPTH_250K] = SSC_DEPTH_250K, + }; + + if (card_clock <= 1000000) { + /* We use 250k(around) here, in initial stage */ + clk_divider = SD_CLK_DIVIDE_128; + card_clock = 30000000; + sd_card->initial_mode = 1; + } else { + clk_divider = SD_CLK_DIVIDE_0; + sd_card->initial_mode = 0; + } + err = rtsx_pci_write_register(pdev, SD_CFG1, + SD_CLK_DIVIDE_MASK, clk_divider); + if (err < 0) + return err; + + card_clock /= 1000000; + dev_dbg(&(pdev->pci->dev), "Switch card clock to %dMHz\n", card_clock); + + min_N = 80; + max_N = 208; + max_div = CLK_DIV_8; + + clk = card_clock; + if (!sd_card->initial_mode && double_clk) + clk = card_clock * 2; + dev_dbg(&(pdev->pci->dev), "Internal SSC clock: %dMHz\n", clk); + + N = (u8)(clk - 2); + if ((clk <= 2) || (N > max_N)) + return -EINVAL; + + mcu_cnt = (u8)(125/clk + 3); + if (mcu_cnt > 15) + mcu_cnt = 15; + + /* Make sure that the SSC clock div_n is equal or greater than min_N */ + div = CLK_DIV_1; + while ((N < min_N) && (div < max_div)) { + N = (N + 2) * 2 - 2; + div++; + } + dev_dbg(&(pdev->pci->dev), "N = %d, div = %d\n", N, div); + + ssc_depth = depth[ssc_depth]; + if (double_clk) + ssc_depth = double_depth(ssc_depth); + + ssc_depth = revise_ssc_depth(ssc_depth, div); + dev_dbg(&(pdev->pci->dev), "ssc_depth = %d\n", ssc_depth); + + rtsx_pci_init_cmd(pdev); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CLK_CTL, + CLK_LOW_FREQ, CLK_LOW_FREQ); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CLK_DIV, + 0xFF, (div << 4) | mcu_cnt); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SSC_CTL1, SSC_RSTB, 0); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SSC_CTL2, + SSC_DEPTH_MASK, ssc_depth); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SSC_DIV_N_0, 0xFF, N); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SSC_CTL1, SSC_RSTB, SSC_RSTB); + if (vpclk) { + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_VPCLK0_CTL, + PHASE_NOT_RESET, 0); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_VPCLK0_CTL, + PHASE_NOT_RESET, PHASE_NOT_RESET); + } + + err = rtsx_pci_send_cmd(pdev, 2000); + if (err < 0) + return err; + + /* Wait SSC clock stable */ + udelay(10); + err = rtsx_pci_write_register(pdev, CLK_CTL, CLK_LOW_FREQ, 0); + if (err < 0) + return err; + + return 0; +} + +static irqreturn_t rtsx_pci_isr(int irq, void *dev_id) +{ + struct rtsx_pdev *pdev = dev_id; + u32 int_enable, int_reg; + + if (!pdev) + return IRQ_NONE; + + spin_lock(&pdev->lock); + + int_enable = rtsx_pci_readl(pdev, RTSX_BIER); + int_reg = rtsx_pci_readl(pdev, RTSX_BIPR); + /* Clear interrupt flag */ + rtsx_pci_writel(pdev, RTSX_BIPR, int_reg); + if ((int_reg & int_enable) == 0) { + spin_unlock(&pdev->lock); + return IRQ_NONE; + } + if (int_reg == 0xFFFFFFFF) { + spin_unlock(&pdev->lock); + return IRQ_HANDLED; + } + + int_reg &= (int_enable | 0x7FFFFF); + + if (int_reg & SD_INT) { + if (int_reg & SD_EXIST) { + pdev->need_reset |= SD_EXIST; + } else { + pdev->need_release |= SD_EXIST; + pdev->need_reset &= ~SD_EXIST; + } + } + + if (pdev->need_reset || pdev->need_release) + rtsx_queue_delayed_work(&pdev->carddet_work, + msecs_to_jiffies(200)); + + if (int_reg & (NEED_COMPLETE_INT | DELINK_INT)) { + if (int_reg & (TRANS_FAIL_INT | DELINK_INT)) { + pdev->trans_result = TRANS_RESULT_FAIL; + if (pdev->done) + complete(pdev->done); + } else if (int_reg & TRANS_OK_INT) { + pdev->trans_result = TRANS_RESULT_OK; + if (pdev->done) + complete(pdev->done); + } else if (int_reg & DATA_DONE_INT) { + pdev->trans_result = TRANS_NOT_READY; + if (pdev->done && (pdev->trans_state == STATE_TRANS_SG)) + complete(pdev->done); + } + } + + spin_unlock(&pdev->lock); + return IRQ_HANDLED; +} + +static int rtsx_pci_acquire_irq(struct rtsx_pdev *pdev) +{ + dev_info(&(pdev->pci->dev), "%s: pdev->msi_en = %d, pci->irq = %d\n", + __func__, pdev->msi_en, pdev->pci->irq); + + if (request_irq(pdev->pci->irq, rtsx_pci_isr, + pdev->msi_en ? 0 : IRQF_SHARED, + DRV_NAME, pdev)) { + dev_err(&(pdev->pci->dev), + "rtsx_sdmmc: unable to grab IRQ %d, disabling device\n", + pdev->pci->irq); + return -1; + } + + pdev->irq = pdev->pci->irq; + pci_intx(pdev->pci, !pdev->msi_en); + + return 0; +} + +static unsigned char get_card_type(u32 card_status) +{ + unsigned char type; + + switch (card_status) { + case XD_EXIST: + type = RTSX_TYPE_XD; + break; + + case MS_EXIST: + type = RTSX_TYPE_MS; + break; + + case SD_EXIST: + type = RTSX_TYPE_SD; + break; + + default: + type = 0; + break; + } + + return type; +} + +static u32 get_card_status(unsigned char type) +{ + u32 card_status; + + switch (type) { + case RTSX_TYPE_XD: + card_status = XD_EXIST; + break; + + case RTSX_TYPE_MS: + card_status = MS_EXIST; + break; + + case RTSX_TYPE_SD: + card_status = SD_EXIST; + break; + + default: + card_status = 0; + break; + } + + return card_status; +} + +static int find_empty_socket(struct rtsx_adapter *adapter) +{ + int i; + int sock_id = -1; + + for (i = 0; i < adapter->num_sockets; i++) { + if (!adapter->sockets[i]) { + sock_id = i; + break; + } + } + + return sock_id; +} + +static void rtsx_pci_card_detect(struct work_struct *work) +{ + struct delayed_work *dwork; + struct rtsx_pdev *pdev; + struct rtsx_adapter *adapter; + struct rtsx_dev *sock; + int err, i; + unsigned long flags; + u32 irq_status; + + dwork = to_delayed_work(work); + pdev = container_of(dwork, struct rtsx_pdev, carddet_work); + + dev_dbg(&(pdev->pci->dev), "--> %s\n", __func__); + + spin_lock_irqsave(&pdev->lock, flags); + + adapter = pdev->adapter; + + irq_status = rtsx_pci_readl(pdev, RTSX_BIPR); + dev_dbg(&(pdev->pci->dev), "irq_status: 0x%08x\n", irq_status); + + if (pdev->need_release) { + /* Card unplugged, unregister device */ + u32 need_release = pdev->need_release; + + for (i = 0; i < adapter->num_sockets; i++) { + u32 card_status; + + sock = adapter->sockets[i]; + if (!sock) + continue; + + card_status = get_card_status(sock->type); + + if (!(need_release & card_status)) + continue; + + pdev->need_release &= ~card_status; + + if (!(irq_status & card_status)) { + adapter->sockets[i] = NULL; + spin_unlock_irqrestore(&pdev->lock, flags); + device_unregister(&sock->dev); + spin_lock_irqsave(&pdev->lock, flags); + } + + } + } + + if (pdev->need_reset) { + /* Card plugged in, register device */ + u32 need_reset; + u32 card_status[] = {XD_EXIST, MS_EXIST, SD_EXIST}; + int id; + + pdev->need_reset &= irq_status; + if (pdev->need_reset == 0) + goto out; + need_reset = pdev->need_reset; + + for (i = 0; i < sizeof(card_status)/sizeof(u32); i++) { + if (need_reset & card_status[i]) { + id = find_empty_socket(adapter); + if (id < 0) { + dev_err(&(pdev->pci->dev), + "No empty socket left!\n"); + goto out; + } + } else { + continue; + } + + pdev->need_reset &= ~card_status[i]; + + spin_unlock_irqrestore(&pdev->lock, flags); + sock = rtsx_alloc_device(adapter, 0, + get_card_type(card_status[i])); + if (sock) { + err = device_register(&sock->dev); + if (!err) + adapter->sockets[id] = sock; + else + rtsx_free_device(&sock->dev); + } + spin_lock_irqsave(&pdev->lock, flags); + } + } + +out: + spin_unlock_irqrestore(&pdev->lock, flags); +} + +static void rtsx_pci_enter_idle(struct work_struct *work) +{ + struct delayed_work *dwork; + struct rtsx_pdev *pdev; + + dwork = to_delayed_work(work); + pdev = container_of(dwork, struct rtsx_pdev, idle_work); + + dev_dbg(&(pdev->pci->dev), "--> %s\n", __func__); + + mutex_lock(&pdev->pdev_mutex); + + pdev->state = PDEV_STAT_IDLE; + + pdev->ops->disable_auto_blink(pdev); + pdev->ops->turn_off_led(pdev); + + mutex_unlock(&pdev->pdev_mutex); +} + +static const struct pdev_ops rts5209_pdev_ops = { + .extra_init_hw = rts5209_extra_init_hw, + .optimize_phy = rts5209_optimize_phy, + .turn_on_led = rts5209_turn_on_led, + .turn_off_led = rts5209_turn_off_led, + .enable_auto_blink = rts5209_enable_auto_blink, + .disable_auto_blink = rts5209_disable_auto_blink, +}; + +static const struct pdev_ops rts5229_pdev_ops = { + .extra_init_hw = rts5229_extra_init_hw, + .optimize_phy = rts5229_optimize_phy, + .turn_on_led = rts5229_turn_on_led, + .turn_off_led = rts5229_turn_off_led, + .enable_auto_blink = rts5229_enable_auto_blink, + .disable_auto_blink = rts5229_disable_auto_blink, +}; + +static int rtsx_pci_init_hw(struct rtsx_pdev *pdev) +{ + int err; + + rtsx_pci_writel(pdev, RTSX_HCBAR, pdev->host_cmds_addr); + + rtsx_pci_enable_bus_int(pdev); + + /* Power on SSC */ + err = rtsx_pci_write_register(pdev, FPDCTL, SSC_POWER_DOWN, 0); + if (err < 0) + return err; + + /* Wait SSC power stable */ + udelay(200); + + err = pdev->ops->optimize_phy(pdev); + if (err < 0) + return err; + + rtsx_pci_init_cmd(pdev); + + /* Set mcu_cnt to 7 to ensure data can be sampled properly */ + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CLK_DIV, 0x07, 0x07); + + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, HOST_SLEEP_STATE, 0x03, 0x00); + /* Disable card clock */ + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CARD_CLK_EN, 0x1E, 0); + /* Reset ASPM state to default value */ + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, ASPM_FORCE_CTL, 0x3F, 0); + /* Card driving select */ + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, + SD30_DRIVE_SEL, 0x07, DRIVER_TYPE_D); + /* Enlarge the estimation window of PERST# glitch + * to reduce the chance of invalid card interrupt + */ + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, PERST_GLITCH_WIDTH, 0xFF, 0x80); + /* Update RC oscillator to 400k + * bit[0] F_HIGH: for RC oscillator, Rst_value is 1'b1 + * 1: 2M 0: 400k + */ + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, RCCTL, 0x01, 0x00); + /* Set interrupt write clear + * bit 1: U_elbi_if_rd_clr_en + * 1: Enable ELBI interrupt[31:22] & [7:0] flag read clear + * 0: ELBI interrupt flag[31:22] & [7:0] only can be write clear + */ + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, NFTS_TX_CTRL, 0x02, 0); + /* Force CLKREQ# PIN to drive 0 to request clock */ + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, PETXCFG, 0x08, 0x08); + + err = rtsx_pci_send_cmd(pdev, 100); + if (err < 0) + return err; + + err = pdev->ops->extra_init_hw(pdev); + if (err < 0) + return err; + + return 0; +} + +/* SD Pull Control Enable: + * SD_DAT[3:0] ==> pull up + * SD_CD ==> pull up + * SD_WP ==> pull up + * SD_CMD ==> pull up + * SD_CLK ==> pull down + */ +static const struct rtsx_reg_pair rts5209_sd_pull_ctl_enable_tbl[] = { + {CARD_PULL_CTL1, 0xAA}, + {CARD_PULL_CTL2, 0xAA}, + {CARD_PULL_CTL3, 0xE9}, + {0, 0}, +}; + +static const struct rtsx_reg_pair rts5229_sd_pull_ctl_enable_tbl1[] = { + {CARD_PULL_CTL2, 0xAA}, + {CARD_PULL_CTL3, 0xE9}, + {0, 0}, +}; + +/* For RTS5229 version C */ +static const struct rtsx_reg_pair rts5229_sd_pull_ctl_enable_tbl2[] = { + {CARD_PULL_CTL2, 0xAA}, + {CARD_PULL_CTL3, 0xD9}, + {0, 0}, +}; + +/* SD Pull Control Disable: + * SD_DAT[3:0] ==> pull down + * SD_CD ==> pull up + * SD_WP ==> pull down + * SD_CMD ==> pull down + * SD_CLK ==> pull down + */ +static const struct rtsx_reg_pair rts5209_sd_pull_ctl_disable_tbl[] = { + {CARD_PULL_CTL1, 0x55}, + {CARD_PULL_CTL2, 0x55}, + {CARD_PULL_CTL3, 0xD5}, + {0, 0}, +}; + +static const struct rtsx_reg_pair rts5229_sd_pull_ctl_disable_tbl1[] = { + {CARD_PULL_CTL2, 0x55}, + {CARD_PULL_CTL3, 0xD5}, + {0, 0}, +}; + +/* For RTS5229 version C */ +static const struct rtsx_reg_pair rts5229_sd_pull_ctl_disable_tbl2[] = { + {CARD_PULL_CTL2, 0x55}, + {CARD_PULL_CTL3, 0xE5}, + {0, 0}, +}; + +static void rtsx_pci_init_chip(struct rtsx_pdev *pdev) +{ + struct rtsx_adapter *adapter = pdev->adapter; + u32 irq_status; + + spin_lock_init(&pdev->lock); + mutex_init(&pdev->pdev_mutex); + + switch (PCI_PID(pdev)) { + default: + case 0x5209: + adapter->extra_caps = EXTRA_CAPS_SD_SDR50 | + EXTRA_CAPS_SD_SDR104 | EXTRA_CAPS_MMC_8BIT; + + pdev->rval.ldo_pwr_on = 0x00; + pdev->rval.ldo_pwr_off = 0x06; + pdev->rval.ldo_pwr_suspend = 0x04; + + pdev->ops = &rts5209_pdev_ops; + + pdev->ic_version = rts5209_get_ic_version(pdev); + pdev->sd_pull_ctl_enable_tbl = rts5209_sd_pull_ctl_enable_tbl; + pdev->sd_pull_ctl_disable_tbl = rts5209_sd_pull_ctl_disable_tbl; + + break; + + case 0x5229: + adapter->extra_caps = EXTRA_CAPS_SD_SDR50 | + EXTRA_CAPS_SD_SDR104; + + pdev->rval.ldo_pwr_on = 0x06; + pdev->rval.ldo_pwr_off = 0x00; + pdev->rval.ldo_pwr_suspend = 0x02; + + pdev->ops = &rts5229_pdev_ops; + + pdev->ic_version = rts5229_get_ic_version(pdev); + if (pdev->ic_version == IC_VER_C) { + pdev->sd_pull_ctl_enable_tbl = + rts5229_sd_pull_ctl_enable_tbl2; + pdev->sd_pull_ctl_disable_tbl = + rts5229_sd_pull_ctl_disable_tbl2; + } else { + pdev->sd_pull_ctl_enable_tbl = + rts5229_sd_pull_ctl_enable_tbl1; + pdev->sd_pull_ctl_disable_tbl = + rts5229_sd_pull_ctl_disable_tbl1; + } + + break; + } + + dev_dbg(&(pdev->pci->dev), "PID: 0x%04x, IC version: 0x%02x\n", + PCI_PID(pdev), pdev->ic_version); + + pdev->state = PDEV_STAT_IDLE; + rtsx_pci_init_hw(pdev); + + irq_status = rtsx_pci_readl(pdev, RTSX_BIPR); + /* Clear interrupt flag */ + rtsx_pci_writel(pdev, RTSX_BIPR, irq_status); + if (irq_status & SD_EXIST) + pdev->need_reset |= SD_EXIST; + + if (pdev->need_reset) + rtsx_queue_delayed_work(&pdev->carddet_work, 0); +} + +static int rtsx_pci_switch_clock(struct rtsx_adapter *adapter, + unsigned int card_clock, u8 ssc_depth, + int double_clk, int vpclk) +{ + struct rtsx_pdev *pdev = dev_get_drvdata(adapter->dev.parent); + int err; + + mutex_lock(&pdev->pdev_mutex); + err = switch_ssc_clock(pdev, card_clock, ssc_depth, double_clk, vpclk); + mutex_unlock(&pdev->pdev_mutex); + return err; +} + +static void rtsx_pci_complete_unfinished_transfer(struct rtsx_adapter *adapter) +{ + struct rtsx_pdev *pdev = dev_get_drvdata(adapter->dev.parent); + struct completion finish; + + pdev->finish_me = &finish; + init_completion(&finish); + + if (pdev->done) + complete(pdev->done); + + if (!pdev->remove_pci) + rtsx_pci_stop_cmd(pdev); + + wait_for_completion_interruptible_timeout(&finish, + msecs_to_jiffies(2)); + pdev->finish_me = NULL; +} + +static const struct rtsx_common_ops rtsx_pci_common_ops = { + .switch_clock = rtsx_pci_switch_clock, + .complete_unfinished_transfer = + rtsx_pci_complete_unfinished_transfer, +}; + +static const struct rtsx_sdmmc_ops rtsx_pci_sdmmc_ops = { + .sdmmc_set_bus_width = pci_sdmmc_set_bus_width, + .sdmmc_set_power_mode = pci_sdmmc_set_power_mode, + .sdmmc_set_timing = pci_sdmmc_set_timing, + .sdmmc_switch_voltage = pci_sdmmc_switch_voltage, + .sdmmc_get_ro = pci_sdmmc_get_ro, + .sdmmc_get_cd = pci_sdmmc_get_cd, + .sdmmc_execute_tuning = pci_sdmmc_execute_tuning, + .sdmmc_send_cmd_get_rsp = pci_sdmmc_send_cmd_get_rsp, + .sdmmc_read_data = pci_sdmmc_read_data, + .sdmmc_write_data = pci_sdmmc_write_data, + .sdmmc_rw_multi = pci_sdmmc_rw_multi, +}; + +static int __devinit rtsx_pci_probe(struct pci_dev *pcidev, + const struct pci_device_id *id) +{ + struct rtsx_pdev *pdev; + struct rtsx_adapter *adapter; + u32 base, len; + int ret; + + pr_info(DRV_NAME + ": Realtek PCI-E Card Reader found at %s [%04x:%04x] (rev %x)\n", + pci_name(pcidev), (int)pcidev->vendor, (int)pcidev->device, + (int)pcidev->revision); + + ret = pci_enable_device(pcidev); + if (ret) + return ret; + + ret = pci_request_regions(pcidev, DRV_NAME); + if (ret) + goto disable; + + pdev = kzalloc(sizeof(*pdev), GFP_KERNEL); + if (!pdev) { + ret = -ENOMEM; + goto release_pci; + } + + adapter = rtsx_alloc_adapter(1, &pcidev->dev); + if (!adapter) { + ret = -ENOMEM; + goto release_pdev; + } + + adapter->common_ops = &rtsx_pci_common_ops; + adapter->sdmmc_ops = &rtsx_pci_sdmmc_ops; + + pdev->pci = pcidev; + dev_set_drvdata(&pcidev->dev, pdev); + + len = pci_resource_len(pcidev, 0); + base = pci_resource_start(pcidev, 0); + pdev->remap_addr = ioremap_nocache(base, len); + if (!pdev->remap_addr) { + ret = -ENOMEM; + goto free_host; + } + + pdev->rtsx_resv_buf = dma_alloc_coherent(&(pcidev->dev), + RTSX_RESV_BUF_LEN, &(pdev->rtsx_resv_buf_addr), + GFP_KERNEL); + if (pdev->rtsx_resv_buf == NULL) { + ret = -ENXIO; + goto unmap; + } + pdev->host_cmds_ptr = pdev->rtsx_resv_buf; + pdev->host_cmds_addr = pdev->rtsx_resv_buf_addr; + pdev->host_sg_tbl_ptr = pdev->rtsx_resv_buf + HOST_CMDS_BUF_LEN; + pdev->host_sg_tbl_addr = pdev->rtsx_resv_buf_addr + HOST_CMDS_BUF_LEN; + pdev->adapter = adapter; + + pdev->need_reset = 0; + pdev->need_release = 0; + INIT_DELAYED_WORK(&pdev->carddet_work, rtsx_pci_card_detect); + INIT_DELAYED_WORK(&pdev->idle_work, rtsx_pci_enter_idle); + + pdev->msi_en = msi_en; + if (pdev->msi_en) { + ret = pci_enable_msi(pcidev); + if (ret < 0) + pdev->msi_en = false; + } + + ret = rtsx_pci_acquire_irq(pdev); + if (ret < 0) { + ret = -EIO; + goto free_dma; + } + + pci_set_master(pcidev); + synchronize_irq(pdev->irq); + + ret = rtsx_add_adapter(adapter); + if (ret) + goto disable_irq; + + rtsx_pci_init_chip(pdev); + + return 0; + +disable_irq: + free_irq(pdev->irq, (void *)pdev); +free_dma: + dma_free_coherent(&(pdev->pci->dev), RTSX_RESV_BUF_LEN, + pdev->rtsx_resv_buf, pdev->rtsx_resv_buf_addr); +unmap: + iounmap(pdev->remap_addr); +free_host: + dev_set_drvdata(&pcidev->dev, NULL); + kfree(adapter); +release_pdev: + kfree(pdev); +release_pci: + pci_release_regions(pcidev); +disable: + pci_disable_device(pcidev); + + return ret; +} + +static void __devexit rtsx_pci_remove(struct pci_dev *pcidev) +{ + struct rtsx_pdev *pdev = pci_get_drvdata(pcidev); + + pdev->remove_pci = true; + + cancel_delayed_work(&pdev->carddet_work); + cancel_delayed_work(&pdev->idle_work); + rtsx_remove_adapter(pdev->adapter); + + dma_free_coherent(&(pdev->pci->dev), RTSX_RESV_BUF_LEN, + pdev->rtsx_resv_buf, pdev->rtsx_resv_buf_addr); + free_irq(pdev->irq, (void *)pdev); + if (pdev->msi_en) + pci_disable_msi(pdev->pci); + iounmap(pdev->remap_addr); + + dev_set_drvdata(&pcidev->dev, NULL); + pci_release_regions(pcidev); + pci_disable_device(pcidev); + rtsx_free_adapter(pdev->adapter); + + pr_info(DRV_NAME + ": Realtek PCI-E Card Reader at %s [%04x:%04x] has been removed\n", + pci_name(pcidev), (int)pcidev->vendor, (int)pcidev->device); +} + +#ifdef CONFIG_PM + +static int rtsx_pci_suspend(struct pci_dev *pcidev, pm_message_t state) +{ + struct rtsx_pdev *pdev; + int ret = 0; + + pdev = pci_get_drvdata(pcidev); + + pci_save_state(pcidev); + pci_enable_wake(pcidev, pci_choose_state(pcidev, state), 0); + pci_disable_device(pcidev); + pci_set_power_state(pcidev, pci_choose_state(pcidev, state)); + + return ret; +} + +static int rtsx_pci_resume(struct pci_dev *pcidev) +{ + struct rtsx_pdev *pdev; + int ret = 0; + + pdev = pci_get_drvdata(pcidev); + + pci_set_power_state(pcidev, PCI_D0); + pci_restore_state(pcidev); + ret = pci_enable_device(pcidev); + if (ret) + return ret; + + return ret; +} + +#else /* CONFIG_PM */ + +#define rtsx_pci_suspend NULL +#define rtsx_pci_resume NULL + +#endif /* CONFIG_PM */ + +static struct pci_driver rtsx_pci_driver = { + .name = DRV_NAME, + .id_table = rtsx_pci_ids, + .probe = rtsx_pci_probe, + .remove = __devexit_p(rtsx_pci_remove), + .suspend = rtsx_pci_suspend, + .resume = rtsx_pci_resume, +}; + +static int __init rtsx_pci_drv_init(void) +{ + return pci_register_driver(&rtsx_pci_driver); +} + +static void __exit rtsx_pci_drv_exit(void) +{ + pci_unregister_driver(&rtsx_pci_driver); +} + +module_init(rtsx_pci_drv_init); +module_exit(rtsx_pci_drv_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Realtek Corp."); +MODULE_DESCRIPTION("Realtek PCI-E Card Reader Adapter Driver"); diff --git a/drivers/misc/realtek_cr/pci/rtsx_pci.h b/drivers/misc/realtek_cr/pci/rtsx_pci.h new file mode 100644 index 0000000..7e30c03 --- /dev/null +++ b/drivers/misc/realtek_cr/pci/rtsx_pci.h @@ -0,0 +1,695 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009 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 as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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: + * Wei WANG <wei_wang@xxxxxxxxxxxxxx> + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#ifndef __RTSX_PCI_H +#define __RTSX_PCI_H + +#include <linux/sched.h> +#include <linux/pci.h> + +#define DRV_NAME "rtsx_pci" + +#define MAX_RW_REG_CNT 1024 + +#define RTSX_MAX_BLOCK_COUNT 65536 +#define RTSX_MAX_BLOCK_LENGTH 2048 + +/* PCI Operation Register Address */ +#define RTSX_HCBAR 0x00 +#define RTSX_HCBCTLR 0x04 +#define RTSX_HDBAR 0x08 +#define RTSX_HDBCTLR 0x0C +#define RTSX_HAIMR 0x10 +#define RTSX_BIPR 0x14 +#define RTSX_BIER 0x18 + +/* Host command buffer control register */ +#define STOP_CMD (0x01 << 28) + +/* Host data buffer control register */ +#define SDMA_MODE 0x00 +#define ADMA_MODE (0x02 << 26) +#define STOP_DMA (0x01 << 28) +#define TRIG_DMA (0x01 << 31) + +/* Bus interrupt pending register */ +#define CMD_DONE_INT (1 << 31) +#define DATA_DONE_INT (1 << 30) +#define TRANS_OK_INT (1 << 29) +#define TRANS_FAIL_INT (1 << 28) +#define XD_INT (1 << 27) +#define MS_INT (1 << 26) +#define SD_INT (1 << 25) +#define GPIO0_INT (1 << 24) +#define OC_INT (1 << 23) +#define SD_WRITE_PROTECT (1 << 19) +#define XD_EXIST (1 << 18) +#define MS_EXIST (1 << 17) +#define SD_EXIST (1 << 16) +#define DELINK_INT GPIO0_INT +#define MS_OC_INT (1 << 23) +#define SD_OC_INT (1 << 22) + +#define CARD_INT (XD_INT | MS_INT | SD_INT) +#define NEED_COMPLETE_INT (DATA_DONE_INT | TRANS_OK_INT | TRANS_FAIL_INT) +#define RTSX_INT (CMD_DONE_INT | NEED_COMPLETE_INT | \ + CARD_INT | GPIO0_INT | OC_INT) + +#define CARD_EXIST (XD_EXIST | MS_EXIST | SD_EXIST) + +/* Bus interrupt enable register */ +#define CMD_DONE_INT_EN (1 << 31) +#define DATA_DONE_INT_EN (1 << 30) +#define TRANS_OK_INT_EN (1 << 29) +#define TRANS_FAIL_INT_EN (1 << 28) +#define XD_INT_EN (1 << 27) +#define MS_INT_EN (1 << 26) +#define SD_INT_EN (1 << 25) +#define GPIO0_INT_EN (1 << 24) +#define OC_INT_EN (1 << 23) +#define DELINK_INT_EN GPIO0_INT_EN +#define MS_OC_INT_EN (1 << 23) +#define SD_OC_INT_EN (1 << 22) + +#define READ_REG_CMD 0 +#define WRITE_REG_CMD 1 +#define CHECK_REG_CMD 2 + +/* + * macros for easy use + */ +#define rtsx_pci_writel(pdev, reg, value) \ + iowrite32(value, (pdev)->remap_addr + reg) +#define rtsx_pci_readl(pdev, reg) \ + ioread32((pdev)->remap_addr + reg) +#define rtsx_pci_writew(pdev, reg, value) \ + iowrite16(value, (pdev)->remap_addr + reg) +#define rtsx_pci_readw(pdev, reg) \ + ioread16((pdev)->remap_addr + reg) +#define rtsx_pci_writeb(pdev, reg, value) \ + iowrite8(value, (pdev)->remap_addr + reg) +#define rtsx_pci_readb(pdev, reg) \ + ioread8((pdev)->remap_addr + reg) + +#define rtsx_pci_read_config_byte(pdev, where, val) \ + pci_read_config_byte((pdev)->pci, where, val) + +#define rtsx_pci_write_config_byte(pdev, where, val) \ + pci_write_config_byte((pdev)->pci, where, val) + +#define rtsx_pci_read_config_dword(pdev, where, val) \ + pci_read_config_dword((pdev)->pci, where, val) + +#define rtsx_pci_write_config_dword(pdev, where, val) \ + pci_write_config_dword((pdev)->pci, where, val) + +#define STATE_TRANS_NONE 0 +#define STATE_TRANS_CMD 1 +#define STATE_TRANS_BUF 2 +#define STATE_TRANS_SG 3 + +#define TRANS_NOT_READY 0 +#define TRANS_RESULT_OK 1 +#define TRANS_RESULT_FAIL 2 +#define TRANS_NO_DEVICE 3 + +#define RTSX_RESV_BUF_LEN 4096 +#define HOST_CMDS_BUF_LEN 1024 +#define HOST_SG_TBL_BUF_LEN (RTSX_RESV_BUF_LEN - HOST_CMDS_BUF_LEN) + +#define HOST_TO_DEVICE 0 +#define DEVICE_TO_HOST 1 + +#define MAX_PHASE 31 +#define RX_TUNING_CNT 3 + +/* SG descriptor */ +#define SG_INT 0x04 +#define SG_END 0x02 +#define SG_VALID 0x01 + +#define SG_NO_OP 0x00 +#define SG_TRANS_DATA (0x02 << 4) +#define SG_LINK_DESC (0x03 << 4) + +/* SD bank voltage */ +#define SD_IO_3V3 0 +#define SD_IO_1V8 1 + + +/* Card Clock Enable Register */ +#define SD_CLK_EN 0x04 + +/* Card Select Register */ +#define SD_MOD_SEL 2 + +/* Card Output Enable Register */ +#define SD_OUTPUT_EN 0x04 + +/* CARD_SHARE_MODE */ +#define CARD_SHARE_MASK 0x0F +#define CARD_SHARE_MULTI_LUN 0x00 +#define CARD_SHARE_NORMAL 0x00 +#define CARD_SHARE_48_SD 0x04 +/* CARD_SHARE_MODE for barossa */ +#define CARD_SHARE_BAROSSA_SD 0x01 + +/* SD30_DRIVE_SEL */ +#define DRIVER_TYPE_A 0x05 +#define DRIVER_TYPE_B 0x03 +#define DRIVER_TYPE_C 0x02 +#define DRIVER_TYPE_D 0x01 + +/* FPDCTL */ +#define SSC_POWER_DOWN 0x01 +#define SD_OC_POWER_DOWN 0x02 +#define ALL_POWER_DOWN 0x07 +#define OC_POWER_DOWN 0x06 + +/* CLK_CTL */ +#define CHANGE_CLK 0x01 + +/* SD_STAT1 */ +#define SD_CRC7_ERR 0x80 +#define SD_CRC16_ERR 0x40 +#define SD_CRC_WRITE_ERR 0x20 +#define SD_CRC_WRITE_ERR_MASK 0x1C +#define GET_CRC_TIME_OUT 0x02 +#define SD_TUNING_COMPARE_ERR 0x01 + +/* SD_STAT2 */ +#define SD_RSP_80CLK_TIMEOUT 0x01 + +/* SD_BUS_STAT */ +#define SD_CLK_TOGGLE_EN 0x80 +#define SD_CLK_FORCE_STOP 0x40 +#define SD_DAT3_STATUS 0x10 +#define SD_DAT2_STATUS 0x08 +#define SD_DAT1_STATUS 0x04 +#define SD_DAT0_STATUS 0x02 +#define SD_CMD_STATUS 0x01 + +/* SD_PAD_CTL */ +#define SD_IO_USING_1V8 0x80 +#define SD_IO_USING_3V3 0x7F +#define TYPE_A_DRIVING 0x00 +#define TYPE_B_DRIVING 0x01 +#define TYPE_C_DRIVING 0x02 +#define TYPE_D_DRIVING 0x03 + +/* SD_SAMPLE_POINT_CTL */ +#define DDR_FIX_RX_DAT 0x00 +#define DDR_VAR_RX_DAT 0x80 +#define DDR_FIX_RX_DAT_EDGE 0x00 +#define DDR_FIX_RX_DAT_14_DELAY 0x40 +#define DDR_FIX_RX_CMD 0x00 +#define DDR_VAR_RX_CMD 0x20 +#define DDR_FIX_RX_CMD_POS_EDGE 0x00 +#define DDR_FIX_RX_CMD_14_DELAY 0x10 +#define SD20_RX_POS_EDGE 0x00 +#define SD20_RX_14_DELAY 0x08 +#define SD20_RX_SEL_MASK 0x08 + +/* SD_PUSH_POINT_CTL */ +#define DDR_FIX_TX_CMD_DAT 0x00 +#define DDR_VAR_TX_CMD_DAT 0x80 +#define DDR_FIX_TX_DAT_14_TSU 0x00 +#define DDR_FIX_TX_DAT_12_TSU 0x40 +#define DDR_FIX_TX_CMD_NEG_EDGE 0x00 +#define DDR_FIX_TX_CMD_14_AHEAD 0x20 +#define SD20_TX_NEG_EDGE 0x00 +#define SD20_TX_14_AHEAD 0x10 +#define SD20_TX_SEL_MASK 0x10 +#define DDR_VAR_SDCLK_POL_SWAP 0x01 + +/* SD_TRANSFER */ +#define SD_TRANSFER_START 0x80 +#define SD_TRANSFER_END 0x40 +#define SD_STAT_IDLE 0x20 +#define SD_TRANSFER_ERR 0x10 +/* SD Transfer Mode definition */ +#define SD_TM_NORMAL_WRITE 0x00 +#define SD_TM_AUTO_WRITE_3 0x01 +#define SD_TM_AUTO_WRITE_4 0x02 +#define SD_TM_AUTO_READ_3 0x05 +#define SD_TM_AUTO_READ_4 0x06 +#define SD_TM_CMD_RSP 0x08 +#define SD_TM_AUTO_WRITE_1 0x09 +#define SD_TM_AUTO_WRITE_2 0x0A +#define SD_TM_NORMAL_READ 0x0C +#define SD_TM_AUTO_READ_1 0x0D +#define SD_TM_AUTO_READ_2 0x0E +#define SD_TM_AUTO_TUNING 0x0F + +/* SD_VPTX_CTL / SD_VPRX_CTL */ +#define PHASE_CHANGE 0x80 +#define PHASE_NOT_RESET 0x40 + +/* SD_DCMPS_TX_CTL / SD_DCMPS_RX_CTL */ +#define DCMPS_CHANGE 0x80 +#define DCMPS_CHANGE_DONE 0x40 +#define DCMPS_ERROR 0x20 +#define DCMPS_CURRENT_PHASE 0x1F + +/* SD Configure 1 Register */ +#define SD_CLK_DIVIDE_0 0x00 +#define SD_CLK_DIVIDE_256 0xC0 +#define SD_CLK_DIVIDE_128 0x80 +#define SD_BUS_WIDTH_1BIT 0x00 +#define SD_BUS_WIDTH_4BIT 0x01 +#define SD_BUS_WIDTH_8BIT 0x02 +#define SD_ASYNC_FIFO_NOT_RST 0x10 +#define SD_20_MODE 0x00 +#define SD_DDR_MODE 0x04 +#define SD_30_MODE 0x08 + +#define SD_CLK_DIVIDE_MASK 0xC0 + +/* SD_CMD_STATE */ +#define SD_CMD_IDLE 0x80 + +/* SD_DATA_STATE */ +#define SD_DATA_IDLE 0x80 + +/* DCM_DRP_CTL */ +#define DCM_RESET 0x08 +#define DCM_LOCKED 0x04 +#define DCM_208M 0x00 +#define DCM_TX 0x01 +#define DCM_RX 0x02 + +/* DCM_DRP_TRIG */ +#define DRP_START 0x80 +#define DRP_DONE 0x40 + +/* DCM_DRP_CFG */ +#define DRP_WRITE 0x80 +#define DRP_READ 0x00 +#define DCM_WRITE_ADDRESS_50 0x50 +#define DCM_WRITE_ADDRESS_51 0x51 +#define DCM_READ_ADDRESS_00 0x00 +#define DCM_READ_ADDRESS_51 0x51 + +/* IRQSTAT0 */ +#define DMA_DONE_INT 0x80 +#define SUSPEND_INT 0x40 +#define LINK_RDY_INT 0x20 +#define LINK_DOWN_INT 0x10 + +/* DMACTL */ +#define DMA_RST 0x80 +#define DMA_BUSY 0x04 +#define DMA_DIR_TO_CARD 0x00 +#define DMA_DIR_FROM_CARD 0x02 +#define DMA_EN 0x01 +#define DMA_128 (0 << 4) +#define DMA_256 (1 << 4) +#define DMA_512 (2 << 4) +#define DMA_1024 (3 << 4) +#define DMA_PACK_SIZE_MASK 0x30 + +/* SSC_CTL1 */ +#define SSC_RSTB 0x80 +#define SSC_8X_EN 0x40 +#define SSC_FIX_FRAC 0x20 +#define SSC_SEL_1M 0x00 +#define SSC_SEL_2M 0x08 +#define SSC_SEL_4M 0x10 +#define SSC_SEL_8M 0x18 + +/* SSC_CTL2 */ +#define SSC_DEPTH_MASK 0x07 +#define SSC_DEPTH_DISALBE 0x00 +#define SSC_DEPTH_4M 0x01 +#define SSC_DEPTH_2M 0x02 +#define SSC_DEPTH_1M 0x03 +#define SSC_DEPTH_500K 0x04 +#define SSC_DEPTH_250K 0x05 + +/* System Clock Control Register */ +#define CLK_LOW_FREQ 0x01 + +/* System Clock Divider Register */ +#define CLK_DIV_1 0x01 +#define CLK_DIV_2 0x02 +#define CLK_DIV_4 0x03 +#define CLK_DIV_8 0x04 + +/* SD Configure 2 Register */ +#define SD_CALCULATE_CRC7 0x00 +#define SD_NO_CALCULATE_CRC7 0x80 +#define SD_CHECK_CRC16 0x00 +#define SD_NO_CHECK_CRC16 0x40 +#define SD_NO_CHECK_WAIT_CRC_TO 0x20 +#define SD_WAIT_BUSY_END 0x08 +#define SD_NO_WAIT_BUSY_END 0x00 +#define SD_CHECK_CRC7 0x00 +#define SD_NO_CHECK_CRC7 0x04 +#define SD_RSP_LEN_0 0x00 +#define SD_RSP_LEN_6 0x01 +#define SD_RSP_LEN_17 0x02 +/* SD/MMC Response Type Definition */ +#define SD_RSP_TYPE_R0 0x04 +#define SD_RSP_TYPE_R1 0x01 +#define SD_RSP_TYPE_R1b 0x09 +#define SD_RSP_TYPE_R2 0x02 +#define SD_RSP_TYPE_R3 0x05 +#define SD_RSP_TYPE_R4 0x05 +#define SD_RSP_TYPE_R5 0x01 +#define SD_RSP_TYPE_R6 0x01 +#define SD_RSP_TYPE_R7 0x01 + +/* SD_CONFIURE3 */ +#define SD_RSP_80CLK_TIMEOUT_EN 0x01 + +/* Card Transfer Reset Register */ +#define SPI_STOP 0x01 +#define XD_STOP 0x02 +#define SD_STOP 0x04 +#define MS_STOP 0x08 +#define SPI_CLR_ERR 0x10 +#define XD_CLR_ERR 0x20 +#define SD_CLR_ERR 0x40 +#define MS_CLR_ERR 0x80 + +/* Card Data Source Register */ +#define PINGPONG_BUFFER 0x01 +#define RING_BUFFER 0x00 + +/* Card Power Control Register */ +#define PMOS_STRG_MASK 0x10 +#define PMOS_STRG_800mA 0x10 +#define PMOS_STRG_400mA 0x00 +#define SD_POWER_OFF 0x03 +#define SD_PARTIAL_POWER_ON 0x01 +#define SD_POWER_ON 0x00 +#define SD_POWER_MASK 0x03 + +/* PWR_GATE_CTRL */ +#define PWR_GATE_EN 0x01 +#define LDO3318_PWR_MASK 0x06 +#define LDO_ON 0x00 +#define LDO_SUSPEND 0x04 +#define LDO_OFF 0x06 + +/* CARD_CLK_SOURCE */ +#define CRC_FIX_CLK (0x00 << 0) +#define CRC_VAR_CLK0 (0x01 << 0) +#define CRC_VAR_CLK1 (0x02 << 0) +#define SD30_FIX_CLK (0x00 << 2) +#define SD30_VAR_CLK0 (0x01 << 2) +#define SD30_VAR_CLK1 (0x02 << 2) +#define SAMPLE_FIX_CLK (0x00 << 4) +#define SAMPLE_VAR_CLK0 (0x01 << 4) +#define SAMPLE_VAR_CLK1 (0x02 << 4) + +#define SD_CFG1 0xFDA0 +#define SD_CFG2 0xFDA1 +#define SD_CFG3 0xFDA2 +#define SD_STAT1 0xFDA3 +#define SD_STAT2 0xFDA4 +#define SD_BUS_STAT 0xFDA5 +#define SD_PAD_CTL 0xFDA6 +#define SD_SAMPLE_POINT_CTL 0xFDA7 +#define SD_PUSH_POINT_CTL 0xFDA8 +#define SD_CMD0 0xFDA9 +#define SD_CMD1 0xFDAA +#define SD_CMD2 0xFDAB +#define SD_CMD3 0xFDAC +#define SD_CMD4 0xFDAD +#define SD_CMD5 0xFDAE +#define SD_BYTE_CNT_L 0xFDAF +#define SD_BYTE_CNT_H 0xFDB0 +#define SD_BLOCK_CNT_L 0xFDB1 +#define SD_BLOCK_CNT_H 0xFDB2 +#define SD_TRANSFER 0xFDB3 +#define SD_CMD_STATE 0xFDB5 +#define SD_DATA_STATE 0xFDB6 + +#define SRCTL 0xFC13 + +#define DCM_DRP_CTL 0xFC23 +#define DCM_DRP_TRIG 0xFC24 +#define DCM_DRP_CFG 0xFC25 +#define DCM_DRP_WR_DATA_L 0xFC26 +#define DCM_DRP_WR_DATA_H 0xFC27 +#define DCM_DRP_RD_DATA_L 0xFC28 +#define DCM_DRP_RD_DATA_H 0xFC29 +#define SD_VPCLK0_CTL 0xFC2A +#define SD_VPCLK1_CTL 0xFC2B +#define SD_DCMPS0_CTL 0xFC2C +#define SD_DCMPS1_CTL 0xFC2D +#define SD_VPTX_CTL SD_VPCLK0_CTL +#define SD_VPRX_CTL SD_VPCLK1_CTL +#define SD_DCMPS_TX_CTL SD_DCMPS0_CTL +#define SD_DCMPS_RX_CTL SD_DCMPS1_CTL +#define CARD_CLK_SOURCE 0xFC2E + +#define CARD_PWR_CTL 0xFD50 +#define CARD_CLK_SWITCH 0xFD51 +#define CARD_SHARE_MODE 0xFD52 +#define CARD_DRIVE_SEL 0xFD53 +#define CARD_STOP 0xFD54 +#define CARD_OE 0xFD55 +#define CARD_AUTO_BLINK 0xFD56 +#define CARD_GPIO_DIR 0xFD57 +#define CARD_GPIO 0xFD58 + +#define CARD_DATA_SOURCE 0xFD5B +#define CARD_SELECT 0xFD5C +#define SD30_DRIVE_SEL 0xFD5E + +#define CARD_CLK_EN 0xFD69 + +#define SDIO_CTRL 0xFD6B + +#define FPDCTL 0xFC00 +#define PDINFO 0xFC01 + +#define CLK_CTL 0xFC02 +#define CLK_DIV 0xFC03 +#define CLK_SEL 0xFC04 + +#define SSC_DIV_N_0 0xFC0F +#define SSC_DIV_N_1 0xFC10 +#define SSC_CTL1 0xFC11 +#define SSC_CTL2 0xFC12 + +#define RCCTL 0xFC14 + +#define FPGA_PULL_CTL 0xFC1D + +#define CARD_PULL_CTL1 0xFD60 +#define CARD_PULL_CTL2 0xFD61 +#define CARD_PULL_CTL3 0xFD62 +#define CARD_PULL_CTL4 0xFD63 +#define CARD_PULL_CTL5 0xFD64 +#define CARD_PULL_CTL6 0xFD65 + +/* PCI Express Related Registers */ +#define IRQEN0 0xFE20 +#define IRQSTAT0 0xFE21 +#define IRQEN1 0xFE22 +#define IRQSTAT1 0xFE23 +#define TLPRIEN 0xFE24 +#define TLPRISTAT 0xFE25 +#define TLPTIEN 0xFE26 +#define TLPTISTAT 0xFE27 +#define DMATC0 0xFE28 +#define DMATC1 0xFE29 +#define DMATC2 0xFE2A +#define DMATC3 0xFE2B +#define DMACTL 0xFE2C +#define BCTL 0xFE2D +#define RBBC0 0xFE2E +#define RBBC1 0xFE2F +#define RBDAT 0xFE30 +#define RBCTL 0xFE34 +#define CFGADDR0 0xFE35 +#define CFGADDR1 0xFE36 +#define CFGDATA0 0xFE37 +#define CFGDATA1 0xFE38 +#define CFGDATA2 0xFE39 +#define CFGDATA3 0xFE3A +#define CFGRWCTL 0xFE3B +#define PHYRWCTL 0xFE3C +#define PHYDATA0 0xFE3D +#define PHYDATA1 0xFE3E +#define PHYADDR 0xFE3F +#define MSGRXDATA0 0xFE40 +#define MSGRXDATA1 0xFE41 +#define MSGRXDATA2 0xFE42 +#define MSGRXDATA3 0xFE43 +#define MSGTXDATA0 0xFE44 +#define MSGTXDATA1 0xFE45 +#define MSGTXDATA2 0xFE46 +#define MSGTXDATA3 0xFE47 +#define MSGTXCTL 0xFE48 +#define PETXCFG 0xFE49 + +#define CDRESUMECTL 0xFE52 +#define WAKE_SEL_CTL 0xFE54 +#define PME_FORCE_CTL 0xFE56 +#define ASPM_FORCE_CTL 0xFE57 +#define PM_CLK_FORCE_CTL 0xFE58 +#define PERST_GLITCH_WIDTH 0xFE5C +#define CHANGE_LINK_STATE 0xFE5B +#define RESET_LOAD_REG 0xFE5E +#define EFUSE_CONTENT 0xFE5F +#define HOST_SLEEP_STATE 0xFE60 +#define MAIN_PWR_OFF_CTL 0xFE70 /* RTS5208 */ +#define SDIO_CFG 0xFE70 /* RTS5209 */ + +#define NFTS_TX_CTRL 0xFE72 + +#define PWR_GATE_CTRL 0xFE75 +#define PWD_SUSPEND_EN 0xFE76 + +/* Memory mapping */ +#define SRAM_BASE 0xE600 +#define RBUF_BASE 0xF400 +#define PPBUF_BASE1 0xF800 +#define PPBUF_BASE2 0xFA00 +#define IMAGE_FLAG_ADDR0 0xCE80 +#define IMAGE_FLAG_ADDR1 0xCE81 + +#define rtsx_pci_init_cmd(pdev) ((pdev)->ci = 0) + +/* SD Tuning Data Structure + * Record continuous timing phase path + */ +struct timing_phase_path { + int start; + int end; + int mid; + int len; +}; + +struct sd_info { + int initial_mode; + int ddr_mode; +}; + +struct reg_val { + /* Enable signal for regulator3318 */ + u8 ldo_pwr_on; + u8 ldo_pwr_off; + u8 ldo_pwr_suspend; +}; + +struct rtsx_adapter; +struct rtsx_pdev; + +struct pdev_ops { + int (*extra_init_hw)(struct rtsx_pdev *pdev); + int (*optimize_phy)(struct rtsx_pdev *pdev); + int (*turn_on_led)(struct rtsx_pdev *pdev); + int (*turn_off_led)(struct rtsx_pdev *pdev); + int (*enable_auto_blink)(struct rtsx_pdev *pdev); + int (*disable_auto_blink)(struct rtsx_pdev *pdev); +}; + +enum PDEV_STAT {PDEV_STAT_IDLE, PDEV_STAT_RUN}; + +struct rtsx_pdev { + struct pci_dev *pci; + + /* pci resources */ + unsigned long addr; + void __iomem *remap_addr; + int irq; + + /* host reserved buffer */ + void *rtsx_resv_buf; + dma_addr_t rtsx_resv_buf_addr; + + void *host_cmds_ptr; + dma_addr_t host_cmds_addr; + int ci; + + void *host_sg_tbl_ptr; + dma_addr_t host_sg_tbl_addr; + int sgi; + + u32 int_reg; + char trans_result; + char trans_state; + + int need_reset; + int need_release; + + spinlock_t lock; + struct mutex pdev_mutex; + struct completion *done; + struct completion *finish_me; + + bool remove_pci; + bool msi_en; + +#define IC_VER_A 0 +#define IC_VER_B 1 +#define IC_VER_C 2 +#define IC_VER_D 3 + u8 ic_version; + + struct delayed_work carddet_work; + struct delayed_work idle_work; + + struct sd_info sd_card; + struct reg_val rval; + const struct rtsx_reg_pair *sd_pull_ctl_enable_tbl; + const struct rtsx_reg_pair *sd_pull_ctl_disable_tbl; + + const struct pdev_ops *ops; + enum PDEV_STAT state; + + struct rtsx_adapter *adapter; +}; + +#define CHK_PCI_PID(pdev, pid) ((pdev)->pci->device == (pid)) +#define PCI_VID(pdev) ((pdev)->pci->vendor) +#define PCI_PID(pdev) ((pdev)->pci->device) + +void rtsx_pci_start_run(struct rtsx_pdev *pdev); +int rtsx_pci_write_register(struct rtsx_pdev *pdev, u16 addr, u8 mask, u8 data); +int rtsx_pci_read_register(struct rtsx_pdev *pdev, u16 addr, u8 *data); +int rtsx_pci_write_phy_register(struct rtsx_pdev *pdev, u8 addr, u16 val); +int rtsx_pci_read_phy_register(struct rtsx_pdev *pdev, u8 addr, u16 *val); +void rtsx_pci_stop_cmd(struct rtsx_pdev *pdev); +void rtsx_pci_add_cmd(struct rtsx_pdev *pdev, + u8 cmd_type, u16 reg_addr, u8 mask, u8 data); +void rtsx_pci_send_cmd_no_wait(struct rtsx_pdev *pdev); +int rtsx_pci_send_cmd(struct rtsx_pdev *pdev, int timeout); +void rtsx_pci_add_sg_tbl(struct rtsx_pdev *pdev, u32 addr, u32 len, u8 option); +int rtsx_pci_transfer_data(struct rtsx_pdev *pdev, + void *buf, size_t len, int use_sg, int read, int timeout); +int rtsx_pci_read_ppbuf(struct rtsx_pdev *pdev, u8 *buf, int buf_len); +int rtsx_pci_write_ppbuf(struct rtsx_pdev *pdev, u8 *buf, int buf_len); +int rtsx_pci_set_pull_ctl(struct rtsx_pdev *pdev, + const struct rtsx_reg_pair *tbl); + +static inline u8 *rtsx_pci_get_cmd_data(struct rtsx_pdev *pdev) +{ + return (u8 *)(pdev->host_cmds_ptr); +} + +#endif diff --git a/drivers/misc/realtek_cr/pci/sdmmc.c b/drivers/misc/realtek_cr/pci/sdmmc.c new file mode 100644 index 0000000..ecc15aa --- /dev/null +++ b/drivers/misc/realtek_cr/pci/sdmmc.c @@ -0,0 +1,1070 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009 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 as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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: + * Wei WANG <wei_wang@xxxxxxxxxxxxxx> + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#include <linux/dma-mapping.h> +#include <linux/highmem.h> +#include <linux/delay.h> +#include <linux/mmc/mmc.h> +#include <linux/mmc/sd.h> +#include <linux/mmc/host.h> +#include <linux/rtsx_core.h> + +#include <asm/unaligned.h> + +#include "rtsx_pci.h" +#include "sdmmc.h" + +static inline void sd_clear_error(struct rtsx_pdev *pdev) +{ + rtsx_pci_write_register(pdev, CARD_STOP, + SD_STOP | SD_CLR_ERR, SD_STOP | SD_CLR_ERR); +} + +#ifdef CONFIG_REALTEK_CR_DEBUG + +static void sd_print_debug_regs(struct rtsx_pdev *pdev) +{ + u16 i; + u8 *ptr; + + /* Print SD host internal registers */ + rtsx_pci_init_cmd(pdev); + for (i = 0xFDA0; i <= 0xFDAE; i++) + rtsx_pci_add_cmd(pdev, READ_REG_CMD, i, 0, 0); + for (i = 0xFD52; i <= 0xFD69; i++) + rtsx_pci_add_cmd(pdev, READ_REG_CMD, i, 0, 0); + rtsx_pci_send_cmd(pdev, 100); + + ptr = rtsx_pci_get_cmd_data(pdev); + for (i = 0xFDA0; i <= 0xFDAE; i++) + dev_dbg(&(pdev->pci->dev), "0x%04X: 0x%02x\n", i, *(ptr++)); + for (i = 0xFD52; i <= 0xFD69; i++) + dev_dbg(&(pdev->pci->dev), "0x%04X: 0x%02x\n", i, *(ptr++)); +} + +#else + +#define sd_print_debug_regs(pdev) + +#endif + +static int sd_read_data(struct rtsx_pdev *pdev, u8 *cmd, + u16 byte_cnt, u8 *buf, int buf_len, int timeout) +{ + struct sd_info *sd_card = &(pdev->sd_card); + int err, i; + u8 trans_mode; + + dev_dbg(&(pdev->pci->dev), "%s: SD/MMC CMD %d\n", + __func__, cmd[0] - 0x40); + + if (!buf) + buf_len = 0; + + if ((cmd[0] & 0x3F) == MMC_SEND_TUNING_BLOCK) + trans_mode = SD_TM_AUTO_TUNING; + else + trans_mode = SD_TM_NORMAL_READ; + + rtsx_pci_init_cmd(pdev); + + for (i = 0; i < 5; i++) + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, + SD_CMD0 + i, 0xFF, cmd[i]); + + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_BYTE_CNT_L, + 0xFF, (u8)byte_cnt); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_BYTE_CNT_H, + 0xFF, (u8)(byte_cnt >> 8)); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_BLOCK_CNT_L, 0xFF, 1); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_BLOCK_CNT_H, 0xFF, 0); + + if (sd_card->initial_mode) + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_CFG1, + SD_CLK_DIVIDE_MASK, SD_CLK_DIVIDE_0); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_CFG2, 0xFF, + SD_CALCULATE_CRC7 | SD_CHECK_CRC16 | + SD_NO_WAIT_BUSY_END | SD_CHECK_CRC7 | SD_RSP_LEN_6); + if (trans_mode != SD_TM_AUTO_TUNING) + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, + CARD_DATA_SOURCE, 0x01, PINGPONG_BUFFER); + + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_TRANSFER, + 0xFF, trans_mode | SD_TRANSFER_START); + rtsx_pci_add_cmd(pdev, CHECK_REG_CMD, SD_TRANSFER, + SD_TRANSFER_END, SD_TRANSFER_END); + + err = rtsx_pci_send_cmd(pdev, timeout); + if (err < 0) { + sd_print_debug_regs(pdev); + dev_dbg(&(pdev->pci->dev), + "rtsx_pci_send_cmd fail (err = %d)\n", err); + return err; + } + + if (buf && buf_len) { + err = rtsx_pci_read_ppbuf(pdev, buf, buf_len); + if (err < 0) { + dev_dbg(&(pdev->pci->dev), + "rtsx_read_ppbuf fail (err = %d)\n", err); + return err; + } + } + + if (sd_card->initial_mode) { + err = rtsx_pci_write_register(pdev, SD_CFG1, + SD_CLK_DIVIDE_MASK, SD_CLK_DIVIDE_128); + if (err < 0) + return err; + } + + return 0; +} + +static int sd_write_data(struct rtsx_pdev *pdev, u8 *cmd, u16 byte_cnt, + u8 *buf, int buf_len, int timeout) +{ + int err, i; + u8 trans_mode; + + if (!buf) + buf_len = 0; + + if (buf && buf_len) { + err = rtsx_pci_write_ppbuf(pdev, buf, buf_len); + if (err < 0) { + dev_dbg(&(pdev->pci->dev), + "rtsx_write_ppbuf fail (err = %d)\n", err); + return err; + } + } + + if (cmd) + trans_mode = SD_TM_AUTO_WRITE_2; + else + trans_mode = SD_TM_AUTO_WRITE_3; + + rtsx_pci_init_cmd(pdev); + + if (cmd) { + dev_dbg(&(pdev->pci->dev), "%s: SD/MMC CMD %d\n", __func__, + cmd[0] - 0x40); + + for (i = 0; i < 5; i++) + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, + SD_CMD0 + i, 0xFF, cmd[i]); + } + + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_BYTE_CNT_L, + 0xFF, (u8)byte_cnt); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_BYTE_CNT_H, + 0xFF, (u8)(byte_cnt >> 8)); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_BLOCK_CNT_L, 0xFF, 1); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_BLOCK_CNT_H, 0xFF, 0); + + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_CFG2, 0xFF, + SD_CALCULATE_CRC7 | SD_CHECK_CRC16 | + SD_NO_WAIT_BUSY_END | SD_CHECK_CRC7 | SD_RSP_LEN_6); + + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_TRANSFER, 0xFF, + trans_mode | SD_TRANSFER_START); + rtsx_pci_add_cmd(pdev, CHECK_REG_CMD, SD_TRANSFER, + SD_TRANSFER_END, SD_TRANSFER_END); + + err = rtsx_pci_send_cmd(pdev, timeout); + if (err < 0) { + sd_print_debug_regs(pdev); + dev_dbg(&(pdev->pci->dev), + "rtsx_pci_send_cmd fail (err = %d)\n", err); + return err; + } + + return 0; +} + +static int sd_rw_multi(struct rtsx_pdev *pdev, void *buf, unsigned int blksz, + unsigned int blocks, unsigned int use_sg, int read, int uhs) +{ + u8 cfg2, trans_mode; + int err; + size_t data_len = blksz * blocks; + + if (read) { + cfg2 = SD_CALCULATE_CRC7 | SD_CHECK_CRC16 | + SD_NO_WAIT_BUSY_END | SD_CHECK_CRC7 | SD_RSP_LEN_0; + trans_mode = SD_TM_AUTO_READ_3; + } else { + cfg2 = SD_NO_CALCULATE_CRC7 | SD_CHECK_CRC16 | + SD_NO_WAIT_BUSY_END | SD_NO_CHECK_CRC7 | SD_RSP_LEN_0; + trans_mode = SD_TM_AUTO_WRITE_3; + } + + if (!uhs) + cfg2 |= SD_NO_CHECK_WAIT_CRC_TO; + + rtsx_pci_init_cmd(pdev); + + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_BYTE_CNT_L, 0xFF, 0x00); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_BYTE_CNT_H, 0xFF, 0x02); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_BLOCK_CNT_L, + 0xFF, (u8)blocks); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_BLOCK_CNT_H, + 0xFF, (u8)(blocks >> 8)); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, + CARD_DATA_SOURCE, 0x01, RING_BUFFER); + + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, IRQSTAT0, + DMA_DONE_INT, DMA_DONE_INT); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, DMATC3, + 0xFF, (u8)(data_len >> 24)); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, DMATC2, + 0xFF, (u8)(data_len >> 16)); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, DMATC1, + 0xFF, (u8)(data_len >> 8)); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, DMATC0, 0xFF, (u8)data_len); + if (read) { + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, DMACTL, + 0x03 | DMA_PACK_SIZE_MASK, + DMA_DIR_FROM_CARD | DMA_EN | DMA_512); + } else { + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, DMACTL, + 0x03 | DMA_PACK_SIZE_MASK, + DMA_DIR_TO_CARD | DMA_EN | DMA_512); + } + + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CARD_DATA_SOURCE, + 0x01, RING_BUFFER); + + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_TRANSFER, 0xFF, + trans_mode | SD_TRANSFER_START); + rtsx_pci_add_cmd(pdev, CHECK_REG_CMD, SD_TRANSFER, + SD_TRANSFER_END, SD_TRANSFER_END); + + rtsx_pci_send_cmd_no_wait(pdev); + + err = rtsx_pci_transfer_data(pdev, buf, data_len, use_sg, read, 10000); + if (err < 0) { + sd_clear_error(pdev); + + return err; + } + + return 0; +} + +static int sd_wait_voltage_stable_1(struct rtsx_pdev *pdev) +{ + int err; + u8 stat; + + /* Reference to Signal Voltage Switch Sequence in SD spec. + * Wait for a period of time so that the card can drive SD_CMD and + * SD_DAT[3:0] to low after sending back CMD11 response. + */ + mdelay(1); + + /* SD_CMD, SD_DAT[3:0] should be driven to low by card; + * If either one of SD_CMD,SD_DAT[3:0] is not low, + * abort the voltage switch sequence; + */ + err = rtsx_pci_read_register(pdev, SD_BUS_STAT, &stat); + if (err < 0) + return err; + + if (stat & (SD_CMD_STATUS | SD_DAT3_STATUS | SD_DAT2_STATUS | + SD_DAT1_STATUS | SD_DAT0_STATUS)) + return -EINVAL; + + /* Stop toggle SD clock */ + err = rtsx_pci_write_register(pdev, SD_BUS_STAT, + 0xFF, SD_CLK_FORCE_STOP); + if (err < 0) + return err; + + return 0; +} + +static int sd_wait_voltage_stable_2(struct rtsx_pdev *pdev) +{ + int err; + u8 stat, mask, val; + + /* Wait 1.8V output of voltage regulator in card stable */ + wait_timeout(50); + + /* Toggle SD clock again */ + err = rtsx_pci_write_register(pdev, SD_BUS_STAT, + 0xFF, SD_CLK_TOGGLE_EN); + if (err < 0) + return err; + + /* Wait for a period of time so that the card can drive + * SD_DAT[3:0] to high at 1.8V + */ + wait_timeout(10); + + /* SD_CMD, SD_DAT[3:0] should be pulled high by host */ + err = rtsx_pci_read_register(pdev, SD_BUS_STAT, &stat); + if (err < 0) + return err; + + mask = SD_CMD_STATUS | SD_DAT3_STATUS | SD_DAT2_STATUS | + SD_DAT1_STATUS | SD_DAT0_STATUS; + val = SD_CMD_STATUS | SD_DAT3_STATUS | SD_DAT2_STATUS | + SD_DAT1_STATUS | SD_DAT0_STATUS; + if ((stat & mask) != val) { + dev_dbg(&(pdev->pci->dev), + "%s: SD_BUS_STAT = 0x%x\n", __func__, stat); + rtsx_pci_write_register(pdev, SD_BUS_STAT, + SD_CLK_TOGGLE_EN | SD_CLK_FORCE_STOP, 0); + rtsx_pci_write_register(pdev, CARD_CLK_EN, 0xFF, 0); + return -EINVAL; + } + + return 0; +} + +static int sd_change_bank_voltage(struct rtsx_pdev *pdev, u8 voltage) +{ + int err; + + if (voltage == SD_IO_3V3) { + err = rtsx_pci_write_phy_register(pdev, 0x08, 0x4FC0 | 0x24); + if (err < 0) + return err; + } else if (voltage == SD_IO_1V8) { + err = rtsx_pci_write_phy_register(pdev, 0x08, 0x4C40 | 0x24); + if (err < 0) + return err; + } else { + return -EINVAL; + } + + return 0; +} + +static int sd_pull_ctl_enable(struct rtsx_pdev *pdev) +{ + int err; + + err = rtsx_pci_set_pull_ctl(pdev, pdev->sd_pull_ctl_enable_tbl); + if (err < 0) + return err; + + return 0; +} + +static int sd_pull_ctl_disable(struct rtsx_pdev *pdev) +{ + int err; + + err = rtsx_pci_set_pull_ctl(pdev, pdev->sd_pull_ctl_disable_tbl); + if (err < 0) + return err; + + return 0; +} + +static int sd_power_on(struct rtsx_pdev *pdev) +{ + int err; + + rtsx_pci_init_cmd(pdev); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CARD_SELECT, 0x07, SD_MOD_SEL); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CARD_SHARE_MODE, + CARD_SHARE_MASK, CARD_SHARE_48_SD); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CARD_CLK_EN, + SD_CLK_EN, SD_CLK_EN); + err = rtsx_pci_send_cmd(pdev, 100); + if (err < 0) + return err; + + err = sd_pull_ctl_enable(pdev); + if (err < 0) + return err; + + rtsx_pci_init_cmd(pdev); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CARD_PWR_CTL, + SD_POWER_MASK, SD_PARTIAL_POWER_ON); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, pdev->rval.ldo_pwr_suspend); + err = rtsx_pci_send_cmd(pdev, 100); + if (err < 0) + return err; + + /* To avoid too large in-rush current */ + udelay(150); + + rtsx_pci_init_cmd(pdev); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CARD_PWR_CTL, + SD_POWER_MASK, SD_POWER_ON); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, pdev->rval.ldo_pwr_on); + err = rtsx_pci_send_cmd(pdev, 100); + if (err < 0) + return err; + + err = rtsx_pci_write_register(pdev, CARD_OE, + SD_OUTPUT_EN, SD_OUTPUT_EN); + if (err < 0) + return err; + + return 0; +} + +static int sd_power_off(struct rtsx_pdev *pdev) +{ + int err; + + rtsx_pci_init_cmd(pdev); + + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CARD_CLK_EN, SD_CLK_EN, 0); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CARD_OE, SD_OUTPUT_EN, 0); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CARD_PWR_CTL, + SD_POWER_MASK | PMOS_STRG_MASK, + SD_POWER_OFF | PMOS_STRG_400mA); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, PWR_GATE_CTRL, + LDO3318_PWR_MASK, pdev->rval.ldo_pwr_off); + + err = rtsx_pci_send_cmd(pdev, 100); + if (err < 0) + return err; + + return sd_pull_ctl_disable(pdev); +} + +static int sd_change_phase(struct rtsx_pdev *pdev, u8 sample_point) +{ + int err; + + dev_dbg(&(pdev->pci->dev), "%s: sample_point = %d\n", + __func__, sample_point); + + rtsx_pci_init_cmd(pdev); + + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CLK_CTL, CHANGE_CLK, CHANGE_CLK); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_VPRX_CTL, 0x1F, sample_point); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_VPCLK0_CTL, + PHASE_NOT_RESET, 0); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_VPCLK0_CTL, + PHASE_NOT_RESET, PHASE_NOT_RESET); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CLK_CTL, CHANGE_CLK, 0); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_CFG1, + SD_ASYNC_FIFO_NOT_RST, 0); + + err = rtsx_pci_send_cmd(pdev, 100); + if (err < 0) + return err; + + return 0; +} + +static u8 sd_search_final_phase(struct rtsx_pdev *pdev, u32 phase_map) +{ + struct timing_phase_path path[MAX_PHASE + 1]; + int i, j, cont_path_cnt; + int new_block, max_len, final_path_idx; + u8 final_phase = 0xFF; + + /* Parse phase_map, take it as a bit-ring */ + cont_path_cnt = 0; + new_block = 1; + j = 0; + for (i = 0; i < MAX_PHASE + 1; i++) { + if (phase_map & (1 << i)) { + if (new_block) { + new_block = 0; + j = cont_path_cnt++; + path[j].start = i; + path[j].end = i; + } else { + path[j].end = i; + } + } else { + new_block = 1; + if (cont_path_cnt) { + /* Calculate path length and middle point */ + int idx = cont_path_cnt - 1; + path[idx].len = + path[idx].end - path[idx].start + 1; + path[idx].mid = + path[idx].start + path[idx].len / 2; + } + } + } + + if (cont_path_cnt == 0) { + dev_dbg(&(pdev->pci->dev), "No continuous phase path\n"); + goto finish; + } else { + /* Calculate last continuous path length and middle point */ + int idx = cont_path_cnt - 1; + path[idx].len = path[idx].end - path[idx].start + 1; + path[idx].mid = path[idx].start + path[idx].len / 2; + } + + /* Connect the first continuous path + * and the last one if they are adjacent */ + if (!path[0].start && (path[cont_path_cnt - 1].end == MAX_PHASE)) { + /* Using negative index */ + path[0].start = path[cont_path_cnt - 1].start - MAX_PHASE - 1; + path[0].len += path[cont_path_cnt - 1].len; + path[0].mid = path[0].start + path[0].len / 2; + /* Convert negative middle point index to positive one */ + if (path[0].mid < 0) + path[0].mid += MAX_PHASE + 1; + cont_path_cnt--; + } + + /* Choose the longest continuous phase path */ + max_len = 0; + final_phase = 0; + final_path_idx = 0; + for (i = 0; i < cont_path_cnt; i++) { + if (path[i].len > max_len) { + max_len = path[i].len; + final_phase = (u8)path[i].mid; + final_path_idx = i; + } + + dev_dbg(&(pdev->pci->dev), "path[%d].start = %d\n", + i, path[i].start); + dev_dbg(&(pdev->pci->dev), "path[%d].end = %d\n", + i, path[i].end); + dev_dbg(&(pdev->pci->dev), "path[%d].len = %d\n", + i, path[i].len); + dev_dbg(&(pdev->pci->dev), "path[%d].mid = %d\n", + i, path[i].mid); + } + +finish: + dev_dbg(&(pdev->pci->dev), "Final choosen phase: %d\n", final_phase); + return final_phase; +} + +static void sd_wait_data_idle(struct rtsx_pdev *pdev) +{ + int err, i; + u8 val = 0; + + for (i = 0; i < 100; i++) { + err = rtsx_pci_read_register(pdev, SD_DATA_STATE, &val); + if (val & SD_DATA_IDLE) + return; + + udelay(100); + } +} + +static int sd_tuning_rx_cmd(struct rtsx_pdev *pdev, u8 opcode, u8 sample_point) +{ + int err; + u8 cmd[5] = {0}; + + err = sd_change_phase(pdev, sample_point); + if (err < 0) + return err; + + cmd[0] = 0x40 | opcode; + err = sd_read_data(pdev, cmd, 0x40, NULL, 0, 100); + if (err < 0) { + /* Wait till SD DATA IDLE */ + sd_wait_data_idle(pdev); + sd_clear_error(pdev); + return err; + } + + return 0; +} + +static int sd_tuning_phase(struct rtsx_pdev *pdev, u8 opcode, u32 *phase_map) +{ + int err, i; + u32 raw_phase_map = 0; + + for (i = MAX_PHASE; i >= 0; i--) { + err = sd_tuning_rx_cmd(pdev, opcode, (u8)i); + if (err == 0) + raw_phase_map |= 1 << i; + } + + if (phase_map) + *phase_map = raw_phase_map; + + return 0; +} + +static int sd_tuning_rx(struct rtsx_pdev *pdev, u8 opcode) +{ + int err, i; + u32 raw_phase_map[RX_TUNING_CNT] = {0}, phase_map; + u8 final_phase; + + for (i = 0; i < RX_TUNING_CNT; i++) { + err = sd_tuning_phase(pdev, opcode, &(raw_phase_map[i])); + if (err < 0) + return err; + + if (raw_phase_map[i] == 0) + break; + } + + phase_map = 0xFFFFFFFF; + for (i = 0; i < RX_TUNING_CNT; i++) { + dev_dbg(&(pdev->pci->dev), "RX raw_phase_map[%d] = 0x%08x\n", + i, raw_phase_map[i]); + phase_map &= raw_phase_map[i]; + } + dev_dbg(&(pdev->pci->dev), "RX phase_map = 0x%08x\n", phase_map); + + if (phase_map) { + final_phase = sd_search_final_phase(pdev, phase_map); + if (final_phase == 0xFF) + return -EINVAL; + + err = sd_change_phase(pdev, final_phase); + if (err < 0) + return err; + } else { + return -EINVAL; + } + + return 0; +} + +int pci_sdmmc_set_bus_width(struct rtsx_adapter *adapter, + unsigned char bus_width) +{ + struct rtsx_pdev *pdev = dev_get_drvdata(adapter->dev.parent); + int err = 0; + u8 width[] = { + [MMC_BUS_WIDTH_1] = SD_BUS_WIDTH_1BIT, + [MMC_BUS_WIDTH_4] = SD_BUS_WIDTH_4BIT, + [MMC_BUS_WIDTH_8] = SD_BUS_WIDTH_8BIT, + }; + + mutex_lock(&pdev->pdev_mutex); + + rtsx_pci_start_run(pdev); + + if (bus_width <= MMC_BUS_WIDTH_8) + err = rtsx_pci_write_register(pdev, SD_CFG1, + 0x03, width[bus_width]); + + mutex_unlock(&pdev->pdev_mutex); + + return err; +} + +int pci_sdmmc_set_power_mode(struct rtsx_adapter *adapter, + unsigned char power_mode) +{ + struct rtsx_pdev *pdev = dev_get_drvdata(adapter->dev.parent); + int err; + + mutex_lock(&pdev->pdev_mutex); + + rtsx_pci_start_run(pdev); + + if (power_mode == MMC_POWER_OFF) + err = sd_power_off(pdev); + else + err = sd_power_on(pdev); + + mutex_unlock(&pdev->pdev_mutex); + + return err; +} + +int pci_sdmmc_set_timing(struct rtsx_adapter *adapter, unsigned char timing) +{ + struct rtsx_pdev *pdev = dev_get_drvdata(adapter->dev.parent); + struct sd_info *sd_card = &(pdev->sd_card); + int err = 0; + + mutex_lock(&pdev->pdev_mutex); + + rtsx_pci_start_run(pdev); + + sd_card->ddr_mode = 0; + + rtsx_pci_init_cmd(pdev); + + switch (timing) { + case MMC_TIMING_UHS_SDR104: + case MMC_TIMING_UHS_SDR50: + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_CFG1, + 0x0C | SD_ASYNC_FIFO_NOT_RST, + SD_30_MODE | SD_ASYNC_FIFO_NOT_RST); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CLK_CTL, + CLK_LOW_FREQ, CLK_LOW_FREQ); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CARD_CLK_SOURCE, 0xFF, + CRC_VAR_CLK0 | SD30_FIX_CLK | SAMPLE_VAR_CLK1); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CLK_CTL, CLK_LOW_FREQ, 0); + break; + + case MMC_TIMING_UHS_DDR50: + sd_card->ddr_mode = 1; + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_CFG1, + 0x0C | SD_ASYNC_FIFO_NOT_RST, + SD_DDR_MODE | SD_ASYNC_FIFO_NOT_RST); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CLK_CTL, + CLK_LOW_FREQ, CLK_LOW_FREQ); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CARD_CLK_SOURCE, 0xFF, + CRC_VAR_CLK0 | SD30_FIX_CLK | SAMPLE_VAR_CLK1); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CLK_CTL, CLK_LOW_FREQ, 0); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_PUSH_POINT_CTL, + DDR_VAR_TX_CMD_DAT, DDR_VAR_TX_CMD_DAT); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_SAMPLE_POINT_CTL, + DDR_VAR_RX_DAT | DDR_VAR_RX_CMD, + DDR_VAR_RX_DAT | DDR_VAR_RX_CMD); + break; + + case MMC_TIMING_MMC_HS: + case MMC_TIMING_SD_HS: + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_CFG1, + 0x0C, SD_20_MODE); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CLK_CTL, + CLK_LOW_FREQ, CLK_LOW_FREQ); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CARD_CLK_SOURCE, 0xFF, + CRC_FIX_CLK | SD30_VAR_CLK0 | SAMPLE_VAR_CLK1); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CLK_CTL, CLK_LOW_FREQ, 0); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_PUSH_POINT_CTL, + SD20_TX_SEL_MASK, SD20_TX_14_AHEAD); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_SAMPLE_POINT_CTL, + SD20_RX_SEL_MASK, SD20_RX_14_DELAY); + break; + + default: + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, + SD_CFG1, 0x0C, SD_20_MODE); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CLK_CTL, + CLK_LOW_FREQ, CLK_LOW_FREQ); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CARD_CLK_SOURCE, 0xFF, + CRC_FIX_CLK | SD30_VAR_CLK0 | SAMPLE_VAR_CLK1); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CLK_CTL, CLK_LOW_FREQ, 0); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, + SD_PUSH_POINT_CTL, 0xFF, 0); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_SAMPLE_POINT_CTL, + SD20_RX_SEL_MASK, SD20_RX_POS_EDGE); + break; + } + + err = rtsx_pci_send_cmd(pdev, 100); + + mutex_unlock(&pdev->pdev_mutex); + + return err; +} + +int pci_sdmmc_switch_voltage(struct rtsx_adapter *adapter, + unsigned char signal_voltage) +{ + struct rtsx_pdev *pdev = dev_get_drvdata(adapter->dev.parent); + int err = 0; + u8 voltage; + + dev_dbg(&(pdev->pci->dev), "%s: signal_voltage = %d\n", + __func__, signal_voltage); + + mutex_lock(&pdev->pdev_mutex); + + rtsx_pci_start_run(pdev); + + if (signal_voltage == MMC_SIGNAL_VOLTAGE_330) + voltage = SD_IO_3V3; + else + voltage = SD_IO_1V8; + + if (voltage == SD_IO_1V8) { + err = rtsx_pci_write_register(pdev, + SD30_DRIVE_SEL, 0x07, DRIVER_TYPE_B); + if (err < 0) + goto out; + + err = sd_wait_voltage_stable_1(pdev); + if (err < 0) + goto out; + } + + err = sd_change_bank_voltage(pdev, voltage); + if (err < 0) + goto out; + + if (voltage == SD_IO_1V8) { + err = sd_wait_voltage_stable_2(pdev); + if (err < 0) + goto out; + } + + /* Stop toggle SD clock in idle */ + err = rtsx_pci_write_register(pdev, SD_BUS_STAT, + SD_CLK_TOGGLE_EN | SD_CLK_FORCE_STOP, 0); + +out: + mutex_unlock(&pdev->pdev_mutex); + + return err; +} + +int pci_sdmmc_get_ro(struct rtsx_adapter *adapter) +{ + struct rtsx_pdev *pdev = dev_get_drvdata(adapter->dev.parent); + int ro = 0; + u32 val; + + mutex_lock(&pdev->pdev_mutex); + + rtsx_pci_start_run(pdev); + + /* Check SD Machanical Write-Protect Switch */ + val = rtsx_pci_readl(pdev, RTSX_BIPR); + dev_dbg(&(pdev->pci->dev), "%s: RTSX_BIPR = 0x%08x\n", __func__, val); + if (val & SD_WRITE_PROTECT) + ro = 1; + + mutex_unlock(&pdev->pdev_mutex); + + return ro; +} + +int pci_sdmmc_get_cd(struct rtsx_adapter *adapter) +{ + struct rtsx_pdev *pdev = dev_get_drvdata(adapter->dev.parent); + int cd = 0; + u32 val; + + mutex_lock(&pdev->pdev_mutex); + + rtsx_pci_start_run(pdev); + + /* Check SD Machanical Write-Protect Switch */ + val = rtsx_pci_readl(pdev, RTSX_BIPR); + dev_dbg(&(pdev->pci->dev), "%s: RTSX_BIPR = 0x%08x\n", __func__, val); + if (val & SD_EXIST) + cd = 1; + + mutex_unlock(&pdev->pdev_mutex); + + return cd; +} + +int pci_sdmmc_execute_tuning(struct rtsx_adapter *adapter) +{ + struct rtsx_pdev *pdev = dev_get_drvdata(adapter->dev.parent); + int err = 0; + + mutex_lock(&pdev->pdev_mutex); + + rtsx_pci_start_run(pdev); + + if (!pdev->sd_card.ddr_mode) + err = sd_tuning_rx(pdev, MMC_SEND_TUNING_BLOCK); + + mutex_unlock(&pdev->pdev_mutex); + + return err; +} + +int pci_sdmmc_send_cmd_get_rsp(struct rtsx_adapter *adapter, u8 cmd_idx, + u32 arg, unsigned int resp_type, u32 *resp) +{ + struct rtsx_pdev *pdev = dev_get_drvdata(adapter->dev.parent); + int err = 0; + int timeout = 100; + int i; + u8 *ptr; + int stat_idx = 0; + u8 rsp_type; + int rsp_len = 5; + + dev_dbg(&(pdev->pci->dev), "%s: SD/MMC CMD %d, arg = 0x%08x\n", + __func__, cmd_idx, arg); + + mutex_lock(&pdev->pdev_mutex); + + rtsx_pci_start_run(pdev); + + /* Response type */ + /* R0 + * R1, R5, R6, R7 + * R1b + * R2 + * R3, R4 + */ + switch (resp_type) { + case MMC_RSP_NONE: + rsp_type = SD_RSP_TYPE_R0; + rsp_len = 0; + break; + case MMC_RSP_R1: + rsp_type = SD_RSP_TYPE_R1; + break; + case MMC_RSP_R1B: + rsp_type = SD_RSP_TYPE_R1b; + break; + case MMC_RSP_R2: + rsp_type = SD_RSP_TYPE_R2; + rsp_len = 16; + break; + case MMC_RSP_R3: + rsp_type = SD_RSP_TYPE_R3; + break; + default: + dev_dbg(&(pdev->pci->dev), "cmd->flag is not valid\n"); + err = -EINVAL; + goto out; + } + + if (rsp_type == SD_RSP_TYPE_R1b) + timeout = 3000; + + if (cmd_idx == SD_SWITCH_VOLTAGE) { + err = rtsx_pci_write_register(pdev, SD_BUS_STAT, + 0xFF, SD_CLK_TOGGLE_EN); + if (err < 0) + goto out; + } + + rtsx_pci_init_cmd(pdev); + + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_CMD0, 0xFF, 0x40 | cmd_idx); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_CMD1, 0xFF, (u8)(arg >> 24)); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_CMD2, 0xFF, (u8)(arg >> 16)); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_CMD3, 0xFF, (u8)(arg >> 8)); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_CMD4, 0xFF, (u8)arg); + + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_CFG2, 0xFF, rsp_type); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, CARD_DATA_SOURCE, + 0x01, PINGPONG_BUFFER); + rtsx_pci_add_cmd(pdev, WRITE_REG_CMD, SD_TRANSFER, + 0xFF, SD_TM_CMD_RSP | SD_TRANSFER_START); + rtsx_pci_add_cmd(pdev, CHECK_REG_CMD, SD_TRANSFER, + SD_TRANSFER_END | SD_STAT_IDLE, + SD_TRANSFER_END | SD_STAT_IDLE); + + if (rsp_type == SD_RSP_TYPE_R2) { + /* Read data from ping-pong buffer */ + for (i = PPBUF_BASE2; i < PPBUF_BASE2 + 16; i++) + rtsx_pci_add_cmd(pdev, READ_REG_CMD, (u16)i, 0, 0); + stat_idx = 16; + } else if (rsp_type != SD_RSP_TYPE_R0) { + /* Read data from SD_CMDx registers */ + for (i = SD_CMD0; i <= SD_CMD4; i++) + rtsx_pci_add_cmd(pdev, READ_REG_CMD, (u16)i, 0, 0); + stat_idx = 5; + } + + rtsx_pci_add_cmd(pdev, READ_REG_CMD, SD_STAT1, 0, 0); + + err = rtsx_pci_send_cmd(pdev, timeout); + if (err < 0) { + sd_print_debug_regs(pdev); + sd_clear_error(pdev); + dev_dbg(&(pdev->pci->dev), + "rtsx_pci_send_cmd error (err = %d)\n", err); + goto out; + } + + if (rsp_type == SD_RSP_TYPE_R0) { + err = 0; + goto out; + } + + /* Eliminate returned value of CHECK_REG_CMD */ + ptr = rtsx_pci_get_cmd_data(pdev) + 1; + + /* Check (Start,Transmission) bit of Response */ + if ((ptr[0] & 0xC0) != 0) { + err = -EILSEQ; + dev_dbg(&(pdev->pci->dev), "Invalid response bit\n"); + goto out; + } + + /* Check CRC7 */ + if (!(rsp_type & SD_NO_CHECK_CRC7)) { + if (ptr[stat_idx] & SD_CRC7_ERR) { + err = -EILSEQ; + dev_dbg(&(pdev->pci->dev), "CRC7 error\n"); + goto out; + } + } + + if (rsp_type == SD_RSP_TYPE_R2) { + for (i = 0; i < 4; i++) { + resp[i] = get_unaligned_be32(ptr + 1 + i * 4); + dev_dbg(&(pdev->pci->dev), "cmd->resp[%d] = 0x%08x\n", + i, resp[i]); + } + } else { + resp[0] = get_unaligned_be32(ptr + 1); + dev_dbg(&(pdev->pci->dev), "cmd->resp[0] = 0x%08x\n", resp[0]); + } + +out: + mutex_unlock(&pdev->pdev_mutex); + + return err; +} + +int pci_sdmmc_read_data(struct rtsx_adapter *adapter, u8 *cmd, + u16 byte_cnt, u8 *buf, int buf_len, int timeout) +{ + struct rtsx_pdev *pdev = dev_get_drvdata(adapter->dev.parent); + int err; + + mutex_lock(&pdev->pdev_mutex); + rtsx_pci_start_run(pdev); + err = sd_read_data(pdev, cmd, byte_cnt, buf, buf_len, timeout); + mutex_unlock(&pdev->pdev_mutex); + + return err; +} + +int pci_sdmmc_write_data(struct rtsx_adapter *adapter, u8 *cmd, + u16 byte_cnt, u8 *buf, int buf_len, int timeout) +{ + struct rtsx_pdev *pdev = dev_get_drvdata(adapter->dev.parent); + int err; + + mutex_lock(&pdev->pdev_mutex); + rtsx_pci_start_run(pdev); + err = sd_write_data(pdev, cmd, byte_cnt, buf, buf_len, timeout); + mutex_unlock(&pdev->pdev_mutex); + + return err; +} + +int pci_sdmmc_rw_multi(struct rtsx_adapter *adapter, void *buf, + unsigned int blksz, unsigned int blocks, + unsigned int use_sg, int read, int uhs) +{ + struct rtsx_pdev *pdev = dev_get_drvdata(adapter->dev.parent); + int err; + + mutex_lock(&pdev->pdev_mutex); + rtsx_pci_start_run(pdev); + err = sd_rw_multi(pdev, buf, blksz, blocks, use_sg, read, uhs); + mutex_unlock(&pdev->pdev_mutex); + + return err; +} diff --git a/drivers/misc/realtek_cr/pci/sdmmc.h b/drivers/misc/realtek_cr/pci/sdmmc.h new file mode 100644 index 0000000..308ed7e --- /dev/null +++ b/drivers/misc/realtek_cr/pci/sdmmc.h @@ -0,0 +1,48 @@ +/* Driver for Realtek PCI-Express card reader + * + * Copyright(c) 2009 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 as published by the + * Free Software Foundation; either version 2, or (at your option) any + * later version. + * + * 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: + * Wei WANG <wei_wang@xxxxxxxxxxxxxx> + * No. 450, Shenhu Road, Suzhou Industry Park, Suzhou, China + */ + +#ifndef __RTSX_PCI_SDMMC_H +#define __RTSX_PCI_SDMMC_H + +struct rtsx_adapter; + +int pci_sdmmc_set_bus_width(struct rtsx_adapter *adapter, + unsigned char bus_width); +int pci_sdmmc_set_power_mode(struct rtsx_adapter *adapter, + unsigned char power_mode); +int pci_sdmmc_set_timing(struct rtsx_adapter *adapter, unsigned char timing); +int pci_sdmmc_switch_voltage(struct rtsx_adapter *adapter, + unsigned char signal_voltage); +int pci_sdmmc_get_ro(struct rtsx_adapter *adapter); +int pci_sdmmc_get_cd(struct rtsx_adapter *adapter); +int pci_sdmmc_execute_tuning(struct rtsx_adapter *adapter); +int pci_sdmmc_send_cmd_get_rsp(struct rtsx_adapter *adapter, u8 cmd_idx, + u32 arg, unsigned int resp_type, u32 *resp); +int pci_sdmmc_read_data(struct rtsx_adapter *adapter, u8 *cmd, + u16 byte_cnt, u8 *buf, int buf_len, int timeout); +int pci_sdmmc_write_data(struct rtsx_adapter *adapter, u8 *cmd, + u16 byte_cnt, u8 *buf, int buf_len, int timeout); +int pci_sdmmc_rw_multi(struct rtsx_adapter *adapter, void *buf, + unsigned int blksz, unsigned int blocks, + unsigned int use_sg, int read, int uhs); + +#endif -- 1.7.9.5 -- To unsubscribe from this list: send the line "unsubscribe linux-mmc" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html