diff mbox series

mmc: add TS7800 FPGA based MMC controller driver

Message ID 20221018133800.10636-1-firas.ashkar@savoirfairelinux.com
State New
Headers show
Series mmc: add TS7800 FPGA based MMC controller driver | expand

Commit Message

Firas Ashkar Oct. 18, 2022, 1:38 p.m. UTC
add standard mmc/host controller driver for TS-7800v1, instead of the
original block based 'tssdcore' driver provided by EmbeddedTS linux-2.6.x
code base.

$ cat /proc/cpuinfo
processor	: 0
model name	: Feroceon rev 0 (v5l)
BogoMIPS	: 333.33
Features	: swp half thumb fastmult edsp
CPU implementer	: 0x41
CPU architecture: 5TEJ
CPU variant	: 0x0
CPU part	: 0x926
CPU revision	: 0

Hardware	: Technologic Systems TS-78xx SBC
Revision	: 0000
Serial		: 0000000000000000
$

$ uname -a
Linux ts-7800 6.1.0-rc1 #3 PREEMPT Mon Oct 17 15:58:49 EDT 2022 armv5tel GNU/Linux
$

$ lsmod
Module                  Size  Used by    Not tainted
$

$ fdisk -lu
mtdblock: MTD device 'mbr' is NAND, please consider using UBI block devices instead.
mtdblock: MTD device 'kernel' is NAND, please consider using UBI block devices instead.
mtdblock: MTD device 'initrd' is NAND, please consider using UBI block devices instead.
mtdblock: MTD device 'rootfs' is NAND, please consider using UBI block devices instead.
$

$ cat /proc/iomem
00000000-07ffffff : System RAM
  00008000-00978fff : Kernel code
  00d68000-00e9cf97 : Kernel data
e8000044-e8000047 : timeriomem_rng
  e8000044-e8000047 : timeriomem_rng timeriomem_rng
e8000100-e80001ff : ts_sdmmc_ctrl
e8000200-e80002ff : ts_irqc
  e8000200-e80002ff : ts7800-irqc ts_irqc
e8000400-e80004ff : ts_dmac
  e8000400-e80004ff : ts7800-dmac ts_dmac
e8000804-e8000807 : gen_nand
  e8000804-e8000807 : gen_nand gen_nand
e8000808-e8000808 : rtc-m48t86
  e8000808-e8000808 : rtc-m48t86 rtc-m48t86
e800080c-e800080c : rtc-m48t86
  e800080c-e800080c : rtc-m48t86 rtc-m48t86
f1012000-f10120ff : serial8250.0
  f1012000-f101201f : serial
f1012100-f10121ff : serial8250.1
  f1012100-f101211f : serial
f1020108-f102010b : orion_wdt
f1020300-f1020303 : orion_wdt
f1020400-f102040f : ts_bridge
  f1020400-f102040f : ts7800-irqc ts_bridge
f1050000-f1050fff : orion-ehci.0
  f1050000-f1050fff : orion-ehci.0 orion-ehci.0
f1060900-f10609ff : xor 0 low
f1060b00-f1060bff : xor 0 high
f1072000-f1075fff : ge00 base
  f1072004-f1072087 : ge00 mvmdio base
f1080000-f1084fff : sata base
f1090000-f109ffff : regs
  f1090000-f109ffff : mv_crypto regs
f10a0000-f10a0fff : orion-ehci.1
  f10a0000-f10a0fff : orion-ehci.1 orion-ehci.1
f2200000-f2201fff : sram
  f2200000-f2201fff : mv_crypto sram
$

$ modprobe ts7800v1_sdmmc
ts7800v1_sdmmc ts7800v1_sdmmc: Detected SDCoreV2
ts7800v1_sdmmc ts7800v1_sdmmc: TS-7800v1 FPGA based SD/MMC Controller initialized
mmc0: new high speed SDHC card at address e624
mmcblk0: mmc0:e624 SU08G 7.40 GiB
 mmcblk0: p1 p2 p3

$

$ fdisk -lu
mtdblock: MTD device 'mbr' is NAND, please consider using UBI block devices instead.
mtdblock: MTD device 'kernel' is NAND, please consider using UBI block devices instead.
mtdblock: MTD device 'initrd' is NAND, please consider using UBI block devices instead.
mtdblock: MTD device 'rootfs' is NAND, please consider using UBI block devices instead.
Disk /dev/mmcblk0: 7580 MB, 7948206080 bytes, 15523840 sectors
242560 cylinders, 4 heads, 16 sectors/track
Units: sectors of 1 * 512 = 512 bytes

Device       Boot StartCHS    EndCHS        StartLBA     EndLBA    Sectors  Size Id Type
/dev/mmcblk0p1    0,0,2       8,40,33              1     131072     131072 64.0M da Unknown
/dev/mmcblk0p2    8,40,34     8,157,42        131073     138452       7380 3690K da Unknown
/dev/mmcblk0p3    8,157,43    73,226,46       138453    1187028    1048576  512M 83 Linux
$

$ cat /proc/iomem
00000000-07ffffff : System RAM
  00008000-00978fff : Kernel code
  00d68000-00e9cf97 : Kernel data
e8000044-e8000047 : timeriomem_rng
  e8000044-e8000047 : timeriomem_rng timeriomem_rng
e8000100-e80001ff : ts_sdmmc_ctrl
  e8000100-e80001ff : ts7800v1_sdmmc ts_sdmmc_ctrl
e8000200-e80002ff : ts_irqc
  e8000200-e80002ff : ts7800-irqc ts_irqc
e8000400-e80004ff : ts_dmac
  e8000400-e80004ff : ts7800-dmac ts_dmac
e8000804-e8000807 : gen_nand
  e8000804-e8000807 : gen_nand gen_nand
e8000808-e8000808 : rtc-m48t86
  e8000808-e8000808 : rtc-m48t86 rtc-m48t86
e800080c-e800080c : rtc-m48t86
  e800080c-e800080c : rtc-m48t86 rtc-m48t86
f1012000-f10120ff : serial8250.0
  f1012000-f101201f : serial
f1012100-f10121ff : serial8250.1
  f1012100-f101211f : serial
f1020108-f102010b : orion_wdt
f1020300-f1020303 : orion_wdt
f1020400-f102040f : ts_bridge
  f1020400-f102040f : ts7800-irqc ts_bridge
f1050000-f1050fff : orion-ehci.0
  f1050000-f1050fff : orion-ehci.0 orion-ehci.0
f1060900-f10609ff : xor 0 low
f1060b00-f1060bff : xor 0 high
f1072000-f1075fff : ge00 base
  f1072004-f1072087 : ge00 mvmdio base
f1080000-f1084fff : sata base
f1090000-f109ffff : regs
  f1090000-f109ffff : mv_crypto regs
f10a0000-f10a0fff : orion-ehci.1
  f10a0000-f10a0fff : orion-ehci.1 orion-ehci.1
f2200000-f2201fff : sram
  f2200000-f2201fff : mv_crypto sram
$

$ mkfs.ext4 -L misc /dev/mmcblk0p3
mke2fs 1.46.5 (30-Dec-2021)
Discarding device blocks: done
Creating filesystem with 131072 4k blocks and 32768 inodes
Filesystem UUID: d5acd725-a775-4e92-81a3-3fdf28880884
Superblock backups stored on blocks:
	32768, 98304

Allocating group tables: done
Writing inode tables: done
Creating journal (4096 blocks): done
Writing superblocks and filesystem accounting information: done

$

$ mount -o sync /dev/mmcblk0p3 /mnt/
EXT4-fs (mmcblk0p3): mounted filesystem with ordered data mode. Quota mode: disabled.
$
$ mount
rootfs on / type rootfs (rw,size=55424k,nr_inodes=13856)
proc on /proc type proc (rw,relatime)
devpts on /dev/pts type devpts (rw,relatime,gid=5,mode=620,ptmxmode=666)
tmpfs on /dev/shm type tmpfs (rw,relatime,mode=777)
tmpfs on /tmp type tmpfs (rw,relatime)
tmpfs on /run type tmpfs (rw,nosuid,nodev,relatime,mode=755)
sysfs on /sys type sysfs (rw,relatime)
/dev/mmcblk0p3 on /mnt type ext4 (rw,sync,relatime)
$

$ ls -lsrt /mnt/
total 16
    16 drwx------    2 root     root         16384 Oct 17 20:12 lost+found
$

$ time cp /lib/libc.so.6 /mnt/
real	0m 1.48s
user	0m 0.01s
sys	0m 0.07s
$

$ ls -lsrt /mnt/
total 1436
    16 drwx------    2 root     root         16384 Oct 17 20:12 lost+found
  1420 -rwxr-xr-x    1 root     root       1450164 Oct 17 20:14 libc.so.6
$

$ sha512sum /mnt/libc.so.6
b7e088bb52ebff255d1e19425513324322b28c0834cf89393daa96f725e19b19650c3a3f529659986323b1a52882131554f0669ba4749e8ccdfc877b11763dbf  /mnt/libc.so.6
$

$ sha512sum /lib/libc.so.6
b7e088bb52ebff255d1e19425513324322b28c0834cf89393daa96f725e19b19650c3a3f529659986323b1a52882131554f0669ba4749e8ccdfc877b11763dbf  /lib/libc.so.6
$

$ sync
$ umount /mnt/
EXT4-fs (mmcblk0p3): unmounting filesystem.
$

Signed-off-by: Firas Ashkar <firas.ashkar@savoirfairelinux.com>
---
:100644 100644 2f4fe3ca5c1a b4dad5b2921d M	arch/arm/mach-orion5x/ts78xx-fpga.h
:100644 100644 af810e7ccd79 8b98395359e4 M	arch/arm/mach-orion5x/ts78xx-setup.c
:100644 100644 f324daadaf70 62b7f9a977ea M	drivers/mmc/host/Kconfig
:100644 100644 4e4ceb32c4b4 34e19d3be7d0 M	drivers/mmc/host/Makefile
:000000 100644 000000000000 b63089f8b9a1 A	drivers/mmc/host/ts7800v1_sdmmc.c
 arch/arm/mach-orion5x/ts78xx-fpga.h  |    1 +
 arch/arm/mach-orion5x/ts78xx-setup.c |   54 +
 drivers/mmc/host/Kconfig             |   10 +
 drivers/mmc/host/Makefile            |    1 +
 drivers/mmc/host/ts7800v1_sdmmc.c    | 2301 ++++++++++++++++++++++++++
 5 files changed, 2367 insertions(+)

Comments

Arnd Bergmann Oct. 18, 2022, 7:06 p.m. UTC | #1
On Tue, Oct 18, 2022, at 15:38, Firas Ashkar wrote:
> add standard mmc/host controller driver for TS-7800v1, instead of the
> original block based 'tssdcore' driver provided by EmbeddedTS linux-2.6.x
> code base.

I'm looking at this driver since Marc pointed me to your
work on the platform and I noticed this post as well.
Not doing a full review, but I'm still pointing out a few
issue that caught my eye.

> +
> +#define DRIVER_NAME "ts7800v1_sdmmc"

Maybe drop this macro and just use the string directly.

> +
> +	if (IS_ERR_OR_NULL(pslot->rw_dma_buf)) {
> +		dev_warn(mmc_dev(ts_sdmmc_host->mmc_host),
> +			 "%s|%d - Error, No allocated DMA read buffer %ld\n",
> +			 __func__, __LINE__, PTR_ERR(pslot->rw_dma_buf));
> +		*data_error = ret = -ENOMEM;
> +		goto done;
> +	}
> +
> +	dat0_sent_crc16_buf = kzalloc(sizeof(u16), GFP_KERNEL);
> +	if (IS_ERR_OR_NULL(dat0_sent_crc16_buf)) {

You should never need IS_ERR_OR_NULL, as all interfaces
in the kernel are supposed to either return an error code
or return NULL on error. Please fix the error handling
for this throughout the driver.

> + spin_lock_bh(&ts_sdmmc_host->bh_lock);

I'm a bit confused by your locking. Why do you use
spin_lock_bh() instead of a normal spin_lock() or
a spin_lock_irq()? I don't see any use of softirqs
(typically tasklets and timers) in this driver, so
disabling softirqs in the critical section should
not change anything.

     Arnd
Firas Ashkar Oct. 20, 2022, 2:42 p.m. UTC | #2
thank you for the comments, pls check below for replies and acks
Arnd Bergmann Oct. 20, 2022, 2:44 p.m. UTC | #3
On Thu, Oct 20, 2022, at 16:42, firas ashkar wrote:
>> 
>> > +
>> > +#define DRIVER_NAME "ts7800v1_sdmmc"
>> 
>> Maybe drop this macro and just use the string directly.
> This define macro used in multiple locations

Yes, exactly that is the problem. Other drivers use the
same macro name, so it's very hard to grep for the actual
string.

    Arnd
diff mbox series

Patch

diff --git a/arch/arm/mach-orion5x/ts78xx-fpga.h b/arch/arm/mach-orion5x/ts78xx-fpga.h
index 2f4fe3ca5c1a..b4dad5b2921d 100644
--- a/arch/arm/mach-orion5x/ts78xx-fpga.h
+++ b/arch/arm/mach-orion5x/ts78xx-fpga.h
@@ -31,6 +31,7 @@  struct fpga_devices {
 	/* Technologic Systems */
 	struct fpga_device	ts_rtc;
 	struct fpga_device	ts_nand;
+	struct fpga_device	ts_sdmmc;
 	struct fpga_device	ts_rng;
 };
 
diff --git a/arch/arm/mach-orion5x/ts78xx-setup.c b/arch/arm/mach-orion5x/ts78xx-setup.c
index af810e7ccd79..8b98395359e4 100644
--- a/arch/arm/mach-orion5x/ts78xx-setup.c
+++ b/arch/arm/mach-orion5x/ts78xx-setup.c
@@ -279,6 +279,48 @@  static void ts78xx_ts_nand_unload(void)
 	platform_device_del(&ts78xx_ts_nand_device);
 }
 
+/*****************************************************************************
+ * SD/MMC Host controller
+ ****************************************************************************/
+#define TS_SDMMC_CTRL (TS78XX_FPGA_REGS_PHYS_BASE + 0x100)
+#define TS_SDMMC_SDBUSY 0x41
+
+static struct resource ts78xx_ts_sdmmc_resources[] = {
+	DEFINE_RES_MEM_NAMED(TS_SDMMC_CTRL, 0x100, "ts_sdmmc_ctrl"),
+	DEFINE_RES_IRQ_NAMED(TS_SDMMC_SDBUSY, "ts_sdmmc_sdbusy"),
+};
+
+static struct platform_device ts78xx_ts_sdmmc_device = {
+	.name = "ts7800v1_sdmmc",
+	.id = -1,
+	.resource = ts78xx_ts_sdmmc_resources,
+	.num_resources = ARRAY_SIZE(ts78xx_ts_sdmmc_resources),
+};
+
+static int ts78xx_ts_sdmmc_load(void)
+{
+	int rc;
+
+	if (ts78xx_fpga.supports.ts_sdmmc.init == 0) {
+		rc = platform_device_register(&ts78xx_ts_sdmmc_device);
+		if (!rc)
+			ts78xx_fpga.supports.ts_sdmmc.init = 1;
+	} else {
+		rc = platform_device_add(&ts78xx_ts_sdmmc_device);
+	}
+
+	if (rc)
+		pr_info("SD/MMC host controller could not be registered: %d\n",
+			rc);
+
+	return rc;
+}
+
+static void ts78xx_ts_sdmmc_unload(void)
+{
+	platform_device_del(&ts78xx_ts_sdmmc_device);
+}
+
 /*****************************************************************************
  * HW RNG
  ****************************************************************************/
@@ -329,6 +371,7 @@  static void ts78xx_fpga_devices_zero_init(void)
 {
 	ts78xx_fpga.supports.ts_rtc.init = 0;
 	ts78xx_fpga.supports.ts_nand.init = 0;
+	ts78xx_fpga.supports.ts_sdmmc.init = 0;
 	ts78xx_fpga.supports.ts_rng.init = 0;
 }
 
@@ -347,6 +390,7 @@  static void ts78xx_fpga_supports(void)
 	case TS7800_REV_9:
 		ts78xx_fpga.supports.ts_rtc.present = 1;
 		ts78xx_fpga.supports.ts_nand.present = 1;
+		ts78xx_fpga.supports.ts_sdmmc.present = 1;
 		ts78xx_fpga.supports.ts_rng.present = 1;
 		break;
 	default:
@@ -357,11 +401,13 @@  static void ts78xx_fpga_supports(void)
 				ts78xx_fpga.id & 0xff);
 			ts78xx_fpga.supports.ts_rtc.present = 1;
 			ts78xx_fpga.supports.ts_nand.present = 1;
+			ts78xx_fpga.supports.ts_sdmmc.present = 1;
 			ts78xx_fpga.supports.ts_rng.present = 1;
 			break;
 		default:
 			ts78xx_fpga.supports.ts_rtc.present = 0;
 			ts78xx_fpga.supports.ts_nand.present = 0;
+			ts78xx_fpga.supports.ts_sdmmc.present = 0;
 			ts78xx_fpga.supports.ts_rng.present = 0;
 		}
 	}
@@ -383,6 +429,12 @@  static int ts78xx_fpga_load_devices(void)
 			ts78xx_fpga.supports.ts_nand.present = 0;
 		ret |= tmp;
 	}
+	if (ts78xx_fpga.supports.ts_sdmmc.present == 1) {
+		tmp = ts78xx_ts_sdmmc_load();
+		if (tmp)
+			ts78xx_fpga.supports.ts_sdmmc.present = 0;
+		ret |= tmp;
+	}
 	if (ts78xx_fpga.supports.ts_rng.present == 1) {
 		tmp = ts78xx_ts_rng_load();
 		if (tmp)
@@ -400,6 +452,8 @@  static int ts78xx_fpga_unload_devices(void)
 		ts78xx_ts_rtc_unload();
 	if (ts78xx_fpga.supports.ts_nand.present == 1)
 		ts78xx_ts_nand_unload();
+	if (ts78xx_fpga.supports.ts_sdmmc.present == 1)
+		ts78xx_ts_sdmmc_unload();
 	if (ts78xx_fpga.supports.ts_rng.present == 1)
 		ts78xx_ts_rng_unload();
 
diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig
index f324daadaf70..62b7f9a977ea 100644
--- a/drivers/mmc/host/Kconfig
+++ b/drivers/mmc/host/Kconfig
@@ -709,6 +709,16 @@  config MMC_TMIO
 	  This provides support for the SD/MMC cell found in TC6393XB,
 	  T7L66XB and also HTC ASIC3
 
+config MMC_TS7800
+	tristate "EmbeddedTS 7800v1 FPGA based MMC Controller"
+	depends on MACH_TS78XX
+	help
+	  This provides support for EmbeddedTS MMC core on TS-7800-V1 platform,
+	  only standard MMC SLOT1 is supported.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called ts7800v1_sdmmc.
+
 config MMC_SDHI
 	tristate "Renesas SDHI SD/SDIO controller support"
 	depends on SUPERH || ARCH_RENESAS || COMPILE_TEST
diff --git a/drivers/mmc/host/Makefile b/drivers/mmc/host/Makefile
index 4e4ceb32c4b4..34e19d3be7d0 100644
--- a/drivers/mmc/host/Makefile
+++ b/drivers/mmc/host/Makefile
@@ -38,6 +38,7 @@  obj-$(CONFIG_MMC_S3C)   	+= s3cmci.o
 obj-$(CONFIG_MMC_SDRICOH_CS)	+= sdricoh_cs.o
 obj-$(CONFIG_MMC_TMIO)		+= tmio_mmc.o
 obj-$(CONFIG_MMC_TMIO_CORE)	+= tmio_mmc_core.o
+obj-$(CONFIG_MMC_TS7800)	+= ts7800v1_sdmmc.o
 obj-$(CONFIG_MMC_SDHI)		+= renesas_sdhi_core.o
 obj-$(CONFIG_MMC_SDHI_SYS_DMAC)		+= renesas_sdhi_sys_dmac.o
 obj-$(CONFIG_MMC_SDHI_INTERNAL_DMAC)	+= renesas_sdhi_internal_dmac.o
diff --git a/drivers/mmc/host/ts7800v1_sdmmc.c b/drivers/mmc/host/ts7800v1_sdmmc.c
new file mode 100644
index 000000000000..b63089f8b9a1
--- /dev/null
+++ b/drivers/mmc/host/ts7800v1_sdmmc.c
@@ -0,0 +1,2301 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * TS-7800 FPGA based SD/MMC host/controller driver
+ *
+ * Copyright (C) 2022 Savoir-faire Linux Inc.
+ *
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/ioport.h>
+#include <linux/errno.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/clk.h>
+#include <linux/interrupt.h>
+#include <linux/types.h>
+
+#include <linux/mmc/host.h>
+#include <linux/mmc/mmc.h>
+#include <linux/mmc/sd.h>
+
+#include <linux/bitfield.h>
+#include <asm/byteorder.h>
+
+#define DRIVER_NAME "ts7800v1_sdmmc"
+#define SDCORE2_SDCMD_REG 0x8
+#define SDCORE2_SDDATA_REG 0x4
+#define SDCORE2_SDBUS_REG 0xc
+
+#define SD_ACTIVE_SLOT 0x1
+#define NUM_SD_SLOTS 0x2
+#define NUM_MEM_RESOURCES 0x1
+#define MAX_CMD_BYTES 0x6
+#define NORM_RESP_BYTES 0x6
+#define LONG_RESP_BYTES 0x11
+#define BYTE_CLK_CYCLES 0x8
+#define DAT03_NIBBLES_PER_CLK_CYCLE 0x1
+#define BYTE_CYCLES_MASK 0xff
+#define NIBBLE_CLK_CYCLES 0x4
+#define CRC7_CYCLES 0x7
+#define CRC7_CYCLES_MASK 0x7f
+#define CRC16_CYCLES 0x10
+#define CRC16_CYCLES_MASK 0xffff
+#define CRC_POLY 0x1021
+
+#define CMDENB_DATENB_SDCLKL_SDCMDH_SDDAT03L 0x10
+#define CMDENB_DATENB_SDCLKH_SDCMDH_SDDAT03L 0x30
+#define CMDENB_DATENB_SDCLKH_SDCMDH_SDDAT03H 0x3f
+#define CMDENB_DATENB_SDCLKL_SDCMDH_SDDATL 0x10
+#define CMDENB_DATENB_SDCLKL_SDCMDH_SDDAT0L 0x1e
+#define CMDENB_DATENB_SDCLKL_SDCMDH_SDDATH 0x1f
+#define CMDENB_DATENB_SDCLKH_SDCMDH_SDDAT0L 0x3e
+#define CMDTRI_DAT0ENB_SDCLKL_SDCMDH_SDDAT0L 0x5e
+#define CMDTRI_DAT0ENB_SDCLKL_SDCMDH_SDDAT0H 0x5f
+#define CMDTRI_DAT0ENB_SDCLKH_SDCMDH_SDDAT0L 0x7e
+#define CMDTRI_DAT0ENB_SDCLKH_SDCMDH_SDDAT0H 0x7f
+#define CMDENB_DATTRI_SDCLKL_SDCMDL_SDDATH 0x8f
+#define CMDENB_DATTRI_SDCLKH_SDCMDL_SDDATH 0xaf
+#define CMDENB_DATTRI_SDCLKL_SDCMDH_SDDATH 0x9f
+#define CMDENB_DATTRI_SDCLKH_SDCMDH_SDDATH 0xbf
+#define CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH 0xdf
+#define CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH 0xff
+
+#define DATSSP_4BIT (1 << 5)
+#define SD_HC BIT(6)
+#define SD_MULTI_BLK BIT(7)
+#define SD_LOWSPEED BIT(8)
+#define SD_SELECTED BIT(9)
+#define SD_RESET BIT(10)
+
+#define MAX_RESP_TIMEOUT_MICROSECS 500
+#define MAX_BUSY_TIMEOUT_MICROSECS 5000
+#define MAX_BLK_SIZE 0x200
+#define MAX_BLK_COUNT 0x400
+#define MAX_BLK_SIZE_DWORDS 0x80
+#define MAX_BLK_SIZE_NIBBLES 0x400
+
+/* TS7800v SD/MMC FIFO size */
+#define MAX_SEG_SIZE 0x1000
+#define MAX_SEGS 0x400
+
+enum bit_endianness { LE_ENDIAN, BE_ENDIAN };
+
+struct ts7800v1_sdmmc_slot {
+	bool sd_detect;
+	bool sd_wprot;
+	u32 sd_state;
+	u32 cmd_timeout;
+	u8 *rw_dma_buf;
+	u32 blk_buf_cycle_indx;
+	u32 blk_buf_nibble_indx;
+	int sg_count;
+	u8 response[LONG_RESP_BYTES];
+	u8 cmdptr[MAX_CMD_BYTES];
+};
+
+struct ts7800v1_sdmmc_host {
+	struct mmc_host *mmc_host;
+	unsigned int sdbusy_irq;
+	u8 hw_version;
+	void __iomem *base_iomem;
+	struct mutex mutex_lock;
+	spinlock_t bh_lock;
+	struct ts7800v1_sdmmc_slot sd_slot[NUM_SD_SLOTS];
+};
+
+static inline void add_1readb_delay(struct ts7800v1_sdmmc_host *ts_sdmmc_host)
+{
+	readb(ts_sdmmc_host->base_iomem);
+}
+
+static inline void add_2readb_delay(struct ts7800v1_sdmmc_host *ts_sdmmc_host)
+{
+	readb(ts_sdmmc_host->base_iomem);
+	readb(ts_sdmmc_host->base_iomem);
+}
+
+static inline void
+add_2clk_cycles_slow(struct ts7800v1_sdmmc_host *ts_sdmmc_host)
+{
+	u8 i;
+
+	for (i = 0; i < 2; ++i) {
+		/* toggle low slow clk-line */
+		writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+		       ts_sdmmc_host->base_iomem);
+
+		add_2readb_delay(ts_sdmmc_host);
+
+		/* toggle high slow clk-line */
+		writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+		       ts_sdmmc_host->base_iomem);
+
+		add_2readb_delay(ts_sdmmc_host);
+	}
+}
+
+static inline void
+add_2clk_cycles_high(struct ts7800v1_sdmmc_host *ts_sdmmc_host)
+{
+	u8 i;
+
+	for (i = 0; i < 2; ++i) {
+		/* toggle low slow clk-line */
+		writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+		       ts_sdmmc_host->base_iomem);
+
+		add_1readb_delay(ts_sdmmc_host);
+
+		/* toggle high slow clk-line */
+		writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+		       ts_sdmmc_host->base_iomem);
+
+		add_1readb_delay(ts_sdmmc_host);
+	}
+}
+
+static inline u16 ts7800v1_sdmmc_ucrc16(u16 crc_in, u8 incr)
+{
+	u16 xor = crc_in >> 15;
+	u16 out = crc_in << 1;
+
+	if (incr)
+		out++;
+
+	if (xor)
+		out ^= CRC_POLY;
+
+	return out;
+}
+
+static u16 ts7800v1_sdmmc_crc16(const u8 *data, u16 size)
+{
+	u16 crc;
+	u8 i;
+
+	for (crc = 0; size > 0; size--, data++)
+		for (i = 0x80; i; i >>= 1)
+			crc = ts7800v1_sdmmc_ucrc16(crc, *data & i);
+
+	for (i = 0; i < 16; i++)
+		crc = ts7800v1_sdmmc_ucrc16(crc, 0);
+
+	return crc;
+}
+
+static inline u8 ts7800v1_sdmmc_crc7(u8 crc, const u8 *data, size_t len,
+				     enum bit_endianness crc7en)
+{
+	size_t i, lenbe = len - 1;
+	u8 ibit, c;
+
+	if (crc7en == LE_ENDIAN) {
+		for (i = 0; i < len; i++) {
+			c = data[i];
+			for (ibit = 0; ibit < 8; ibit++) {
+				crc <<= 1;
+				if ((c ^ crc) & 0x80)
+					crc ^= 0x09;
+
+				c <<= 1;
+			}
+
+			crc &= 0x7F;
+		}
+	} else {
+		for (i = 0; i < len; i++) {
+			c = data[lenbe - i];
+			for (ibit = 0; ibit < 8; ibit++) {
+				crc <<= 1;
+				if ((c ^ crc) & 0x80)
+					crc ^= 0x09;
+
+				c <<= 1;
+			}
+
+			crc &= 0x7F;
+		}
+	}
+
+	return crc;
+}
+
+static inline void lowspeed_mkcommand(u8 cmdindx, u32 arg, u8 *retcmd)
+{
+	retcmd[0] = BIT(6) | cmdindx;
+	retcmd[1] = arg >> 24;
+	retcmd[2] = arg >> 16;
+	retcmd[3] = arg >> 8;
+	retcmd[4] = arg;
+	retcmd[5] =
+		(0x1 | (ts7800v1_sdmmc_crc7(0, retcmd, 0x5, LE_ENDIAN) << 1));
+}
+
+/*
+ * return 0 : 8 bit TS-SDCORE v1
+ * return 1 : 8 bit 4x8 TS-SDCORE v2
+ * return 2 : 32 bit 4x32 TS-SDCORE v2 (TS-7800v1 hw_version 0x2)
+ * return 3 : 16 bit 4x32 TS-SDCORE v2
+ * return 4 : 8 bit 4x32 TS-SDCORE v2
+ */
+static int ts7800v1_sdmmc_hw_version(struct ts7800v1_sdmmc_host *ts_sdmmc_host)
+{
+	u8 a, b;
+	u32 c;
+	u16 d;
+	int ret;
+
+	/*
+	 * Bit-30 On TS-SDCORE 2, this bit is stuck 0. On TS-SDCORE 1, this bit is read/write.
+	 * This can be used for detecting which hardware core is present.
+	 */
+	a = readb(ts_sdmmc_host->base_iomem + 0x3);
+
+	writeb((a | BIT(6)), ts_sdmmc_host->base_iomem + 0x3);
+
+	b = readb(ts_sdmmc_host->base_iomem + 0x3);
+
+	/* restore */
+	writeb(a, ts_sdmmc_host->base_iomem + 0x3);
+
+	if ((a & BIT(6)) ^ (b & BIT(6))) {
+		ret = 0;
+		goto print_out;
+	} else if (a & BIT(6)) {
+		ret = 1;
+		goto print_out;
+	}
+
+	c = readl(ts_sdmmc_host->base_iomem + SDCORE2_SDBUS_REG);
+	d = readw(ts_sdmmc_host->base_iomem + SDCORE2_SDBUS_REG);
+
+	if ((c & BIT(6)) && (d & BIT(6))) {
+		ret = 2;
+		goto print_out;
+	}
+
+	a = readb(ts_sdmmc_host->base_iomem + SDCORE2_SDBUS_REG);
+	if (a & BIT(6)) {
+		ret = 3;
+		goto print_out;
+	} else {
+		ret = 4;
+		goto print_out;
+	}
+
+print_out:
+	dev_info(mmc_dev(ts_sdmmc_host->mmc_host), "Detected SDCoreV%d\n", ret);
+	return ret;
+}
+
+static inline u8 get_clksel(struct ts7800v1_sdmmc_host *ts_sdmmc_host)
+{
+	return readb(ts_sdmmc_host->base_iomem + 0x2) & GENMASK(2, 0);
+}
+
+static inline void set_clksel(struct ts7800v1_sdmmc_host *ts_sdmmc_host,
+			      u8 slot)
+{
+	u8 a;
+
+	spin_lock_bh(&ts_sdmmc_host->bh_lock);
+	a = get_clksel(ts_sdmmc_host);
+	a &= ~(GENMASK(2, 0));
+	a |= (slot & GENMASK(2, 0));
+	writeb(a, ts_sdmmc_host->base_iomem + 0x2);
+	spin_unlock_bh(&ts_sdmmc_host->bh_lock);
+}
+
+static u32 set_clkspd(struct ts7800v1_sdmmc_host *ts_sdmmc_host,
+		      struct ts7800v1_sdmmc_slot *pslot, bool high_speed)
+{
+	u8 a;
+
+	/* since this is a single host multi slot/card state machine */
+	/* always change clock frequency for current slot */
+	spin_lock_bh(&ts_sdmmc_host->bh_lock);
+	pslot->sd_state &= ~(SD_LOWSPEED);
+	a = readb(ts_sdmmc_host->base_iomem + 0x1);
+	a &= ~(BIT(5));
+	if (high_speed) {
+		a |= BIT(5);
+		writeb(a, ts_sdmmc_host->base_iomem + 0x1);
+	} else {
+		writeb(a, ts_sdmmc_host->base_iomem + 0x1);
+		pslot->sd_state |= SD_LOWSPEED;
+	}
+	spin_unlock_bh(&ts_sdmmc_host->bh_lock);
+
+	return (pslot->sd_state & SD_LOWSPEED);
+}
+
+static u32 set_mlt_rdwr(struct ts7800v1_sdmmc_host *ts_sdmmc_host,
+			struct ts7800v1_sdmmc_slot *pslot, bool multi_word)
+{
+	u8 a;
+
+	/* since this is a single host multi slot/card state machine */
+	/* always change read/write type for current slot */
+	spin_lock_bh(&ts_sdmmc_host->bh_lock);
+	pslot->sd_state &= ~SD_MULTI_BLK;
+	a = readb(ts_sdmmc_host->base_iomem + 0x1);
+	a &= ~(GENMASK(4, 3));
+	if (multi_word) {
+		a |= GENMASK(4, 3);
+		writeb(a, ts_sdmmc_host->base_iomem + 0x1);
+		pslot->sd_state |= SD_MULTI_BLK;
+	} else {
+		writeb(a, ts_sdmmc_host->base_iomem + 0x1);
+	}
+	spin_unlock_bh(&ts_sdmmc_host->bh_lock);
+
+	return (a & GENMASK(4, 3));
+}
+
+static int activate_slot_clk(struct ts7800v1_sdmmc_host *ts_sdmmc_host, u8 slot)
+{
+	struct ts7800v1_sdmmc_slot *pslot = &ts_sdmmc_host->sd_slot[slot];
+	bool high_speed, multi_rw;
+
+	/* Are we already selected? */
+	if ((pslot->sd_state & (SD_SELECTED | SD_RESET)) == SD_SELECTED)
+		return 0;
+
+	/* Change clock routing */
+	set_clksel(ts_sdmmc_host, slot);
+
+	/* Change clock freq/multi-blk read/write */
+	multi_rw = (pslot->sd_state & SD_MULTI_BLK) ? true : false;
+	set_mlt_rdwr(ts_sdmmc_host, pslot, multi_rw);
+	high_speed = (pslot->sd_state & SD_LOWSPEED) ? false : true;
+	set_clkspd(ts_sdmmc_host, pslot, high_speed);
+
+	/* mark us as selected */
+	pslot->sd_state |= SD_SELECTED;
+
+	return 0;
+}
+
+static int card_reset(struct ts7800v1_sdmmc_host *ts_sdmmc_host, u8 slot)
+{
+	struct ts7800v1_sdmmc_slot *pslot = &ts_sdmmc_host->sd_slot[slot];
+	u16 i;
+	u8 a;
+
+	/* reset sdmmc state bits */
+	pslot->sd_state = 0x0;
+
+	/* start with low speed */
+	pslot->sd_state |= SD_LOWSPEED;
+
+	/* select which LUN gets the clocks */
+	activate_slot_clk(ts_sdmmc_host, slot);
+
+	/* disable clk, cmd and dat[0-3] => power off SD card */
+	writeb(0x0, ts_sdmmc_host->base_iomem);
+	msleep(100);
+
+	writeb(CMDENB_DATTRI_SDCLKH_SDCMDH_SDDATH, ts_sdmmc_host->base_iomem);
+	usleep_range(200, 300);
+	writeb(CMDENB_DATTRI_SDCLKL_SDCMDL_SDDATH, ts_sdmmc_host->base_iomem);
+	msleep(100);
+
+	// generate free 750-clocks cycles for the cards
+	for (i = 0; i < 750; ++i) {
+		writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+		       ts_sdmmc_host->base_iomem);
+
+		/* 2-delay reads */
+		add_2readb_delay(ts_sdmmc_host);
+
+		/* toggle low slow clk-line */
+		writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+		       ts_sdmmc_host->base_iomem);
+
+		/* 2-delay reads */
+		add_2readb_delay(ts_sdmmc_host);
+	}
+
+	/* reset any timeout/crc conditions */
+	a = readb(ts_sdmmc_host->base_iomem + 0x1);
+
+	/* set card-detect and write-protect */
+	pslot->sd_detect = (a & BIT(0)) ? true : false;
+	pslot->sd_wprot = (a & BIT(1)) ? true : false;
+
+	pslot->sd_state &= ~(SD_RESET);
+
+	return 0;
+}
+
+/* set/clear bit location in any contiguous memory buffer/fifo
+ * this function assumes pfifo content are cleared prior to calling it
+ */
+static inline void set_fifo_bit(u8 *pfifo, uint32_t cycle, u8 value)
+{
+	u32 byte_indx = cycle >> 3;
+	u8 bit_indx = cycle - (byte_indx << 3);
+
+	if (value)
+		pfifo[byte_indx] |= BIT(bit_indx);
+	else
+		pfifo[byte_indx] &= ~BIT(bit_indx);
+}
+
+/* reversed big endian set/clear bit location in any contiguous memory buffer/fifo
+ * this function assumes pfifo content are cleared prior to calling it
+ */
+static inline void set_fifo_bit_reversed(u8 *pfifo, uint32_t cycle, u8 value)
+{
+	u32 byte_indx = cycle >> 3;
+	u8 bit_indx = 7 - (cycle - (byte_indx << 3));
+
+	if (value)
+		pfifo[byte_indx] |= BIT(bit_indx);
+	else
+		pfifo[byte_indx] &= ~BIT(bit_indx);
+}
+
+static inline void set_fifo_nibble_reversed(u8 *pfifo, uint32_t nibble_cycle,
+					    u8 value)
+{
+	u32 byte_indx = nibble_cycle >> 1;
+	u8 nibble_indx = (nibble_cycle - (byte_indx << 1));
+
+	if (nibble_indx)
+		pfifo[byte_indx] |= (value & GENMASK(3, 0));
+	else
+		pfifo[byte_indx] |= ((value & GENMASK(3, 0)) << 0x4);
+}
+
+/* bitbang read SD_CMD/SD_DAT (high speed) */
+static inline void
+read_sd_cmd_sd_dat_highspeed(struct ts7800v1_sdmmc_host *ts_sdmmc_host,
+			     u8 *sdcmd_buffer, u8 *sddat_buffer,
+			     u32 sdcmd_cycles, u32 sddat_nibble_cycles, u8 slot)
+{
+	struct ts7800v1_sdmmc_slot *pslot = &ts_sdmmc_host->sd_slot[slot];
+	bool dat_started = false;
+	u32 i, sdcmd_msb_indx = sdcmd_cycles - 1;
+	u8 x;
+
+	/* set cmd start bit */
+	if (sdcmd_buffer != NULL)
+		set_fifo_bit(sdcmd_buffer, sdcmd_msb_indx, 0x0);
+
+	/* read/sample sdcmd/sddat0 bits */
+	for (i = 1; i < sdcmd_cycles; i++) {
+		/* toggle high slow clk-line */
+		writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+		       ts_sdmmc_host->base_iomem);
+
+		add_1readb_delay(ts_sdmmc_host);
+
+		/* read/sample */
+		x = readb(ts_sdmmc_host->base_iomem);
+
+		/* toggle low slow clk-line */
+		writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+		       ts_sdmmc_host->base_iomem);
+
+		/* set cmd bit */
+		if (sdcmd_buffer != NULL) {
+			set_fifo_bit(sdcmd_buffer, sdcmd_msb_indx - i,
+				     ((x & BIT(0x4)) >> 0x4));
+		}
+
+		/* set dat0-dat3 bits */
+		if (sddat_buffer != NULL) {
+			if (dat_started) {
+				if (pslot->blk_buf_nibble_indx <
+				    sddat_nibble_cycles) {
+					set_fifo_nibble_reversed(
+						sddat_buffer,
+						pslot->blk_buf_nibble_indx,
+						(x & GENMASK(3, 0)));
+					pslot->blk_buf_nibble_indx++;
+				}
+
+			} else {
+				/* ignore start bit */
+				if ((x & GENMASK(3, 0)) == 0x0)
+					dat_started = true;
+			}
+		}
+	}
+
+	/* continue reading remaining dat0-dat3 until next block boundary */
+	if (sddat_buffer != NULL && dat_started) {
+		while (pslot->blk_buf_nibble_indx < sddat_nibble_cycles &&
+		       pslot->blk_buf_nibble_indx < MAX_BLK_SIZE_NIBBLES) {
+			/* toggle high slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+
+			add_1readb_delay(ts_sdmmc_host);
+
+			/* read/sample */
+			x = readb(ts_sdmmc_host->base_iomem);
+
+			/* toggle low slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+
+			set_fifo_nibble_reversed(sddat_buffer,
+						 pslot->blk_buf_nibble_indx,
+						 (x & GENMASK(3, 0)));
+			pslot->blk_buf_nibble_indx++;
+		}
+	}
+
+	if (pslot->blk_buf_nibble_indx == MAX_BLK_SIZE_NIBBLES) {
+		/* read/consume sd_dat CRC16 */
+		for (i = 0; i < 0x20; i++) {
+			/* toggle high slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+
+			add_1readb_delay(ts_sdmmc_host);
+
+			/* read/sample */
+			x = readb(ts_sdmmc_host->base_iomem);
+
+			/* toggle low slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+
+			add_1readb_delay(ts_sdmmc_host);
+		}
+	}
+}
+
+/* bitbang read SD_CMD/SD_DAT (low speed) */
+static inline void
+read_sdcmd_sddat0_lowspeed(struct ts7800v1_sdmmc_host *ts_sdmmc_host,
+			   u8 *sdcmd_buffer, u8 *sddat0_buffer,
+			   u32 sdcmd_cycles, u32 sddat0_cycles, u8 slot)
+{
+	struct ts7800v1_sdmmc_slot *pslot = &ts_sdmmc_host->sd_slot[slot];
+	bool dat0_started = false;
+	u32 i, sdcmd_msb_indx = sdcmd_cycles - 1;
+	u8 x;
+
+	/* set cmd start bit */
+	if (sdcmd_buffer != NULL)
+		set_fifo_bit(sdcmd_buffer, sdcmd_msb_indx, 0x0);
+
+	/* read/sample sdcmd/sddat0 bits */
+	for (i = 1; i < sdcmd_cycles; i++) {
+		/* toggle high slow clk-line */
+		writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+		       ts_sdmmc_host->base_iomem);
+
+		/* 2-delay read */
+		add_2readb_delay(ts_sdmmc_host);
+
+		/* read/sample */
+		x = readb(ts_sdmmc_host->base_iomem);
+
+		/* toggle low slow clk-line */
+		writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+		       ts_sdmmc_host->base_iomem);
+
+		/* set cmd bit */
+		if (sdcmd_buffer != NULL) {
+			set_fifo_bit(sdcmd_buffer, sdcmd_msb_indx - i,
+				     ((x & BIT(0x4)) >> 0x4));
+		}
+
+		/* set dat0 bit */
+		if (sddat0_buffer != NULL) {
+			if (dat0_started) {
+				if (pslot->blk_buf_cycle_indx < sddat0_cycles) {
+					set_fifo_bit_reversed(
+						sddat0_buffer,
+						pslot->blk_buf_cycle_indx,
+						(x & BIT(0x0)));
+					pslot->blk_buf_cycle_indx++;
+				}
+
+			} else {
+				/* ignore start bit */
+				if ((x & GENMASK(3, 0)) == 0xe)
+					dat0_started = true;
+			}
+		}
+
+		/* 1-delay read */
+		add_1readb_delay(ts_sdmmc_host);
+	}
+}
+
+/* read/continue previously started bit read operation */
+static inline void
+read_continue_sddat0_lowspeed(struct ts7800v1_sdmmc_host *ts_sdmmc_host,
+			      u8 *sddat0_buffer, u32 start_cycle,
+			      u32 sddat0_cycles, u8 slot, bool reverse)
+{
+	u32 i, sddat0_msb_indx = sddat0_cycles - 1;
+
+	/* reverse bit/byte order rw DMA buffer */
+	if (reverse) {
+		/* read/sample sdcmd/sddat0 bits */
+		for (i = start_cycle; i < sddat0_cycles; i++) {
+			u8 x;
+			/* toggle high slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+
+			/* 1-delay read */
+			add_1readb_delay(ts_sdmmc_host);
+
+			/* read/sample */
+			x = readb(ts_sdmmc_host->base_iomem);
+
+			/* toggle low slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+
+			/* set dat0 bit */
+			if (sddat0_buffer != NULL) {
+				set_fifo_bit_reversed(sddat0_buffer, i,
+						      (x & BIT(0x0)));
+			}
+
+			/* 1-delay read */
+			add_1readb_delay(ts_sdmmc_host);
+		}
+	} else {
+		/* read/sample sdcmd/sddat0 bits */
+		for (i = start_cycle; i < sddat0_cycles; i++) {
+			u8 x;
+			/* toggle high slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+
+			/* 1-delay read */
+			add_2readb_delay(ts_sdmmc_host);
+
+			/* read/sample */
+			x = readb(ts_sdmmc_host->base_iomem);
+
+			/* toggle low slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+
+			/* set dat0 bit */
+			if (sddat0_buffer != NULL) {
+				set_fifo_bit(sddat0_buffer, sddat0_msb_indx - i,
+					     (x & BIT(0x0)));
+			}
+
+			/* 1-delay read */
+			add_1readb_delay(ts_sdmmc_host);
+		}
+	}
+}
+
+static inline void sd_cmd_write(struct ts7800v1_sdmmc_host *ts_sdmmc_host,
+				u8 offset, u32 opcode, u32 arg)
+{
+	writeb(CMDENB_DATTRI_SDCLKH_SDCMDH_SDDATH, ts_sdmmc_host->base_iomem);
+
+	if (offset == 0x20) {
+		u32 x = 0x0;
+
+		x = ((BIT(6) | opcode) & 0xff) << 24;
+		x |= ((arg >> 24) & 0xff) << 16;
+		x |= ((arg >> 16) & 0xff) << 8;
+		x |= ((arg >> 8) & 0xff);
+
+		writel(x, ts_sdmmc_host->base_iomem + SDCORE2_SDCMD_REG);
+
+		/* send remaining 1-byte of arg */
+		/* NOTE: CRC7 + STOP bit are added automatically */
+		x = (arg & 0xff) << 24;
+		writel(x, ts_sdmmc_host->base_iomem + SDCORE2_SDCMD_REG);
+
+	} else if (offset == 0x10) {
+		u16 x = 0x0;
+
+		x = (opcode & 0xff) << 8;
+		x |= ((arg >> 24) & 0xff);
+		writew(x, ts_sdmmc_host->base_iomem + SDCORE2_SDCMD_REG);
+
+		x = ((arg >> 16) & 0xff) << 8;
+		x |= ((arg >> 8) & 0xff);
+		writew(x, ts_sdmmc_host->base_iomem + SDCORE2_SDCMD_REG);
+
+		x = (arg & 0xff) << 8;
+		writew(x, ts_sdmmc_host->base_iomem + SDCORE2_SDCMD_REG);
+	} else {
+		u8 x = 0x0;
+
+		x = (opcode & 0xff);
+		writeb(x, ts_sdmmc_host->base_iomem + SDCORE2_SDCMD_REG);
+		x = ((arg >> 24) & 0xff);
+		writeb(x, ts_sdmmc_host->base_iomem + SDCORE2_SDCMD_REG);
+		x = ((arg >> 16) & 0xff);
+		writeb(x, ts_sdmmc_host->base_iomem + SDCORE2_SDCMD_REG);
+		x = ((arg >> 8) & 0xff);
+		writeb(x, ts_sdmmc_host->base_iomem + SDCORE2_SDCMD_REG);
+		x = ((arg)&0xff);
+		writeb(x, ts_sdmmc_host->base_iomem + SDCORE2_SDCMD_REG);
+	}
+
+	writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH, ts_sdmmc_host->base_iomem);
+}
+
+static inline void sd_cmd_read(struct ts7800v1_sdmmc_host *ts_sdmmc_host,
+			       struct ts7800v1_sdmmc_slot *pslot,
+			       u8 resp_len_bytes, size_t sz)
+{
+	u8 i, j;
+	u8 x8, resp_last_byte = resp_len_bytes - 1;
+	u16 x16;
+	u32 x32;
+
+	for (i = 0; i < resp_len_bytes; i++) {
+		if (!(i % sz)) {
+			u8 shift = (sz - 1) << 3;
+
+			if (sz == sizeof(x8)) {
+				x8 = readb(ts_sdmmc_host->base_iomem +
+					   SDCORE2_SDCMD_REG);
+				for (j = i; j < i + sz; ++j) {
+					if (j < resp_len_bytes) {
+						pslot->response[resp_last_byte -
+								j] =
+							(x8 >> shift) & 0xff;
+						shift -= 8;
+					}
+				}
+			} else if (sz == sizeof(x16)) {
+				x16 = readw(ts_sdmmc_host->base_iomem +
+					    SDCORE2_SDCMD_REG);
+				for (j = i; j < i + sz; ++j) {
+					if (j < resp_len_bytes) {
+						pslot->response[resp_last_byte -
+								j] =
+							(x16 >> shift) & 0xff;
+						shift -= 8;
+					}
+				}
+			} else {
+				x32 = (readl(ts_sdmmc_host->base_iomem +
+					     SDCORE2_SDCMD_REG));
+
+				for (j = i; j < i + sz; ++j) {
+					if (j < resp_len_bytes) {
+						pslot->response[resp_last_byte -
+								j] =
+							(x32 >> shift) & 0xff;
+
+						shift -= 8;
+					}
+				}
+			}
+		}
+	}
+}
+
+/* This function should be called after holding spin lock */
+static inline void send_serialize_cmd(struct ts7800v1_sdmmc_host *ts_sdmmc_host,
+				      struct ts7800v1_sdmmc_slot *pslot,
+				      u32 opcode, u32 arg)
+{
+	add_2clk_cycles_high(ts_sdmmc_host);
+	sd_cmd_write(ts_sdmmc_host, 0x20, opcode, arg);
+}
+
+/* This function should be called after holding mutex lock */
+static inline void wait_for_response(struct ts7800v1_sdmmc_host *ts_sdmmc_host,
+				     struct ts7800v1_sdmmc_slot *pslot,
+				     bool low_speed)
+{
+	pslot->cmd_timeout = 0x0;
+	for (pslot->cmd_timeout = 0;
+	     pslot->cmd_timeout < MAX_RESP_TIMEOUT_MICROSECS;
+	     ++pslot->cmd_timeout) {
+		u8 x;
+
+		/* toggle high slow clk-line */
+		writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+		       ts_sdmmc_host->base_iomem);
+
+		/* add 1-delay */
+		if (low_speed)
+			add_1readb_delay(ts_sdmmc_host);
+
+		/* read/sample sd_cmd state */
+		x = readb(ts_sdmmc_host->base_iomem);
+
+		/* toggle low slow clk-line */
+		writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+		       ts_sdmmc_host->base_iomem);
+
+		/* add 2-delay */
+		if (low_speed)
+			add_2readb_delay(ts_sdmmc_host);
+
+		if ((x & 0x10) == 0x0)
+			break;
+
+		usleep_range(1, 2);
+	}
+}
+
+static inline void
+send_sddat_start_nibble(struct ts7800v1_sdmmc_host *ts_sdmmc_host)
+{
+	add_2clk_cycles_high(ts_sdmmc_host);
+
+	/* write start bit */
+	writeb(CMDENB_DATENB_SDCLKH_SDCMDH_SDDAT03L, ts_sdmmc_host->base_iomem);
+	/* 1-delay reads */
+	add_1readb_delay(ts_sdmmc_host);
+
+	writeb(CMDENB_DATENB_SDCLKL_SDCMDH_SDDAT03L, ts_sdmmc_host->base_iomem);
+	/* 1-delay reads */
+	add_1readb_delay(ts_sdmmc_host);
+}
+
+static int send_cmd_recv_resp_simple(struct ts7800v1_sdmmc_host *ts_sdmmc_host,
+				     u8 slot, u32 cmd_opcode, u32 cmd_arg,
+				     unsigned int cmd_flags, int *cmd_error,
+				     u32 *cmd_resp)
+{
+	struct ts7800v1_sdmmc_slot *pslot = &ts_sdmmc_host->sd_slot[slot];
+	u8 stat, resp_len_bytes, sent_resp_crc7, calc_resp_crc7;
+	bool low_speed;
+	int ret = 0x0, i;
+
+	/* initial ok state, following are not pre-set by default */
+	pslot->sg_count = -1;
+	pslot->cmd_timeout = 0x0;
+	pslot->blk_buf_cycle_indx = pslot->blk_buf_nibble_indx = 0x0;
+
+	/* low speed = sample on cmd-line, dat0-line */
+	low_speed = (pslot->sd_state & SD_LOWSPEED) ? true : false;
+
+	activate_slot_clk(ts_sdmmc_host, slot);
+
+	/* serialize command */
+	spin_lock_bh(&ts_sdmmc_host->bh_lock);
+	send_serialize_cmd(ts_sdmmc_host, pslot, cmd_opcode, cmd_arg);
+	spin_unlock_bh(&ts_sdmmc_host->bh_lock);
+
+	switch ((cmd_flags & (MMC_RSP_PRESENT | MMC_RSP_136 | MMC_RSP_CRC |
+			      MMC_RSP_BUSY | MMC_RSP_OPCODE))) {
+	case MMC_RSP_NONE:
+		resp_len_bytes = 0x0;
+		goto done;
+	case MMC_RSP_R1:
+	case MMC_RSP_R1B:
+	case MMC_RSP_R3:
+		resp_len_bytes = NORM_RESP_BYTES;
+		break;
+	case MMC_RSP_R2:
+		resp_len_bytes = LONG_RESP_BYTES;
+		break;
+	default:
+		dev_warn(mmc_dev(ts_sdmmc_host->mmc_host),
+			 "%s|%d - Warning, Invalid response type\n", __func__,
+			 __LINE__);
+		*cmd_error = ret = -EINVAL;
+		goto done;
+	}
+
+	if (cmd_flags & MMC_RSP_PRESENT) {
+		/* wait for response, i.e. start bit=0 on slow sd_cmd */
+		mutex_lock(&ts_sdmmc_host->mutex_lock);
+		wait_for_response(ts_sdmmc_host, pslot, low_speed);
+		mutex_unlock(&ts_sdmmc_host->mutex_lock);
+
+		if (pslot->cmd_timeout >= MAX_RESP_TIMEOUT_MICROSECS) {
+			*cmd_error = ret = -ETIMEDOUT;
+			goto done;
+		}
+
+		/* serialize/consume cmd response */
+		spin_lock_bh(&ts_sdmmc_host->bh_lock);
+		if (!low_speed && (pslot->sd_state & DATSSP_4BIT)) {
+			sd_cmd_read(ts_sdmmc_host, pslot, resp_len_bytes,
+				    sizeof(u32));
+
+		} else {
+			/* NOTE: response includes crc7 and stop bit */
+			read_sdcmd_sddat0_lowspeed(
+				ts_sdmmc_host, pslot->response, NULL,
+				(resp_len_bytes * BYTE_CLK_CYCLES), 0x0, slot);
+		}
+
+		spin_unlock_bh(&ts_sdmmc_host->bh_lock);
+
+		/* process data outside of spin locks */
+		if (cmd_flags & MMC_RSP_136) {
+			/* R2, copy 12-bytes/3-double-words of argument including internal CRC7 */
+			memcpy(&cmd_resp[0], &pslot->response[12], 0x4);
+			memcpy(&cmd_resp[1], &pslot->response[8], 0x4);
+			memcpy(&cmd_resp[2], &pslot->response[4], 0x4);
+			memcpy(&cmd_resp[3], &pslot->response[0], 0x4);
+		} else {
+			/* R1, R1b, R3, R4, R5, R6 4-bytes arguments only*/
+			memcpy(cmd_resp, &pslot->response[1], 0x4);
+		}
+
+		/* resp crc7 check */
+		switch ((cmd_flags &
+			 (MMC_RSP_PRESENT | MMC_RSP_136 | MMC_RSP_CRC |
+			  MMC_RSP_BUSY | MMC_RSP_OPCODE))) {
+		case MMC_RSP_R1:
+		case MMC_RSP_R1B:
+			sent_resp_crc7 = pslot->response[0] >> 1;
+			calc_resp_crc7 =
+				ts7800v1_sdmmc_crc7(0, &pslot->response[1],
+						    resp_len_bytes - 1,
+						    BE_ENDIAN);
+
+			if (sent_resp_crc7 != calc_resp_crc7) {
+				*cmd_error = ret = -EILSEQ;
+				goto done;
+			}
+			break;
+		default:
+			/* no crc7 check*/
+			break;
+		}
+	}
+
+	/* serialize/consume card's busy response if any */
+	if (cmd_flags & MMC_RSP_BUSY) {
+		stat = 0x0;
+		/* reset cmd time-out */
+		pslot->cmd_timeout = 0x0;
+
+		mutex_lock(&ts_sdmmc_host->mutex_lock);
+
+		while ((stat & 0x7) != 0x7) {
+			if (pslot->cmd_timeout++ >=
+			    MAX_BUSY_TIMEOUT_MICROSECS) {
+				*cmd_error = ret = -ETIMEDOUT;
+				goto done;
+			}
+
+			/* toggle high slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+
+			/* 1-delay reads */
+			if (low_speed)
+				add_1readb_delay(ts_sdmmc_host);
+
+			stat = stat << 1;
+			stat |= readb(ts_sdmmc_host->base_iomem) & 0x1;
+
+			/* toggle low slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+
+			/* 1-delay reads */
+			if (low_speed)
+				add_1readb_delay(ts_sdmmc_host);
+
+			usleep_range(15, 25);
+		}
+
+		mutex_unlock(&ts_sdmmc_host->mutex_lock);
+	}
+
+	*cmd_error = ret = 0x0;
+
+done:
+
+	// 8 clocks before stopping
+	spin_lock_bh(&ts_sdmmc_host->bh_lock);
+
+	if (low_speed)
+		for (i = 0; i < 8; i++) {
+			/* toggle hi slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+			/* 2-delay reads */
+			add_2readb_delay(ts_sdmmc_host);
+
+			/* toggle low slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+			/* 2-delay reads */
+			add_2readb_delay(ts_sdmmc_host);
+		}
+	else {
+		/* send 8-bits on fast clk-line */
+		writeb(0xff, ts_sdmmc_host->base_iomem + SDCORE2_SDCMD_REG);
+	}
+
+	spin_unlock_bh(&ts_sdmmc_host->bh_lock);
+
+	return ret;
+}
+
+static int send_cmd_recv_resp_read_blk(
+	struct ts7800v1_sdmmc_host *ts_sdmmc_host, u8 slot, u32 cmd_opcode,
+	u32 cmd_arg, unsigned int cmd_flags, int *cmd_error, u32 *cmd_resp,
+	unsigned int data_blksz, u32 data_offset, int *data_error)
+{
+	struct ts7800v1_sdmmc_slot *pslot = &ts_sdmmc_host->sd_slot[slot];
+	u32 blksz_dwords = data_blksz >> 2;
+	u16 sent_dat0_crc16, calc_crc16;
+	u8 stat, resp_len_bytes, sent_resp_crc7, calc_resp_crc7,
+		*dat0_sent_crc16_buf = NULL;
+	bool low_speed, data_read_done;
+	int ret = 0x0, i;
+
+	/* initial ok state, following are not pre-set by default */
+	pslot->sg_count = -1;
+	pslot->cmd_timeout = 0x0;
+	pslot->blk_buf_cycle_indx = pslot->blk_buf_nibble_indx = 0x0;
+	data_read_done = false;
+	/* clear crc_err/timeout */
+	readb(ts_sdmmc_host->base_iomem + 0x1);
+
+	/* low speed = sample on cmd-line, dat0-line */
+	low_speed = (pslot->sd_state & SD_LOWSPEED) ? true : false;
+
+	activate_slot_clk(ts_sdmmc_host, slot);
+
+	if (IS_ERR_OR_NULL(pslot->rw_dma_buf)) {
+		dev_warn(mmc_dev(ts_sdmmc_host->mmc_host),
+			 "%s|%d - Error, No allocated DMA read buffer %ld\n",
+			 __func__, __LINE__, PTR_ERR(pslot->rw_dma_buf));
+		*data_error = ret = -ENOMEM;
+		goto done;
+	}
+
+	dat0_sent_crc16_buf = kzalloc(sizeof(u16), GFP_KERNEL);
+	if (IS_ERR_OR_NULL(dat0_sent_crc16_buf)) {
+		dev_warn(
+			mmc_dev(ts_sdmmc_host->mmc_host),
+			"%s|%d - Error, kzalloc 'dat0_sent_crc16_buf' of size %u failed with %ld\n",
+			__func__, __LINE__, sizeof(u16),
+			PTR_ERR(dat0_sent_crc16_buf));
+		*data_error = ret = -ENOMEM;
+		goto done;
+	}
+
+	if (!(cmd_flags & MMC_RSP_PRESENT)) {
+		dev_warn(
+			mmc_dev(ts_sdmmc_host->mmc_host),
+			"%s|%d - Error, read block command flgas must have a response\n",
+			__func__, __LINE__);
+		*cmd_error = ret = -EINVAL;
+	} else {
+		switch ((cmd_flags &
+			 (MMC_RSP_PRESENT | MMC_RSP_136 | MMC_RSP_CRC |
+			  MMC_RSP_BUSY | MMC_RSP_OPCODE))) {
+		case MMC_RSP_NONE:
+			resp_len_bytes = 0x0;
+			goto done;
+		case MMC_RSP_R1:
+		case MMC_RSP_R1B:
+		case MMC_RSP_R3:
+			resp_len_bytes = NORM_RESP_BYTES;
+			break;
+		case MMC_RSP_R2:
+			resp_len_bytes = LONG_RESP_BYTES;
+			break;
+		default:
+			dev_warn(mmc_dev(ts_sdmmc_host->mmc_host),
+				 "%s|%d - Warning, Invalid response type\n",
+				 __func__, __LINE__);
+			*cmd_error = ret = -EINVAL;
+			goto done;
+		}
+	}
+
+	/* serialize command */
+	spin_lock_bh(&ts_sdmmc_host->bh_lock);
+	send_serialize_cmd(ts_sdmmc_host, pslot, cmd_opcode, cmd_arg);
+	spin_unlock_bh(&ts_sdmmc_host->bh_lock);
+
+	/* wait for response, i.e. start bit=0 on slow sd_cmd */
+	mutex_lock(&ts_sdmmc_host->mutex_lock);
+	wait_for_response(ts_sdmmc_host, pslot, low_speed);
+	mutex_unlock(&ts_sdmmc_host->mutex_lock);
+
+	if (pslot->cmd_timeout >= MAX_RESP_TIMEOUT_MICROSECS) {
+		*cmd_error = ret = -ETIMEDOUT;
+		goto done;
+	}
+
+	/* serialize/consume cmd response */
+	if (!low_speed && (pslot->sd_state & DATSSP_4BIT)) {
+		spin_lock_bh(&ts_sdmmc_host->bh_lock);
+		read_sd_cmd_sd_dat_highspeed(
+			ts_sdmmc_host, pslot->response, pslot->rw_dma_buf,
+			resp_len_bytes * BYTE_CLK_CYCLES,
+			data_blksz << DAT03_NIBBLES_PER_CLK_CYCLE, slot);
+
+		if (pslot->blk_buf_nibble_indx != 0) {
+			if (pslot->blk_buf_nibble_indx < (data_blksz << 1)) {
+				u32 blk_buf_nibble_indx_dwords;
+
+				stat = 0x0;
+
+				/* ignore One-cycle Pull-up */
+				writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+				       ts_sdmmc_host->base_iomem);
+				/* read/sample */
+				readb(ts_sdmmc_host->base_iomem);
+				/* toggle low slow clk-line */
+				writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+				       ts_sdmmc_host->base_iomem);
+				add_1readb_delay(ts_sdmmc_host);
+
+				blk_buf_nibble_indx_dwords =
+					pslot->blk_buf_nibble_indx >> 3;
+				for (i = blk_buf_nibble_indx_dwords;
+				     i < blksz_dwords; ++i) {
+					u32 x32;
+
+					if (!(i % MAX_BLK_SIZE_DWORDS)) {
+						/* check/clear crc_err/timeout */
+						stat = readb(
+							ts_sdmmc_host
+								->base_iomem +
+							0x1);
+
+						if (stat & BIT(2)) {
+							spin_unlock_bh(
+								&ts_sdmmc_host
+									 ->bh_lock);
+
+							dev_warn(
+								mmc_dev(ts_sdmmc_host
+										->mmc_host),
+								"%s|%d - Warning, dat0-3 crc16 mismatch\n",
+								__func__,
+								__LINE__);
+
+							*data_error = ret =
+								-EILSEQ;
+
+							goto done;
+						}
+
+						if (stat & BIT(6)) {
+							spin_unlock_bh(
+								&ts_sdmmc_host
+									 ->bh_lock);
+
+							dev_warn(
+								mmc_dev(ts_sdmmc_host
+										->mmc_host),
+								"%s|%d - Warning, dat0-3 time-out\n",
+								__func__,
+								__LINE__);
+
+							*data_error = ret =
+								-ETIMEDOUT;
+
+							goto done;
+						}
+					}
+
+					x32 = readl(ts_sdmmc_host->base_iomem +
+						    SDCORE2_SDDATA_REG);
+
+					memcpy(&pslot->rw_dma_buf[i << 2], &x32,
+					       sizeof(u32));
+				}
+
+				data_read_done = true;
+			} else {
+				/* already completed reading data blk */
+				data_read_done = true;
+			}
+		}
+
+		spin_unlock_bh(&ts_sdmmc_host->bh_lock);
+
+	} else {
+		spin_lock_bh(&ts_sdmmc_host->bh_lock);
+		/* NOTE: response includes crc7 and stop bit */
+		read_sdcmd_sddat0_lowspeed(ts_sdmmc_host, pslot->response,
+					   pslot->rw_dma_buf,
+					   (resp_len_bytes * BYTE_CLK_CYCLES),
+					   (data_blksz * BYTE_CLK_CYCLES),
+					   slot);
+
+		if (pslot->blk_buf_cycle_indx != 0) {
+			read_continue_sddat0_lowspeed(
+				ts_sdmmc_host, pslot->rw_dma_buf,
+				pslot->blk_buf_cycle_indx,
+				(data_blksz * BYTE_CLK_CYCLES), slot, true);
+
+			/* read dat0 CRC16 */
+			read_continue_sddat0_lowspeed(ts_sdmmc_host,
+						      dat0_sent_crc16_buf, 0x0,
+						      CRC16_CYCLES, slot,
+						      false);
+
+			sent_dat0_crc16 = (dat0_sent_crc16_buf[1] << 0x8) |
+					  dat0_sent_crc16_buf[0];
+
+			/* serialize/consume stop bit */
+			read_continue_sddat0_lowspeed(ts_sdmmc_host, NULL, 0x0,
+						      0x1, slot, false);
+
+			/* check data crc16 */
+			calc_crc16 = ts7800v1_sdmmc_crc16(pslot->rw_dma_buf,
+							  data_blksz);
+
+			if (calc_crc16 != sent_dat0_crc16) {
+				spin_unlock_bh(&ts_sdmmc_host->bh_lock);
+
+				dev_warn(
+					mmc_dev(ts_sdmmc_host->mmc_host),
+					"%s|%d - Warning, dat0 crc16 mismatch sent-crc16 %#x, calc-crc16 %#x\n",
+					__func__, __LINE__, sent_dat0_crc16,
+					calc_crc16);
+
+				*data_error = ret = -EILSEQ;
+				goto done;
+			}
+
+			data_read_done = true;
+		}
+
+		spin_unlock_bh(&ts_sdmmc_host->bh_lock);
+	}
+
+	/* serialize/consume card's busy response if any */
+	if (cmd_flags & MMC_RSP_BUSY) {
+		stat = 0x0;
+		/* reset cmd time-out */
+		pslot->cmd_timeout = 0x0;
+
+		mutex_lock(&ts_sdmmc_host->mutex_lock);
+
+		while ((stat & 0x7) != 0x7) {
+			if (pslot->cmd_timeout++ >=
+			    MAX_BUSY_TIMEOUT_MICROSECS) {
+				*cmd_error = ret = -ETIMEDOUT;
+				goto done;
+			}
+
+			/* toggle high slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+
+			/* 1-delay reads */
+			if (low_speed)
+				add_1readb_delay(ts_sdmmc_host);
+
+			stat = stat << 1;
+			stat |= readb(ts_sdmmc_host->base_iomem) & 0x1;
+
+			/* toggle low slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+
+			/* 1-delay reads */
+			if (low_speed)
+				add_1readb_delay(ts_sdmmc_host);
+
+			usleep_range(15, 25);
+		}
+
+		mutex_unlock(&ts_sdmmc_host->mutex_lock);
+	}
+
+	/* process response outside of spin locks */
+	if (cmd_flags & MMC_RSP_136) {
+		/* R2, copy 12-bytes/3-double-words of argument including internal CRC7 */
+		memcpy(&cmd_resp[0], &pslot->response[12], 0x4);
+		memcpy(&cmd_resp[1], &pslot->response[8], 0x4);
+		memcpy(&cmd_resp[2], &pslot->response[4], 0x4);
+		memcpy(&cmd_resp[3], &pslot->response[0], 0x4);
+	} else {
+		/* R1, R1b, R3, R4, R5, R6 4-bytes arguments only*/
+		memcpy(cmd_resp, &pslot->response[1], 0x4);
+	}
+
+	/* resp crc7 check */
+	switch ((cmd_flags & (MMC_RSP_PRESENT | MMC_RSP_136 | MMC_RSP_CRC |
+			      MMC_RSP_BUSY | MMC_RSP_OPCODE))) {
+	case MMC_RSP_R1:
+	case MMC_RSP_R1B:
+		sent_resp_crc7 = pslot->response[0] >> 1;
+		calc_resp_crc7 = ts7800v1_sdmmc_crc7(
+			0, &pslot->response[1], resp_len_bytes - 1, BE_ENDIAN);
+
+		if (sent_resp_crc7 != calc_resp_crc7) {
+			*cmd_error = ret = -EILSEQ;
+			goto done;
+		}
+		break;
+	default:
+		/* no crc7 check*/
+		break;
+	}
+
+	if (!data_read_done) {
+		/* wait for data lines to become low, i.e. dat[0-3]=0x0 */
+		if (!low_speed && (pslot->sd_state & DATSSP_4BIT)) {
+			mutex_lock(&ts_sdmmc_host->mutex_lock);
+			/* wait for start block token */
+			pslot->cmd_timeout = 0x0;
+			stat = 0xff;
+
+			while ((stat & 0xf) == 0xf) {
+				if (pslot->cmd_timeout++ >=
+				    MAX_BUSY_TIMEOUT_MICROSECS) {
+					/* reset time-out before going to done,
+					 * since cmd may have been successful and
+					 * only data-transfer failed
+					 */
+					pslot->cmd_timeout = 0x0;
+					*data_error = ret = -ETIMEDOUT;
+
+					mutex_unlock(
+						&ts_sdmmc_host->mutex_lock);
+					goto done;
+				}
+
+				/* toggle high slow clk-line */
+				writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+				       ts_sdmmc_host->base_iomem);
+
+				/* 1-delay reads */
+				add_1readb_delay(ts_sdmmc_host);
+
+				/* toggle low slow clk-line */
+				writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+				       ts_sdmmc_host->base_iomem);
+
+				stat = readb(ts_sdmmc_host->base_iomem);
+
+				usleep_range(15, 25);
+			}
+
+			mutex_unlock(&ts_sdmmc_host->mutex_lock);
+
+			spin_lock_bh(&ts_sdmmc_host->bh_lock);
+
+			/* ignore One-cycle Pull-up */
+			for (i = 0; i < 0x1; ++i) {
+				/* toggle high slow clk-line */
+				writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+				       ts_sdmmc_host->base_iomem);
+
+				add_1readb_delay(ts_sdmmc_host);
+
+				/* read/sample */
+				stat = (readb(ts_sdmmc_host->base_iomem) & 0xf);
+
+				/* toggle low slow clk-line */
+				writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+				       ts_sdmmc_host->base_iomem);
+
+				/* add 2-clk delay */
+				add_2readb_delay(ts_sdmmc_host);
+			}
+
+			for (i = 0; i < blksz_dwords; i++) {
+				u32 x32;
+
+				if (!(i % MAX_BLK_SIZE_DWORDS)) {
+					/* check/clear crc_err/timeout */
+					stat = readb(ts_sdmmc_host->base_iomem +
+						     0x1);
+
+					if (stat & BIT(2)) {
+						spin_unlock_bh(
+							&ts_sdmmc_host->bh_lock);
+
+						dev_warn(
+							mmc_dev(ts_sdmmc_host
+									->mmc_host),
+							"%s|%d - Warning, dat0-3 crc16 mismatch\n",
+							__func__, __LINE__);
+
+						*data_error = ret = -EILSEQ;
+
+						goto done;
+					}
+
+					if (stat & BIT(6)) {
+						spin_unlock_bh(
+							&ts_sdmmc_host->bh_lock);
+
+						dev_warn(
+							mmc_dev(ts_sdmmc_host
+									->mmc_host),
+							"%s|%d - Warning, dat0-3 time-out\n",
+							__func__, __LINE__);
+
+						*data_error = ret = -ETIMEDOUT;
+
+						goto done;
+					}
+				}
+
+				x32 = readl(ts_sdmmc_host->base_iomem +
+					    SDCORE2_SDDATA_REG);
+
+				memcpy(&pslot->rw_dma_buf[i << 2], &x32,
+				       sizeof(u32));
+			}
+
+			spin_unlock_bh(&ts_sdmmc_host->bh_lock);
+
+		} else {
+			pslot->cmd_timeout = 0x0;
+			stat = 0xff;
+
+			mutex_lock(&ts_sdmmc_host->mutex_lock);
+			while ((stat & 0xf) == 0xf) {
+				if (pslot->cmd_timeout++ >=
+				    MAX_BUSY_TIMEOUT_MICROSECS) {
+					/* reset time-out before going to done,
+					 * since cmd may have been successful and only
+					 * data-transfer failed
+					 */
+					pslot->cmd_timeout = 0x0;
+					*data_error = ret = -ETIMEDOUT;
+
+					mutex_unlock(
+						&ts_sdmmc_host->mutex_lock);
+					goto done;
+				}
+
+				/* toggle high slow clk-line */
+				writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+				       ts_sdmmc_host->base_iomem);
+
+				/* 2-delay reads */
+				add_2readb_delay(ts_sdmmc_host);
+
+				/* toggle low slow clk-line */
+				writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+				       ts_sdmmc_host->base_iomem);
+
+				add_1readb_delay(ts_sdmmc_host);
+
+				stat = readb(ts_sdmmc_host->base_iomem);
+
+				usleep_range(15, 25);
+			}
+
+			mutex_unlock(&ts_sdmmc_host->mutex_lock);
+
+			spin_lock_bh(&ts_sdmmc_host->bh_lock);
+			/* ignore start bit */
+			for (i = 0; i < 0x1; ++i) {
+				/* toggle high slow clk-line */
+				writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+				       ts_sdmmc_host->base_iomem);
+
+				add_1readb_delay(ts_sdmmc_host);
+
+				/* read/sample */
+				stat = (readb(ts_sdmmc_host->base_iomem) & 0xf);
+
+				/* toggle low slow clk-line */
+				writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+				       ts_sdmmc_host->base_iomem);
+
+				/* add 2-clk delay */
+				add_2readb_delay(ts_sdmmc_host);
+			}
+
+			/* consume data, read dat0 */
+			read_continue_sddat0_lowspeed(
+				ts_sdmmc_host, pslot->rw_dma_buf, 0x0,
+				data_blksz * BYTE_CLK_CYCLES, slot, true);
+
+			/* read dat0 CRC16 */
+			read_continue_sddat0_lowspeed(ts_sdmmc_host,
+						      dat0_sent_crc16_buf, 0x0,
+						      CRC16_CYCLES, slot,
+						      false);
+
+			sent_dat0_crc16 = (dat0_sent_crc16_buf[1] << 0x8) |
+					  dat0_sent_crc16_buf[0];
+
+			/* serialize/consume stop bit */
+			read_continue_sddat0_lowspeed(ts_sdmmc_host, NULL, 0x0,
+						      0x1, slot, false);
+
+			spin_unlock_bh(&ts_sdmmc_host->bh_lock);
+
+			/* check data crc16 */
+			calc_crc16 = ts7800v1_sdmmc_crc16(pslot->rw_dma_buf,
+							  data_blksz);
+
+			if (calc_crc16 != sent_dat0_crc16) {
+				dev_warn(
+					mmc_dev(ts_sdmmc_host->mmc_host),
+					"%s|%d - Warning, dat0 crc16 mismatch sent-crc16 %#x, calc-crc16 %#x\n",
+					__func__, __LINE__, sent_dat0_crc16,
+					calc_crc16);
+
+				*data_error = ret = -EILSEQ;
+				goto done;
+			}
+		}
+	}
+
+	*cmd_error = *data_error = ret = 0x0;
+
+done:
+
+	// 8 clocks before stopping
+	spin_lock_bh(&ts_sdmmc_host->bh_lock);
+
+	if (low_speed)
+		for (i = 0; i < 8; i++) {
+			/* toggle hi slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+			/* 2-delay reads */
+			add_2readb_delay(ts_sdmmc_host);
+
+			/* toggle low slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+			/* 2-delay reads */
+			add_2readb_delay(ts_sdmmc_host);
+		}
+	else {
+		/* send 8-bits on fast clk-line */
+		writeb(0xff, ts_sdmmc_host->base_iomem + SDCORE2_SDCMD_REG);
+	}
+
+	spin_unlock_bh(&ts_sdmmc_host->bh_lock);
+
+	if (!IS_ERR_OR_NULL(dat0_sent_crc16_buf)) {
+		kfree(dat0_sent_crc16_buf);
+		dat0_sent_crc16_buf = NULL;
+	}
+
+	return ret;
+}
+
+static int send_cmd_recv_resp_write_blk(
+	struct ts7800v1_sdmmc_host *ts_sdmmc_host, u8 slot, u32 cmd_opcode,
+	u32 cmd_arg, unsigned int cmd_flags, int *cmd_error, u32 *cmd_resp,
+	unsigned int data_blksz, u32 data_offset, int *data_error)
+{
+	struct ts7800v1_sdmmc_slot *pslot = &ts_sdmmc_host->sd_slot[slot];
+	u8 stat, resp_len_bytes, sent_resp_crc7, calc_resp_crc7;
+	u32 blksz_dwords = data_blksz >> 2, x32;
+	bool low_speed, data_read_done;
+	int ret = 0x0, i;
+
+	/* initial ok state, following are not pre-set by default */
+	pslot->sg_count = -1;
+	pslot->cmd_timeout = 0x0;
+	pslot->blk_buf_cycle_indx = pslot->blk_buf_nibble_indx = 0x0;
+	data_read_done = false;
+	/* clear crc_err/timeout */
+	readb(ts_sdmmc_host->base_iomem + 0x1);
+
+	/* low speed = sample on cmd-line, dat0-line */
+	low_speed = (pslot->sd_state & SD_LOWSPEED) ? true : false;
+
+	activate_slot_clk(ts_sdmmc_host, slot);
+
+	if (IS_ERR_OR_NULL(pslot->rw_dma_buf)) {
+		dev_warn(mmc_dev(ts_sdmmc_host->mmc_host),
+			 "%s|%d - Error, No allocated DMA read buffer %ld\n",
+			 __func__, __LINE__, PTR_ERR(pslot->rw_dma_buf));
+		*data_error = ret = -ENOMEM;
+		goto done;
+	}
+
+	if (!(cmd_flags & MMC_RSP_PRESENT)) {
+		dev_warn(
+			mmc_dev(ts_sdmmc_host->mmc_host),
+			"%s|%d - Error, read block command flgas must have a response\n",
+			__func__, __LINE__);
+		*cmd_error = ret = -EINVAL;
+	} else {
+		switch ((cmd_flags &
+			 (MMC_RSP_PRESENT | MMC_RSP_136 | MMC_RSP_CRC |
+			  MMC_RSP_BUSY | MMC_RSP_OPCODE))) {
+		case MMC_RSP_NONE:
+			resp_len_bytes = 0x0;
+			goto done;
+		case MMC_RSP_R1:
+		case MMC_RSP_R1B:
+		case MMC_RSP_R3:
+			resp_len_bytes = NORM_RESP_BYTES;
+			break;
+		case MMC_RSP_R2:
+			resp_len_bytes = LONG_RESP_BYTES;
+			break;
+		default:
+			dev_warn(mmc_dev(ts_sdmmc_host->mmc_host),
+				 "%s|%d - Warning, Invalid response type\n",
+				 __func__, __LINE__);
+			*cmd_error = ret = -EINVAL;
+			goto done;
+		}
+	}
+
+	/* serialize command */
+	spin_lock_bh(&ts_sdmmc_host->bh_lock);
+	send_serialize_cmd(ts_sdmmc_host, pslot, cmd_opcode, cmd_arg);
+	spin_unlock_bh(&ts_sdmmc_host->bh_lock);
+
+	/* wait for response, i.e. start bit=0 on slow sd_cmd */
+	mutex_lock(&ts_sdmmc_host->mutex_lock);
+	wait_for_response(ts_sdmmc_host, pslot, low_speed);
+	mutex_unlock(&ts_sdmmc_host->mutex_lock);
+
+	if (pslot->cmd_timeout >= MAX_RESP_TIMEOUT_MICROSECS) {
+		*cmd_error = ret = -ETIMEDOUT;
+		goto done;
+	}
+
+	/* serialize/consume cmd response */
+	spin_lock_bh(&ts_sdmmc_host->bh_lock);
+	sd_cmd_read(ts_sdmmc_host, pslot, resp_len_bytes, sizeof(u32));
+
+	spin_unlock_bh(&ts_sdmmc_host->bh_lock);
+
+	/* serialize/consume card's busy response if any */
+	if (cmd_flags & MMC_RSP_BUSY) {
+		stat = 0x0;
+		/* reset cmd time-out */
+		pslot->cmd_timeout = 0x0;
+
+		mutex_lock(&ts_sdmmc_host->mutex_lock);
+
+		while ((stat & 0x7) != 0x7) {
+			if (pslot->cmd_timeout++ >=
+			    MAX_BUSY_TIMEOUT_MICROSECS) {
+				*cmd_error = ret = -ETIMEDOUT;
+				goto done;
+			}
+
+			/* toggle high slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+
+			/* 1-delay reads */
+			if (low_speed)
+				add_1readb_delay(ts_sdmmc_host);
+
+			stat = stat << 1;
+			stat |= readb(ts_sdmmc_host->base_iomem) & 0x1;
+
+			/* toggle low slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+
+			/* 1-delay reads */
+			if (low_speed)
+				add_1readb_delay(ts_sdmmc_host);
+
+			usleep_range(15, 25);
+		}
+
+		mutex_unlock(&ts_sdmmc_host->mutex_lock);
+	}
+
+	/* process response outside of spin locks */
+	if (cmd_flags & MMC_RSP_136) {
+		/* R2, copy 12-bytes/3-double-words of argument including internal CRC7 */
+		memcpy(&cmd_resp[0], &pslot->response[12], 0x4);
+		memcpy(&cmd_resp[1], &pslot->response[8], 0x4);
+		memcpy(&cmd_resp[2], &pslot->response[4], 0x4);
+		memcpy(&cmd_resp[3], &pslot->response[0], 0x4);
+	} else {
+		/* R1, R1b, R3, R4, R5, R6 4-bytes arguments only*/
+		memcpy(cmd_resp, &pslot->response[1], 0x4);
+	}
+
+	/* resp crc7 check */
+	switch ((cmd_flags & (MMC_RSP_PRESENT | MMC_RSP_136 | MMC_RSP_CRC |
+			      MMC_RSP_BUSY | MMC_RSP_OPCODE))) {
+	case MMC_RSP_R1:
+	case MMC_RSP_R1B:
+		sent_resp_crc7 = pslot->response[0] >> 1;
+		calc_resp_crc7 = ts7800v1_sdmmc_crc7(
+			0, &pslot->response[1], resp_len_bytes - 1, BE_ENDIAN);
+
+		if (sent_resp_crc7 != calc_resp_crc7) {
+			*cmd_error = ret = -EILSEQ;
+			goto done;
+		}
+		break;
+	default:
+		/* no crc7 check*/
+		break;
+	}
+
+	spin_lock_bh(&ts_sdmmc_host->bh_lock);
+	send_sddat_start_nibble(ts_sdmmc_host);
+
+	for (i = 0; i < blksz_dwords; i++) {
+		memcpy(&x32, &pslot->rw_dma_buf[i << 2], sizeof(u32));
+
+		writel(x32, ts_sdmmc_host->base_iomem + SDCORE2_SDDATA_REG);
+	}
+
+	spin_unlock_bh(&ts_sdmmc_host->bh_lock);
+
+	*cmd_error = *data_error = ret = 0x0;
+
+done:
+
+	// 8 clocks before stopping
+	spin_lock_bh(&ts_sdmmc_host->bh_lock);
+
+	if (low_speed)
+		for (i = 0; i < 8; i++) {
+			/* toggle hi slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+			/* 2-delay reads */
+			add_2readb_delay(ts_sdmmc_host);
+
+			/* toggle low slow clk-line */
+			writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+			       ts_sdmmc_host->base_iomem);
+			/* 2-delay reads */
+			add_2readb_delay(ts_sdmmc_host);
+		}
+	else {
+		/* send 8-bits on fast clk-line */
+		writeb(0xff, ts_sdmmc_host->base_iomem + SDCORE2_SDCMD_REG);
+	}
+
+	spin_unlock_bh(&ts_sdmmc_host->bh_lock);
+
+	return ret;
+}
+
+static void ts7800v1_sdmmc_request(struct mmc_host *mmc,
+				   struct mmc_request *mmc_req)
+{
+	struct ts7800v1_sdmmc_host *ts_sdmmc_host = mmc_priv(mmc);
+	struct mmc_command *req_cmd = mmc_req->cmd;
+	struct mmc_command *sbc_cmd = mmc_req->sbc;
+	struct mmc_command *stop_cmd = mmc_req->stop;
+
+	if (req_cmd != NULL) {
+		if (req_cmd->data != NULL &&
+		    ((req_cmd->data->flags & MMC_DATA_READ) ||
+		     (req_cmd->data->flags & MMC_DATA_WRITE))) {
+			struct ts7800v1_sdmmc_slot *pslot =
+				&ts_sdmmc_host->sd_slot[SD_ACTIVE_SLOT];
+			u32 blocks;
+
+			enum dma_data_direction dma_data_dir =
+				mmc_get_dma_dir(req_cmd->data);
+			if (dma_data_dir == DMA_NONE) {
+				dev_err(mmc_dev(ts_sdmmc_host->mmc_host),
+					"%s|%d - Invalid data direction %d\n",
+					__func__, __LINE__, dma_data_dir);
+				req_cmd->error = req_cmd->data->error = -EINVAL;
+				goto mmc_request_done;
+			}
+
+			if (IS_ERR_OR_NULL(pslot)) {
+				dev_err(mmc_dev(ts_sdmmc_host->mmc_host),
+					"%s|%d - no valid slot selected!\n",
+					__func__, __LINE__);
+				req_cmd->error = req_cmd->data->error = -EINVAL;
+				goto mmc_request_done;
+			}
+
+			pslot->sg_count = -1;
+			blocks = (req_cmd->data->blksz * req_cmd->data->blocks);
+
+			pslot->rw_dma_buf =
+				kzalloc(blocks * sizeof(u8), GFP_KERNEL);
+			if (IS_ERR_OR_NULL(pslot->rw_dma_buf)) {
+				dev_err(mmc_dev(ts_sdmmc_host->mmc_host),
+					"%s|%d - kzalloc 'rw_dma_buf' size %u failed with %ld\n",
+					__func__, __LINE__, blocks,
+					PTR_ERR(pslot->rw_dma_buf));
+				req_cmd->error = req_cmd->data->error = -ENOMEM;
+				goto done;
+			}
+
+			pslot->sg_count = dma_map_sg(
+				mmc_dev(mmc_from_priv(ts_sdmmc_host)),
+				req_cmd->data->sg, req_cmd->data->sg_len,
+				dma_data_dir);
+
+			if (pslot->sg_count == 0) {
+				req_cmd->error = req_cmd->data->error = -ENOSPC;
+				goto done;
+			}
+
+			req_cmd->data->sg_count = pslot->sg_count;
+
+			if (req_cmd->data->flags & MMC_DATA_READ) {
+				/* check request data state */
+
+				if (sbc_cmd != NULL) {
+					sbc_cmd->error =
+						send_cmd_recv_resp_simple(
+							ts_sdmmc_host,
+							SD_ACTIVE_SLOT,
+							sbc_cmd->opcode,
+							sbc_cmd->arg,
+							sbc_cmd->flags,
+							&sbc_cmd->error,
+							sbc_cmd->resp);
+					if (sbc_cmd->error) {
+						dev_err(mmc_dev(ts_sdmmc_host
+									->mmc_host),
+							"%s|%d - sbc command failed with error %d!\n",
+							__func__, __LINE__,
+							sbc_cmd->error);
+
+						req_cmd->error =
+							req_cmd->data->error =
+								sbc_cmd->error;
+						goto done;
+					}
+				}
+
+				req_cmd->error = send_cmd_recv_resp_read_blk(
+					ts_sdmmc_host, SD_ACTIVE_SLOT,
+					req_cmd->opcode, req_cmd->arg,
+					req_cmd->flags, &req_cmd->error,
+					req_cmd->resp, blocks, 0,
+					&req_cmd->data->error);
+
+				/*
+				 * fpga controller bug workaround, simply re-issue command
+				 * This happens with some cards when querying their supported
+				 * function groups prior to swtiching to high speed mode
+				 */
+				if (req_cmd->error && req_cmd->opcode == 0x6)
+					req_cmd->error =
+						send_cmd_recv_resp_read_blk(
+							ts_sdmmc_host,
+							SD_ACTIVE_SLOT,
+							req_cmd->opcode,
+							req_cmd->arg,
+							req_cmd->flags,
+							&req_cmd->error,
+							req_cmd->resp, blocks,
+							0,
+							&req_cmd->data->error);
+
+				if (req_cmd->error != 0x0 ||
+				    req_cmd->data->error != 0x0)
+					goto done;
+
+				req_cmd->data->bytes_xfered += blocks;
+
+				/* Send stop-transmission command if requested */
+				if (stop_cmd != NULL && sbc_cmd == NULL) {
+					stop_cmd->error =
+						send_cmd_recv_resp_simple(
+							ts_sdmmc_host,
+							SD_ACTIVE_SLOT,
+							stop_cmd->opcode,
+							stop_cmd->arg,
+							stop_cmd->flags,
+							&stop_cmd->error,
+							stop_cmd->resp);
+
+					if (stop_cmd->error) {
+						dev_err(mmc_dev(ts_sdmmc_host
+									->mmc_host),
+							"%s|%d - stop command failed with error %d!\n",
+							__func__, __LINE__,
+							stop_cmd->error);
+
+						goto done;
+					}
+				}
+
+				/* mem-to-mem dma using SoC controller */
+					sg_copy_from_buffer(
+						req_cmd->data->sg,
+						req_cmd->data->sg_len,
+						pslot->rw_dma_buf, blocks);
+
+			} else { /* MMC_DATA_WRITE */
+				/* copy user data, mem-to-mem dma using SoC controller */
+				sg_copy_to_buffer(req_cmd->data->sg,
+						  req_cmd->data->sg_len,
+						  pslot->rw_dma_buf, blocks);
+
+				/* check request data state */
+
+				if (sbc_cmd != NULL) {
+					sbc_cmd->error =
+						send_cmd_recv_resp_simple(
+							ts_sdmmc_host,
+							SD_ACTIVE_SLOT,
+							sbc_cmd->opcode,
+							sbc_cmd->arg,
+							sbc_cmd->flags,
+							&sbc_cmd->error,
+							sbc_cmd->resp);
+					if (sbc_cmd->error) {
+						dev_err(mmc_dev(ts_sdmmc_host
+									->mmc_host),
+							"%s|%d - sbc command failed with error %d!\n",
+							__func__, __LINE__,
+							sbc_cmd->error);
+
+						req_cmd->error =
+							req_cmd->data->error =
+								sbc_cmd->error;
+						goto done;
+					}
+				}
+
+				req_cmd->error = send_cmd_recv_resp_write_blk(
+					ts_sdmmc_host, SD_ACTIVE_SLOT,
+					req_cmd->opcode, req_cmd->arg,
+					req_cmd->flags, &req_cmd->error,
+					req_cmd->resp, blocks, 0,
+					&req_cmd->data->error);
+
+				if (req_cmd->error != 0x0 ||
+				    req_cmd->data->error != 0x0)
+					goto done;
+
+				req_cmd->data->bytes_xfered += blocks;
+
+				/* Send stop-transmission command if requested */
+				if (stop_cmd != NULL && sbc_cmd == NULL) {
+					stop_cmd->error =
+						send_cmd_recv_resp_simple(
+							ts_sdmmc_host,
+							SD_ACTIVE_SLOT,
+							stop_cmd->opcode,
+							stop_cmd->arg,
+							stop_cmd->flags,
+							&stop_cmd->error,
+							stop_cmd->resp);
+
+					if (stop_cmd->error) {
+						dev_err(mmc_dev(ts_sdmmc_host
+									->mmc_host),
+							"%s|%d - stop command failed with error %d!\n",
+							__func__, __LINE__,
+							stop_cmd->error);
+						goto done;
+					}
+				}
+			}
+
+done:
+			if (pslot->sg_count > 0) {
+				dma_unmap_sg(
+					mmc_dev(mmc_from_priv(ts_sdmmc_host)),
+					req_cmd->data->sg,
+					req_cmd->data->sg_len, dma_data_dir);
+				pslot->sg_count = -1;
+			}
+
+			if (!IS_ERR_OR_NULL(pslot->rw_dma_buf)) {
+				kfree(pslot->rw_dma_buf);
+				pslot->rw_dma_buf = NULL;
+			}
+
+		} else {
+			req_cmd->error = send_cmd_recv_resp_simple(
+				ts_sdmmc_host, SD_ACTIVE_SLOT, req_cmd->opcode,
+				req_cmd->arg, req_cmd->flags, &req_cmd->error,
+				req_cmd->resp);
+
+			/* fpga controller bug workaround, simply re-issue command
+			 * This happens with some cards when querying their supported
+			 * function groups prior to swtiching to high speed mode
+			 */
+			if (req_cmd->error && req_cmd->opcode == 0x6)
+				req_cmd->error = send_cmd_recv_resp_simple(
+					ts_sdmmc_host, SD_ACTIVE_SLOT,
+					req_cmd->opcode, req_cmd->arg,
+					req_cmd->flags, &req_cmd->error,
+					req_cmd->resp);
+		}
+	}
+
+mmc_request_done:
+	mmc_request_done(mmc, mmc_req);
+}
+
+static void ts7800v1_sdmmc_host_init(struct ts7800v1_sdmmc_host *ts_sdmmc_host)
+{
+	ts_sdmmc_host->hw_version = ts7800v1_sdmmc_hw_version(ts_sdmmc_host);
+	card_reset(ts_sdmmc_host, SD_ACTIVE_SLOT);
+}
+
+static void ts7800v1_sdmmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{
+	/* change
+	 * power supply mode,
+	 * data bus width,
+	 * timing specification used, such as: MMC_TIMING_LEGACY,
+	 * MMC_TIMING_MMC_HS, MMC_TIMING_UHS_SDR12, etc
+	 * signalling voltage (1.8V or 3.3V),
+	 * driver type (A, B, C, D)
+	 */
+
+	struct ts7800v1_sdmmc_host *ts_sdmmc_host = mmc_priv(mmc);
+	struct ts7800v1_sdmmc_slot *pslot =
+		&ts_sdmmc_host->sd_slot[SD_ACTIVE_SLOT];
+
+	switch (ios->timing) {
+	case MMC_TIMING_LEGACY:
+		set_clkspd(ts_sdmmc_host, pslot, false /*slow*/);
+		break;
+
+	case MMC_TIMING_MMC_HS:
+	case MMC_TIMING_SD_HS:
+	default:
+		set_clkspd(ts_sdmmc_host, pslot, true /*fast*/);
+	}
+
+	switch (ios->bus_width) {
+	case MMC_BUS_WIDTH_4:
+		pslot->sd_state |= DATSSP_4BIT;
+		set_mlt_rdwr(ts_sdmmc_host, pslot, true /* multi */);
+
+		break;
+	default:
+		/* keep default 1 bit mode */
+		pslot->sd_state &= ~DATSSP_4BIT;
+		set_mlt_rdwr(ts_sdmmc_host, pslot, false);
+	}
+}
+
+static int ts7800v1_sdmmc_card_busy(struct mmc_host *mmc)
+{
+	struct ts7800v1_sdmmc_host *ts_sdmmc_host = mmc_priv(mmc);
+	u8 stat, a, i;
+
+	stat = 0x0;
+	for (i = 0; i < 8; i++) {
+		/* toggle high slow clk-line */
+		writeb(CMDTRI_DATTRI_SDCLKH_SDCMDH_SDDATH,
+		       ts_sdmmc_host->base_iomem);
+
+		/* 1-delay reads */
+		a = readb(ts_sdmmc_host->base_iomem);
+
+		/* look for 3-consecutive dat0 = 1 */
+		stat = stat << 1;
+		/* check dat0 for busy bit=1 */
+		stat |= readb(ts_sdmmc_host->base_iomem) & 0x1;
+
+		writeb(CMDTRI_DATTRI_SDCLKL_SDCMDH_SDDATH,
+		       ts_sdmmc_host->base_iomem);
+
+		/* 1-delay reads */
+		a = readb(ts_sdmmc_host->base_iomem);
+
+		if ((stat & 0x7) == 0x7)
+			break;
+	}
+
+	stat &= GENMASK(3, 0);
+	return !(stat == GENMASK(3, 0));
+}
+
+static void ts7800v1_sdmmc_hw_reset(struct mmc_host *mmc)
+{
+	struct ts7800v1_sdmmc_host *ts_sdmmc_host = mmc_priv(mmc);
+
+	card_reset(ts_sdmmc_host, SD_ACTIVE_SLOT);
+}
+
+/*
+ * 0 for a read/write card
+ * 1 for a read-only card
+ */
+static int ts7800v1_sdmmc_get_ro(struct mmc_host *mmc)
+{
+	struct ts7800v1_sdmmc_host *ts_sdmmc_host = mmc_priv(mmc);
+
+	return ts_sdmmc_host->sd_slot[SD_ACTIVE_SLOT].sd_wprot;
+}
+
+/*
+ * 0 for a absent card
+ * 1 for a present card
+ */
+static int ts7800v1_sdmmc_get_cd(struct mmc_host *mmc)
+{
+	struct ts7800v1_sdmmc_host *ts_sdmmc_host = mmc_priv(mmc);
+
+	return ts_sdmmc_host->sd_slot[SD_ACTIVE_SLOT].sd_detect;
+}
+
+static const struct mmc_host_ops ts7800v1_sdmmc_host_ops = {
+	.request = ts7800v1_sdmmc_request,
+	.set_ios = ts7800v1_sdmmc_set_ios,
+	.get_ro = ts7800v1_sdmmc_get_ro,
+	.get_cd = ts7800v1_sdmmc_get_cd,
+	.card_busy = ts7800v1_sdmmc_card_busy,
+	.card_hw_reset = ts7800v1_sdmmc_hw_reset,
+};
+
+static int ts7800v1_sdmmc_probe(struct platform_device *pdev)
+{
+	struct ts7800v1_sdmmc_host *ts_sdmmc_host = NULL;
+	struct mmc_host *mmc_host = NULL;
+	struct resource *mem_res = NULL, *irq_res = NULL;
+	int ret;
+
+	mmc_host =
+		mmc_alloc_host(sizeof(struct ts7800v1_sdmmc_host), &pdev->dev);
+	if (IS_ERR_OR_NULL(mmc_host)) {
+		dev_err(&pdev->dev,
+			"%s|%d - Failed to allocate mmc host, error %ld\n",
+			__func__, __LINE__, PTR_ERR(mmc_host));
+		ret = -ENOMEM;
+		goto err_alloc_host;
+	}
+
+	ts_sdmmc_host = mmc_priv(mmc_host);
+	ts_sdmmc_host->mmc_host = mmc_host;
+	ts_sdmmc_host->base_iomem = NULL;
+
+	spin_lock_init(&ts_sdmmc_host->bh_lock);
+	mutex_init(&ts_sdmmc_host->mutex_lock);
+
+	mem_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					       "ts_sdmmc_ctrl");
+	if (IS_ERR_OR_NULL(mem_res)) {
+		dev_err(&pdev->dev,
+			"%s|%d - Failed to get platform memory resource, error %ld\n",
+			__func__, __LINE__, PTR_ERR(mem_res));
+		ret = -ENXIO;
+		goto pltfrm_get_res_mem_err;
+	}
+
+	ts_sdmmc_host->base_iomem = devm_ioremap_resource(&pdev->dev, mem_res);
+	if (IS_ERR_OR_NULL(ts_sdmmc_host->base_iomem)) {
+		dev_err(&pdev->dev,
+			"%s|%d - Failed to IO map resource %s, error %ld\n",
+			__func__, __LINE__, mem_res->name,
+			PTR_ERR(ts_sdmmc_host->base_iomem));
+		ret = -EBUSY;
+		goto devm_ioremap_res_mem_err;
+	}
+
+	irq_res = platform_get_resource_byname(pdev, IORESOURCE_IRQ,
+					       "ts_sdmmc_sdbusy");
+	if (IS_ERR_OR_NULL(irq_res)) {
+		dev_err(&pdev->dev,
+			"%s|%d - Failed to get irq resource, error %ld\n",
+			__func__, __LINE__, PTR_ERR(irq_res));
+		ret = -ENXIO;
+		goto pltfrm_get_res_irq_err;
+	}
+
+	/* ensure 4-bit bus_width (only width supported by hardware) */
+	mmc_host->caps &= ~MMC_CAP_8_BIT_DATA;
+	mmc_host->caps |= MMC_CAP_4_BIT_DATA;
+
+	/* controller does not auto-generate CMD23 */
+	mmc_host->caps &= ~MMC_CAP_CMD23;
+
+	mmc_host->max_blk_count = MAX_BLK_COUNT;
+	mmc_host->max_blk_size = MAX_BLK_SIZE;
+	mmc_host->max_req_size =
+		mmc_host->max_blk_count * mmc_host->max_blk_size;
+	mmc_host->max_segs = MAX_SEGS;
+	mmc_host->max_seg_size = MAX_SEG_SIZE;
+	dma_set_max_seg_size(&pdev->dev, mmc_host->max_seg_size);
+
+	/* Set default capabilities */
+	mmc_host->caps |= MMC_CAP_WAIT_WHILE_BUSY | MMC_CAP_MMC_HIGHSPEED |
+			  MMC_CAP_SD_HIGHSPEED;
+
+	mmc_host->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
+
+	mmc_host->ops = &ts7800v1_sdmmc_host_ops;
+
+	ts7800v1_sdmmc_host_init(ts_sdmmc_host);
+
+	platform_set_drvdata(pdev, ts_sdmmc_host);
+
+	ret = mmc_add_host(mmc_host);
+	if (ret != 0) {
+		dev_err(&pdev->dev,
+			"%s|%d - Failed to add TS-7800v1 SD/MMC host controller, error %d\n",
+			__func__, __LINE__, ret);
+		goto err_mmc_add_host;
+	}
+
+	dev_info(&pdev->dev,
+		 "TS-7800v1 FPGA based SD/MMC Controller initialized\n");
+
+	return 0;
+
+err_mmc_add_host:
+	devm_free_irq(&pdev->dev, ts_sdmmc_host->sdbusy_irq, ts_sdmmc_host);
+pltfrm_get_res_irq_err:
+	devm_iounmap(&pdev->dev, ts_sdmmc_host->base_iomem);
+devm_ioremap_res_mem_err:
+pltfrm_get_res_mem_err:
+	mutex_destroy(&ts_sdmmc_host->mutex_lock);
+	mmc_free_host(mmc_host);
+err_alloc_host:
+	return ret;
+}
+
+static int ts7800v1_sdmmc_remove(struct platform_device *pdev)
+{
+	struct ts7800v1_sdmmc_host *ts_sdmmc_host = platform_get_drvdata(pdev);
+
+	mutex_destroy(&ts_sdmmc_host->mutex_lock);
+	mmc_remove_host(ts_sdmmc_host->mmc_host);
+	if (!IS_ERR_OR_NULL(ts_sdmmc_host->mmc_host))
+		mmc_free_host(ts_sdmmc_host->mmc_host);
+
+	dev_info(&pdev->dev,
+		 "TS-7800v1 FPGA based SD/MMC controller removed\n");
+
+	return 0;
+}
+
+static const struct platform_device_id ts7800v1_sdmmc_ids[] = {
+	{
+		.name = DRIVER_NAME,
+	},
+	{
+		/* sentinel */
+	}
+};
+
+MODULE_DEVICE_TABLE(platform, ts7800v1_sdmmc_ids);
+
+static struct platform_driver ts7800v1_sdmmc_driver = {
+	.probe = ts7800v1_sdmmc_probe,
+	.remove = ts7800v1_sdmmc_remove,
+	.id_table	= ts7800v1_sdmmc_ids,
+	.driver = {
+		.name = DRIVER_NAME,
+		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
+	},
+};
+
+module_platform_driver(ts7800v1_sdmmc_driver);
+
+MODULE_DESCRIPTION("TS-7800v1 FPGA based MMC Driver");
+MODULE_AUTHOR("Firas Ashkar <firas.ashkar@savoirfairelinux.com>");
+MODULE_LICENSE("GPL");