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@xxxxxxxxxxxxxxxxxxxx> --- :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(+) 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@xxxxxxxxxxxxxxxxxxxx>"); +MODULE_LICENSE("GPL"); -- 2.34.1