Commit 441bf733 authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman

staging: m57621-mmc: delete driver from the tree.

The license text in this driver is "interesting" and not really obvious
that it is supposed to be able to be distributed in the kernel source
tree.  Yes, the MODULE_LICENSE() text says GPL, so it's probably ok, but
to be safe, I am deleting this driver.  I will be glad to add it back if
the license is properly sorted out, but for now, this isn't worth the
potential risk, I should have never taken it in the first place.

Cc: Matthias Brugger <matthias.bgg@gmail.com>
Cc: NeilBrown <neil@brown.name>
Cc: George Hilliard <thirtythreeforty@gmail.com>
Cc: "Christian Lütke-Stetzkamp" <christian@lkamp.de>
Cc: Nishad Kamdar <nishadkamdar@gmail.com>
Cc: Sergej Perschin <ser.perschin@gmail.com>
Cc: John Crispin <blogic@openwrt.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 97ed8eab
...@@ -113,8 +113,6 @@ source "drivers/staging/mt7621-dma/Kconfig" ...@@ -113,8 +113,6 @@ source "drivers/staging/mt7621-dma/Kconfig"
source "drivers/staging/ralink-gdma/Kconfig" source "drivers/staging/ralink-gdma/Kconfig"
source "drivers/staging/mt7621-mmc/Kconfig"
source "drivers/staging/mt7621-dts/Kconfig" source "drivers/staging/mt7621-dts/Kconfig"
source "drivers/staging/gasket/Kconfig" source "drivers/staging/gasket/Kconfig"
......
...@@ -46,7 +46,6 @@ obj-$(CONFIG_PINCTRL_RT2880) += mt7621-pinctrl/ ...@@ -46,7 +46,6 @@ obj-$(CONFIG_PINCTRL_RT2880) += mt7621-pinctrl/
obj-$(CONFIG_SPI_MT7621) += mt7621-spi/ obj-$(CONFIG_SPI_MT7621) += mt7621-spi/
obj-$(CONFIG_SOC_MT7621) += mt7621-dma/ obj-$(CONFIG_SOC_MT7621) += mt7621-dma/
obj-$(CONFIG_DMA_RALINK) += ralink-gdma/ obj-$(CONFIG_DMA_RALINK) += ralink-gdma/
obj-$(CONFIG_MTK_MMC) += mt7621-mmc/
obj-$(CONFIG_SOC_MT7621) += mt7621-dts/ obj-$(CONFIG_SOC_MT7621) += mt7621-dts/
obj-$(CONFIG_STAGING_GASKET_FRAMEWORK) += gasket/ obj-$(CONFIG_STAGING_GASKET_FRAMEWORK) += gasket/
obj-$(CONFIG_XIL_AXIS_FIFO) += axis-fifo/ obj-$(CONFIG_XIL_AXIS_FIFO) += axis-fifo/
......
# SPDX-License-Identifier: GPL-2.0
config MTK_MMC
tristate "MTK SD/MMC"
depends on RALINK && MMC
config MTK_AEE_KDUMP
bool "MTK AEE KDUMP"
depends on MTK_MMC
# Copyright Statement:
#
# This software/firmware and related documentation ("MediaTek Software") are
# protected under relevant copyright laws. The information contained herein
# is confidential and proprietary to MediaTek Inc. and/or its licensors.
# Without the prior written permission of MediaTek inc. and/or its licensors,
# any reproduction, modification, use or disclosure of MediaTek Software,
# and information contained herein, in whole or in part, shall be strictly prohibited.
#
# MediaTek Inc. (C) 2010. All rights reserved.
#
# BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
# THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
# RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO RECEIVER ON
# AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
# NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
# SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
# SUPPLIED WITH THE MEDIATEK SOFTWARE, AND RECEIVER AGREES TO LOOK ONLY TO SUCH
# THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. RECEIVER EXPRESSLY ACKNOWLEDGES
# THAT IT IS RECEIVER'S SOLE RESPONSIBILITY TO OBTAIN FROM ANY THIRD PARTY ALL PROPER LICENSES
# CONTAINED IN MEDIATEK SOFTWARE. MEDIATEK SHALL ALSO NOT BE RESPONSIBLE FOR ANY MEDIATEK
# SOFTWARE RELEASES MADE TO RECEIVER'S SPECIFICATION OR TO CONFORM TO A PARTICULAR
# STANDARD OR OPEN FORUM. RECEIVER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND
# CUMULATIVE LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,
# AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,
# OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY RECEIVER TO
# MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
#
# The following software/firmware and/or related documentation ("MediaTek Software")
# have been modified by MediaTek Inc. All revisions are subject to any receiver's
# applicable license agreements with MediaTek Inc.
obj-$(CONFIG_MTK_MMC) += mtk_sd.o
mtk_sd-objs := sd.o dbg.o
ifeq ($(CONFIG_MTK_AEE_KDUMP),y)
EXTRA_CFLAGS += -DMT6575_SD_DEBUG
endif
clean:
@rm -f *.o modules.order .*.cmd
- general code review and clean up
- ensure device-tree requirements are documented
- should probably be merged with drivers/mmc/host/mtk-sd.c
- possibly fix to work with highmem pages so a bounce buffer isn't
needed.
Cc: NeilBrown <neil@brown.name>
/* Copyright Statement:
*
* This software/firmware and related documentation ("MediaTek Software") are
* protected under relevant copyright laws. The information contained herein
* is confidential and proprietary to MediaTek Inc. and/or its licensors.
* Without the prior written permission of MediaTek inc. and/or its licensors,
* any reproduction, modification, use or disclosure of MediaTek Software,
* and information contained herein, in whole or in part, shall be strictly prohibited.
*/
/* MediaTek Inc. (C) 2010. All rights reserved.
*
* BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
* THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
* RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO RECEIVER ON
* AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
* NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
* SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
* SUPPLIED WITH THE MEDIATEK SOFTWARE, AND RECEIVER AGREES TO LOOK ONLY TO SUCH
* THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. RECEIVER EXPRESSLY ACKNOWLEDGES
* THAT IT IS RECEIVER'S SOLE RESPONSIBILITY TO OBTAIN FROM ANY THIRD PARTY ALL PROPER LICENSES
* CONTAINED IN MEDIATEK SOFTWARE. MEDIATEK SHALL ALSO NOT BE RESPONSIBLE FOR ANY MEDIATEK
* SOFTWARE RELEASES MADE TO RECEIVER'S SPECIFICATION OR TO CONFORM TO A PARTICULAR
* STANDARD OR OPEN FORUM. RECEIVER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND
* CUMULATIVE LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,
* AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,
* OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY RECEIVER TO
* MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
*
* The following software/firmware and/or related documentation ("MediaTek Software")
* have been modified by MediaTek Inc. All revisions are subject to any receiver's
* applicable license agreements with MediaTek Inc.
*/
#ifndef __ARCH_ARM_MACH_BOARD_H
#define __ARCH_ARM_MACH_BOARD_H
#define MSDC_CD_PIN_EN BIT(0) /* card detection pin is wired */
#define MSDC_WP_PIN_EN BIT(1) /* write protection pin is wired */
#define MSDC_RST_PIN_EN BIT(2) /* emmc reset pin is wired */
#define MSDC_REMOVABLE BIT(5) /* removable slot */
#define MSDC_SMPL_RISING (0)
#define MSDC_SMPL_FALLING (1)
#define MSDC_CMD_PIN (0)
#define MSDC_DAT_PIN (1)
#define MSDC_CD_PIN (2)
#define MSDC_WP_PIN (3)
#define MSDC_RST_PIN (4)
struct msdc_hw {
unsigned char clk_src; /* host clock source */
unsigned long flags; /* hardware capability flags */
/* config gpio pull mode */
void (*config_gpio_pin)(int type, int pull);
};
extern struct msdc_hw msdc0_hw;
#endif /* __ARCH_ARM_MACH_BOARD_H */
/* Copyright Statement:
*
* This software/firmware and related documentation ("MediaTek Software") are
* protected under relevant copyright laws. The information contained herein
* is confidential and proprietary to MediaTek Inc. and/or its licensors.
* Without the prior written permission of MediaTek inc. and/or its licensors,
* any reproduction, modification, use or disclosure of MediaTek Software,
* and information contained herein, in whole or in part, shall be strictly
* prohibited.
*
* MediaTek Inc. (C) 2010. All rights reserved.
*
* BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
* THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
* RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO RECEIVER ON
* AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
* NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
* SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
* SUPPLIED WITH THE MEDIATEK SOFTWARE, AND RECEIVER AGREES TO LOOK ONLY TO
* SUCH THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. RECEIVER EXPRESSLY
* ACKNOWLEDGES THAT IT IS RECEIVER'S SOLE RESPONSIBILITY TO OBTAIN FROM ANY
* THIRD PARTY ALL PROPER LICENSES CONTAINED IN MEDIATEK SOFTWARE. MEDIATEK
* SHALL ALSO NOT BE RESPONSIBLE FOR ANY MEDIATEK SOFTWARE RELEASES MADE TO
* RECEIVER'S SPECIFICATION OR TO CONFORM TO A PARTICULAR STANDARD OR OPEN
* FORUM. RECEIVER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND
* CUMULATIVE LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED
* HEREUNDER WILL BE, AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK
* SOFTWARE AT ISSUE, OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE
* PAID BY RECEIVER TO MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
*
* The following software/firmware and/or related documentation
* ("MediaTek Software") have been modified by MediaTek Inc. All revisions
* are subject to any receiver's applicable license agreements with MediaTek
* Inc.
*/
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/string.h>
#include <linux/uaccess.h>
// #include <mach/mt6575_gpt.h> /* --- by chhung */
#include "dbg.h"
#include "mt6575_sd.h"
#include <linux/seq_file.h>
/* for debug zone */
unsigned int sd_debug_zone[4] = {
0,
0,
0,
0
};
#if defined(MT6575_SD_DEBUG)
static char cmd_buf[256];
/* for driver profile */
#define TICKS_ONE_MS (13000)
u32 gpt_enable;
u32 sdio_pro_enable; /* make sure gpt is enabled */
u32 sdio_pro_time; /* no more than 30s */
struct sdio_profile sdio_perfomance = {0};
u32 msdc_time_calc(u32 old_L32, u32 old_H32, u32 new_L32, u32 new_H32)
{
u32 ret = 0;
if (new_H32 == old_H32) {
ret = new_L32 - old_L32;
} else if (new_H32 == (old_H32 + 1)) {
if (new_L32 > old_L32)
pr_debug("msdc old_L<0x%x> new_L<0x%x>\n",
old_L32, new_L32);
ret = (0xffffffff - old_L32);
ret += new_L32;
} else {
pr_debug("msdc old_H<0x%x> new_H<0x%x>\n", old_H32, new_H32);
}
return ret;
}
void msdc_sdio_profile(struct sdio_profile *result)
{
struct cmd_profile *cmd;
u32 i;
pr_debug("sdio === performance dump ===\n");
pr_debug("sdio === total execute tick<%d> time<%dms> Tx<%dB> Rx<%dB>\n",
result->total_tc, result->total_tc / TICKS_ONE_MS,
result->total_tx_bytes, result->total_rx_bytes);
/* CMD52 Dump */
cmd = &result->cmd52_rx;
pr_debug("sdio === CMD52 Rx <%d>times tick<%d> Max<%d> Min<%d> Aver<%d>\n",
cmd->count, cmd->tot_tc, cmd->max_tc, cmd->min_tc,
cmd->tot_tc / cmd->count);
cmd = &result->cmd52_tx;
pr_debug("sdio === CMD52 Tx <%d>times tick<%d> Max<%d> Min<%d> Aver<%d>\n",
cmd->count, cmd->tot_tc, cmd->max_tc, cmd->min_tc,
cmd->tot_tc / cmd->count);
/* CMD53 Rx bytes + block mode */
for (i = 0; i < 512; i++) {
cmd = &result->cmd53_rx_byte[i];
if (cmd->count) {
pr_debug("sdio<%6d><%3dB>_Rx_<%9d><%9d><%6d><%6d>_<%9dB><%2dM>\n",
cmd->count, i, cmd->tot_tc, cmd->max_tc,
cmd->min_tc, cmd->tot_tc / cmd->count,
cmd->tot_bytes,
(cmd->tot_bytes / 10) * 13 / (cmd->tot_tc / 10));
}
}
for (i = 0; i < 100; i++) {
cmd = &result->cmd53_rx_blk[i];
if (cmd->count) {
pr_debug("sdio<%6d><%3d>B_Rx_<%9d><%9d><%6d><%6d>_<%9dB><%2dM>\n",
cmd->count, i, cmd->tot_tc, cmd->max_tc,
cmd->min_tc, cmd->tot_tc / cmd->count,
cmd->tot_bytes,
(cmd->tot_bytes / 10) * 13 / (cmd->tot_tc / 10));
}
}
/* CMD53 Tx bytes + block mode */
for (i = 0; i < 512; i++) {
cmd = &result->cmd53_tx_byte[i];
if (cmd->count) {
pr_debug("sdio<%6d><%3dB>_Tx_<%9d><%9d><%6d><%6d>_<%9dB><%2dM>\n",
cmd->count, i, cmd->tot_tc, cmd->max_tc,
cmd->min_tc, cmd->tot_tc / cmd->count,
cmd->tot_bytes,
(cmd->tot_bytes / 10) * 13 / (cmd->tot_tc / 10));
}
}
for (i = 0; i < 100; i++) {
cmd = &result->cmd53_tx_blk[i];
if (cmd->count) {
pr_debug("sdio<%6d><%3d>B_Tx_<%9d><%9d><%6d><%6d>_<%9dB><%2dM>\n",
cmd->count, i, cmd->tot_tc, cmd->max_tc,
cmd->min_tc, cmd->tot_tc / cmd->count,
cmd->tot_bytes,
(cmd->tot_bytes / 10) * 13 / (cmd->tot_tc / 10));
}
}
pr_debug("sdio === performance dump done ===\n");
}
//========= sdio command table ===========
void msdc_performance(u32 opcode, u32 sizes, u32 bRx, u32 ticks)
{
struct sdio_profile *result = &sdio_perfomance;
struct cmd_profile *cmd;
u32 block;
if (sdio_pro_enable == 0)
return;
if (opcode == 52) {
cmd = bRx ? &result->cmd52_rx : &result->cmd52_tx;
} else if (opcode == 53) {
if (sizes < 512) {
cmd = bRx ? &result->cmd53_rx_byte[sizes] : &result->cmd53_tx_byte[sizes];
} else {
block = sizes / 512;
if (block >= 99) {
pr_err("cmd53 error blocks\n");
while (1)
;
}
cmd = bRx ? &result->cmd53_rx_blk[block] : &result->cmd53_tx_blk[block];
}
} else {
return;
}
/* update the members */
if (ticks > cmd->max_tc)
cmd->max_tc = ticks;
if (cmd->min_tc == 0 || ticks < cmd->min_tc)
cmd->min_tc = ticks;
cmd->tot_tc += ticks;
cmd->tot_bytes += sizes;
cmd->count++;
if (bRx)
result->total_rx_bytes += sizes;
else
result->total_tx_bytes += sizes;
result->total_tc += ticks;
/* dump when total_tc > 30s */
if (result->total_tc >= sdio_pro_time * TICKS_ONE_MS * 1000) {
msdc_sdio_profile(result);
memset(result, 0, sizeof(struct sdio_profile));
}
}
//========== driver proc interface ===========
static int msdc_debug_proc_read(struct seq_file *s, void *p)
{
seq_puts(s, "\n=========================================\n");
seq_puts(s, "Index<0> + Id + Zone\n");
seq_puts(s, "-> PWR<9> WRN<8> | FIO<7> OPS<6> FUN<5> CFG<4> | INT<3> RSP<2> CMD<1> DMA<0>\n");
seq_puts(s, "-> echo 0 3 0x3ff >msdc_bebug -> host[3] debug zone set to 0x3ff\n");
seq_printf(s, "-> MSDC[0] Zone: 0x%.8x\n", sd_debug_zone[0]);
seq_printf(s, "-> MSDC[1] Zone: 0x%.8x\n", sd_debug_zone[1]);
seq_printf(s, "-> MSDC[2] Zone: 0x%.8x\n", sd_debug_zone[2]);
seq_printf(s, "-> MSDC[3] Zone: 0x%.8x\n", sd_debug_zone[3]);
seq_puts(s, "Index<3> + SDIO_PROFILE + TIME\n");
seq_puts(s, "-> echo 3 1 0x1E >msdc_bebug -> enable sdio_profile, 30s\n");
seq_printf(s, "-> SDIO_PROFILE<%d> TIME<%ds>\n",
sdio_pro_enable, sdio_pro_time);
seq_puts(s, "=========================================\n\n");
return 0;
}
static ssize_t msdc_debug_proc_write(struct file *file,
const char __user *buf,
size_t count, loff_t *data)
{
int ret;
int cmd, p1, p2;
int id, zone;
if (count == 0)
return -1;
if (count > 255)
count = 255;
if (copy_from_user(cmd_buf, buf, count))
return -EFAULT;
cmd_buf[count] = '\0';
pr_debug("msdc Write %s\n", cmd_buf);
ret = sscanf(cmd_buf, "%x %x %x", &cmd, &p1, &p2);
if (ret != 3)
return -EINVAL;
if (cmd == SD_TOOL_ZONE) {
id = p1;
zone = p2;
zone &= 0x3ff;
pr_debug("msdc host_id<%d> zone<0x%.8x>\n", id, zone);
if (id >= 0 && id <= 3) {
sd_debug_zone[id] = zone;
} else if (id == 4) {
sd_debug_zone[0] = sd_debug_zone[1] = zone;
sd_debug_zone[2] = sd_debug_zone[3] = zone;
} else {
pr_err("msdc host_id error when set debug zone\n");
}
} else if (cmd == SD_TOOL_SDIO_PROFILE) {
if (p1 == 1) { /* enable profile */
if (gpt_enable == 0)
gpt_enable = 1;
sdio_pro_enable = 1;
if (p2 == 0)
p2 = 1;
if (p2 >= 30)
p2 = 30;
sdio_pro_time = p2;
} else if (p1 == 0) {
/* todo */
sdio_pro_enable = 0;
}
}
return count;
}
static int msdc_debug_show(struct inode *inode, struct file *file)
{
return single_open(file, msdc_debug_proc_read, NULL);
}
static const struct file_operations msdc_debug_fops = {
.owner = THIS_MODULE,
.open = msdc_debug_show,
.read = seq_read,
.write = msdc_debug_proc_write,
.llseek = seq_lseek,
.release = single_release,
};
// Keep ahold of the proc entry we create so it can be freed during
// module removal
struct proc_dir_entry *msdc_debug_proc_entry;
void __init msdc_debug_proc_init(void)
{
msdc_debug_proc_entry = proc_create("msdc_debug", 0660,
NULL, &msdc_debug_fops);
}
void __exit msdc_debug_proc_deinit(void)
{
proc_remove(msdc_debug_proc_entry);
}
#endif
/* Copyright Statement:
*
* This software/firmware and related documentation ("MediaTek Software") are
* protected under relevant copyright laws. The information contained herein
* is confidential and proprietary to MediaTek Inc. and/or its licensors.
* Without the prior written permission of MediaTek inc. and/or its licensors,
* any reproduction, modification, use or disclosure of MediaTek Software,
* and information contained herein, in whole or in part, shall be strictly
* prohibited.
*
* MediaTek Inc. (C) 2010. All rights reserved.
*
* BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
* THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
* RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO RECEIVER ON
* AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
* NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
* SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
* SUPPLIED WITH THE MEDIATEK SOFTWARE, AND RECEIVER AGREES TO LOOK ONLY TO SUCH
* THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. RECEIVER EXPRESSLY
* ACKNOWLEDGES THAT IT IS RECEIVER'S SOLE RESPONSIBILITY TO OBTAIN FROM ANY
* THIRD PARTY ALL PROPER LICENSES CONTAINED IN MEDIATEK SOFTWARE. MEDIATEK
* SHALL ALSO NOT BE RESPONSIBLE FOR ANY MEDIATEK SOFTWARE RELEASES MADE TO
* RECEIVER'S SPECIFICATION OR TO CONFORM TO A PARTICULAR STANDARD OR OPEN
* FORUM. RECEIVER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND
* CUMULATIVE LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED
* HEREUNDER WILL BE, AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK
* SOFTWARE AT ISSUE, OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE
* PAID BY RECEIVER TO MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
*
* The following software/firmware and/or related documentation
* ("MediaTek Software") have been modified by MediaTek Inc. All revisions are
* subject to any receiver's applicable license agreements with MediaTek Inc.
*/
#ifndef __MT_MSDC_DEUBG__
#define __MT_MSDC_DEUBG__
//==========================
extern u32 sdio_pro_enable;
/* for a type command, e.g. CMD53, 2 blocks */
struct cmd_profile {
u32 max_tc; /* Max tick count */
u32 min_tc;
u32 tot_tc; /* total tick count */
u32 tot_bytes;
u32 count; /* the counts of the command */
};
/* dump when total_tc and total_bytes */
struct sdio_profile {
u32 total_tc; /* total tick count of CMD52 and CMD53 */
u32 total_tx_bytes; /* total bytes of CMD53 Tx */
u32 total_rx_bytes; /* total bytes of CMD53 Rx */
/*CMD52*/
struct cmd_profile cmd52_tx;
struct cmd_profile cmd52_rx;
/*CMD53 in byte unit */
struct cmd_profile cmd53_tx_byte[512];
struct cmd_profile cmd53_rx_byte[512];
/*CMD53 in block unit */
struct cmd_profile cmd53_tx_blk[100];
struct cmd_profile cmd53_rx_blk[100];
};
//==========================
enum msdc_dbg {
SD_TOOL_ZONE = 0,
SD_TOOL_DMA_SIZE = 1,
SD_TOOL_PM_ENABLE = 2,
SD_TOOL_SDIO_PROFILE = 3,
};
/* Debug message event */
#define DBG_EVT_NONE (0) /* No event */
#define DBG_EVT_DMA BIT(0) /* DMA related event */
#define DBG_EVT_CMD BIT(1) /* MSDC CMD related event */
#define DBG_EVT_RSP BIT(2) /* MSDC CMD RSP related event */
#define DBG_EVT_INT BIT(3) /* MSDC INT event */
#define DBG_EVT_CFG BIT(4) /* MSDC CFG event */
#define DBG_EVT_FUC BIT(5) /* Function event */
#define DBG_EVT_OPS BIT(6) /* Read/Write operation event */
#define DBG_EVT_FIO BIT(7) /* FIFO operation event */
#define DBG_EVT_WRN BIT(8) /* Warning event */
#define DBG_EVT_PWR BIT(9) /* Power event */
#define DBG_EVT_ALL (0xffffffff)
#define DBG_EVT_MASK (DBG_EVT_ALL)
extern unsigned int sd_debug_zone[4];
#ifdef MT6575_SD_DEBUG
void __init msdc_debug_proc_init(void);
void __exit msdc_debug_proc_deinit(void);
#else
static inline void msdc_debug_proc_init(void) {}
static inline void msdc_debug_proc_deinit(void) {}
#endif
u32 msdc_time_calc(u32 old_L32, u32 old_H32, u32 new_L32, u32 new_H32);
void msdc_performance(u32 opcode, u32 sizes, u32 bRx, u32 ticks);
#endif
/* Copyright Statement:
*
* This software/firmware and related documentation ("MediaTek Software") are
* protected under relevant copyright laws. The information contained herein
* is confidential and proprietary to MediaTek Inc. and/or its licensors.
* Without the prior written permission of MediaTek inc. and/or its licensors,
* any reproduction, modification, use or disclosure of MediaTek Software,
* and information contained herein, in whole or in part, shall be strictly prohibited.
*/
/* MediaTek Inc. (C) 2010. All rights reserved.
*
* BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
* THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
* RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO RECEIVER ON
* AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
* NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
* SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
* SUPPLIED WITH THE MEDIATEK SOFTWARE, AND RECEIVER AGREES TO LOOK ONLY TO SUCH
* THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. RECEIVER EXPRESSLY ACKNOWLEDGES
* THAT IT IS RECEIVER'S SOLE RESPONSIBILITY TO OBTAIN FROM ANY THIRD PARTY ALL PROPER LICENSES
* CONTAINED IN MEDIATEK SOFTWARE. MEDIATEK SHALL ALSO NOT BE RESPONSIBLE FOR ANY MEDIATEK
* SOFTWARE RELEASES MADE TO RECEIVER'S SPECIFICATION OR TO CONFORM TO A PARTICULAR
* STANDARD OR OPEN FORUM. RECEIVER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND
* CUMULATIVE LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,
* AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,
* OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY RECEIVER TO
* MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
*
* The following software/firmware and/or related documentation ("MediaTek Software")
* have been modified by MediaTek Inc. All revisions are subject to any receiver's
* applicable license agreements with MediaTek Inc.
*/
#ifndef MT6575_SD_H
#define MT6575_SD_H
#include <linux/bitops.h>
#include <linux/mmc/host.h>
/*--------------------------------------------------------------------------*/
/* Common Definition */
/*--------------------------------------------------------------------------*/
#define MSDC_FIFO_SZ (128)
#define MSDC_FIFO_THD (64) // (128)
#define MSDC_NUM (4)
#define MSDC_MS (0)
#define MSDC_SDMMC (1)
#define MSDC_BUS_1BITS (0)
#define MSDC_BUS_4BITS (1)
#define MSDC_BUS_8BITS (2)
#define MSDC_BURST_8B (3)
#define MSDC_BURST_16B (4)
#define MSDC_BURST_32B (5)
#define MSDC_BURST_64B (6)
#define MSDC_PIN_PULL_NONE (0)
#define MSDC_PIN_PULL_DOWN (1)
#define MSDC_PIN_PULL_UP (2)
#define MSDC_PIN_KEEP (3)
#define MSDC_MAX_SCLK (48000000) /* +/- by chhung */
#define MSDC_MIN_SCLK (260000)
#define MSDC_AUTOCMD12 (0x0001)
#define MSDC_AUTOCMD23 (0x0002)
#define MSDC_AUTOCMD19 (0x0003)
#define MSDC_EMMC_BOOTMODE0 (0) /* Pull low CMD mode */
#define MSDC_EMMC_BOOTMODE1 (1) /* Reset CMD mode */
enum {
RESP_NONE = 0,
RESP_R1,
RESP_R2,
RESP_R3,
RESP_R4,
RESP_R5,
RESP_R6,
RESP_R7,
RESP_R1B
};
/*--------------------------------------------------------------------------*/
/* Register Offset */
/*--------------------------------------------------------------------------*/
#define MSDC_CFG (0x0)
#define MSDC_IOCON (0x04)
#define MSDC_PS (0x08)
#define MSDC_INT (0x0c)
#define MSDC_INTEN (0x10)
#define MSDC_FIFOCS (0x14)
#define MSDC_TXDATA (0x18)
#define MSDC_RXDATA (0x1c)
#define SDC_CFG (0x30)
#define SDC_CMD (0x34)
#define SDC_ARG (0x38)
#define SDC_STS (0x3c)
#define SDC_RESP0 (0x40)
#define SDC_RESP1 (0x44)
#define SDC_RESP2 (0x48)
#define SDC_RESP3 (0x4c)
#define SDC_BLK_NUM (0x50)
#define SDC_CSTS (0x58)
#define SDC_CSTS_EN (0x5c)
#define SDC_DCRC_STS (0x60)
#define EMMC_CFG0 (0x70)
#define EMMC_CFG1 (0x74)
#define EMMC_STS (0x78)
#define EMMC_IOCON (0x7c)
#define SDC_ACMD_RESP (0x80)
#define SDC_ACMD19_TRG (0x84)
#define SDC_ACMD19_STS (0x88)
#define MSDC_DMA_SA (0x90)
#define MSDC_DMA_CA (0x94)
#define MSDC_DMA_CTRL (0x98)
#define MSDC_DMA_CFG (0x9c)
#define MSDC_DBG_SEL (0xa0)
#define MSDC_DBG_OUT (0xa4)
#define MSDC_PATCH_BIT (0xb0)
#define MSDC_PATCH_BIT0 MSDC_PATCH_BIT
#define MSDC_PATCH_BIT1 (0xb4)
#define MSDC_PAD_CTL0 (0xe0)
#define MSDC_PAD_CTL1 (0xe4)
#define MSDC_PAD_CTL2 (0xe8)
#define MSDC_PAD_TUNE (0xec)
#define MSDC_DAT_RDDLY0 (0xf0)
#define MSDC_DAT_RDDLY1 (0xf4)
#define MSDC_HW_DBG (0xf8)
#define MSDC_VERSION (0x100)
#define MSDC_ECO_VER (0x104)
/*--------------------------------------------------------------------------*/
/* Register Mask */
/*--------------------------------------------------------------------------*/
/* MSDC_CFG mask */
#define MSDC_CFG_MODE (0x1 << 0) /* RW */
#define MSDC_CFG_CKPDN (0x1 << 1) /* RW */
#define MSDC_CFG_RST (0x1 << 2) /* RW */
#define MSDC_CFG_PIO (0x1 << 3) /* RW */
#define MSDC_CFG_CKDRVEN (0x1 << 4) /* RW */
#define MSDC_CFG_BV18SDT (0x1 << 5) /* RW */
#define MSDC_CFG_BV18PSS (0x1 << 6) /* R */
#define MSDC_CFG_CKSTB (0x1 << 7) /* R */
#define MSDC_CFG_CKDIV (0xff << 8) /* RW */
#define MSDC_CFG_CKMOD (0x3 << 16) /* RW */
/* MSDC_IOCON mask */
#define MSDC_IOCON_SDR104CKS (0x1 << 0) /* RW */
#define MSDC_IOCON_RSPL (0x1 << 1) /* RW */
#define MSDC_IOCON_DSPL (0x1 << 2) /* RW */
#define MSDC_IOCON_DDLSEL (0x1 << 3) /* RW */
#define MSDC_IOCON_DDR50CKD (0x1 << 4) /* RW */
#define MSDC_IOCON_DSPLSEL (0x1 << 5) /* RW */
#define MSDC_IOCON_D0SPL (0x1 << 16) /* RW */
#define MSDC_IOCON_D1SPL (0x1 << 17) /* RW */
#define MSDC_IOCON_D2SPL (0x1 << 18) /* RW */
#define MSDC_IOCON_D3SPL (0x1 << 19) /* RW */
#define MSDC_IOCON_D4SPL (0x1 << 20) /* RW */
#define MSDC_IOCON_D5SPL (0x1 << 21) /* RW */
#define MSDC_IOCON_D6SPL (0x1 << 22) /* RW */
#define MSDC_IOCON_D7SPL (0x1 << 23) /* RW */
#define MSDC_IOCON_RISCSZ (0x3 << 24) /* RW */
/* MSDC_PS mask */
#define MSDC_PS_CDEN (0x1 << 0) /* RW */
#define MSDC_PS_CDSTS (0x1 << 1) /* R */
#define MSDC_PS_CDDEBOUNCE (0xf << 12) /* RW */
#define MSDC_PS_DAT (0xff << 16) /* R */
#define MSDC_PS_CMD (0x1 << 24) /* R */
#define MSDC_PS_WP (0x1UL << 31) /* R */
/* MSDC_INT mask */
#define MSDC_INT_MMCIRQ (0x1 << 0) /* W1C */
#define MSDC_INT_CDSC (0x1 << 1) /* W1C */
#define MSDC_INT_ACMDRDY (0x1 << 3) /* W1C */
#define MSDC_INT_ACMDTMO (0x1 << 4) /* W1C */
#define MSDC_INT_ACMDCRCERR (0x1 << 5) /* W1C */
#define MSDC_INT_DMAQ_EMPTY (0x1 << 6) /* W1C */
#define MSDC_INT_SDIOIRQ (0x1 << 7) /* W1C */
#define MSDC_INT_CMDRDY (0x1 << 8) /* W1C */
#define MSDC_INT_CMDTMO (0x1 << 9) /* W1C */
#define MSDC_INT_RSPCRCERR (0x1 << 10) /* W1C */
#define MSDC_INT_CSTA (0x1 << 11) /* R */
#define MSDC_INT_XFER_COMPL (0x1 << 12) /* W1C */
#define MSDC_INT_DXFER_DONE (0x1 << 13) /* W1C */
#define MSDC_INT_DATTMO (0x1 << 14) /* W1C */
#define MSDC_INT_DATCRCERR (0x1 << 15) /* W1C */
#define MSDC_INT_ACMD19_DONE (0x1 << 16) /* W1C */
/* MSDC_INTEN mask */
#define MSDC_INTEN_MMCIRQ (0x1 << 0) /* RW */
#define MSDC_INTEN_CDSC (0x1 << 1) /* RW */
#define MSDC_INTEN_ACMDRDY (0x1 << 3) /* RW */
#define MSDC_INTEN_ACMDTMO (0x1 << 4) /* RW */
#define MSDC_INTEN_ACMDCRCERR (0x1 << 5) /* RW */
#define MSDC_INTEN_DMAQ_EMPTY (0x1 << 6) /* RW */
#define MSDC_INTEN_SDIOIRQ (0x1 << 7) /* RW */
#define MSDC_INTEN_CMDRDY (0x1 << 8) /* RW */
#define MSDC_INTEN_CMDTMO (0x1 << 9) /* RW */
#define MSDC_INTEN_RSPCRCERR (0x1 << 10) /* RW */
#define MSDC_INTEN_CSTA (0x1 << 11) /* RW */
#define MSDC_INTEN_XFER_COMPL (0x1 << 12) /* RW */
#define MSDC_INTEN_DXFER_DONE (0x1 << 13) /* RW */
#define MSDC_INTEN_DATTMO (0x1 << 14) /* RW */
#define MSDC_INTEN_DATCRCERR (0x1 << 15) /* RW */
#define MSDC_INTEN_ACMD19_DONE (0x1 << 16) /* RW */
/* MSDC_FIFOCS mask */
#define MSDC_FIFOCS_RXCNT (0xff << 0) /* R */
#define MSDC_FIFOCS_TXCNT (0xff << 16) /* R */
#define MSDC_FIFOCS_CLR (0x1UL << 31) /* RW */
/* SDC_CFG mask */
#define SDC_CFG_SDIOINTWKUP (0x1 << 0) /* RW */
#define SDC_CFG_INSWKUP (0x1 << 1) /* RW */
#define SDC_CFG_BUSWIDTH (0x3 << 16) /* RW */
#define SDC_CFG_SDIO (0x1 << 19) /* RW */
#define SDC_CFG_SDIOIDE (0x1 << 20) /* RW */
#define SDC_CFG_INTATGAP (0x1 << 21) /* RW */
#define SDC_CFG_DTOC (0xffUL << 24) /* RW */
/* SDC_CMD mask */
#define SDC_CMD_OPC (0x3f << 0) /* RW */
#define SDC_CMD_BRK (0x1 << 6) /* RW */
#define SDC_CMD_RSPTYP (0x7 << 7) /* RW */
#define SDC_CMD_DTYP (0x3 << 11) /* RW */
#define SDC_CMD_DTYP (0x3 << 11) /* RW */
#define SDC_CMD_RW (0x1 << 13) /* RW */
#define SDC_CMD_STOP (0x1 << 14) /* RW */
#define SDC_CMD_GOIRQ (0x1 << 15) /* RW */
#define SDC_CMD_BLKLEN (0xfff << 16) /* RW */
#define SDC_CMD_AUTOCMD (0x3 << 28) /* RW */
#define SDC_CMD_VOLSWTH (0x1 << 30) /* RW */
/* SDC_STS mask */
#define SDC_STS_SDCBUSY (0x1 << 0) /* RW */
#define SDC_STS_CMDBUSY (0x1 << 1) /* RW */
#define SDC_STS_SWR_COMPL (0x1 << 31) /* RW */
/* SDC_DCRC_STS mask */
#define SDC_DCRC_STS_NEG (0xf << 8) /* RO */
#define SDC_DCRC_STS_POS (0xff << 0) /* RO */
/* EMMC_CFG0 mask */
#define EMMC_CFG0_BOOTSTART (0x1 << 0) /* W */
#define EMMC_CFG0_BOOTSTOP (0x1 << 1) /* W */
#define EMMC_CFG0_BOOTMODE (0x1 << 2) /* RW */
#define EMMC_CFG0_BOOTACKDIS (0x1 << 3) /* RW */
#define EMMC_CFG0_BOOTWDLY (0x7 << 12) /* RW */
#define EMMC_CFG0_BOOTSUPP (0x1 << 15) /* RW */
/* EMMC_CFG1 mask */
#define EMMC_CFG1_BOOTDATTMC (0xfffff << 0) /* RW */
#define EMMC_CFG1_BOOTACKTMC (0xfffUL << 20) /* RW */
/* EMMC_STS mask */
#define EMMC_STS_BOOTCRCERR (0x1 << 0) /* W1C */
#define EMMC_STS_BOOTACKERR (0x1 << 1) /* W1C */
#define EMMC_STS_BOOTDATTMO (0x1 << 2) /* W1C */
#define EMMC_STS_BOOTACKTMO (0x1 << 3) /* W1C */
#define EMMC_STS_BOOTUPSTATE (0x1 << 4) /* R */
#define EMMC_STS_BOOTACKRCV (0x1 << 5) /* W1C */
#define EMMC_STS_BOOTDATRCV (0x1 << 6) /* R */
/* EMMC_IOCON mask */
#define EMMC_IOCON_BOOTRST (0x1 << 0) /* RW */
/* SDC_ACMD19_TRG mask */
#define SDC_ACMD19_TRG_TUNESEL (0xf << 0) /* RW */
/* MSDC_DMA_CTRL mask */
#define MSDC_DMA_CTRL_START (0x1 << 0) /* W */
#define MSDC_DMA_CTRL_STOP (0x1 << 1) /* W */
#define MSDC_DMA_CTRL_RESUME (0x1 << 2) /* W */
#define MSDC_DMA_CTRL_MODE (0x1 << 8) /* RW */
#define MSDC_DMA_CTRL_LASTBUF (0x1 << 10) /* RW */
#define MSDC_DMA_CTRL_BURSTSZ (0x7 << 12) /* RW */
#define MSDC_DMA_CTRL_XFERSZ (0xffffUL << 16)/* RW */
/* MSDC_DMA_CFG mask */
#define MSDC_DMA_CFG_STS (0x1 << 0) /* R */
#define MSDC_DMA_CFG_DECSEN (0x1 << 1) /* RW */
#define MSDC_DMA_CFG_BDCSERR (0x1 << 4) /* R */
#define MSDC_DMA_CFG_GPDCSERR (0x1 << 5) /* R */
/* MSDC_PATCH_BIT mask */
#define MSDC_PATCH_BIT_WFLSMODE (0x1 << 0) /* RW */
#define MSDC_PATCH_BIT_ODDSUPP (0x1 << 1) /* RW */
#define MSDC_PATCH_BIT_CKGEN_CK (0x1 << 6) /* E2: Fixed to 1 */
#define MSDC_PATCH_BIT_IODSSEL (0x1 << 16) /* RW */
#define MSDC_PATCH_BIT_IOINTSEL (0x1 << 17) /* RW */
#define MSDC_PATCH_BIT_BUSYDLY (0xf << 18) /* RW */
#define MSDC_PATCH_BIT_WDOD (0xf << 22) /* RW */
#define MSDC_PATCH_BIT_IDRTSEL (0x1 << 26) /* RW */
#define MSDC_PATCH_BIT_CMDFSEL (0x1 << 27) /* RW */
#define MSDC_PATCH_BIT_INTDLSEL (0x1 << 28) /* RW */
#define MSDC_PATCH_BIT_SPCPUSH (0x1 << 29) /* RW */
#define MSDC_PATCH_BIT_DECRCTMO (0x1 << 30) /* RW */
/* MSDC_PATCH_BIT1 mask */
#define MSDC_PATCH_BIT1_WRDAT_CRCS (0x7 << 3)
#define MSDC_PATCH_BIT1_CMD_RSP (0x7 << 0)
/* MSDC_PAD_CTL0 mask */
#define MSDC_PAD_CTL0_CLKDRVN (0x7 << 0) /* RW */
#define MSDC_PAD_CTL0_CLKDRVP (0x7 << 4) /* RW */
#define MSDC_PAD_CTL0_CLKSR (0x1 << 8) /* RW */
#define MSDC_PAD_CTL0_CLKPD (0x1 << 16) /* RW */
#define MSDC_PAD_CTL0_CLKPU (0x1 << 17) /* RW */
#define MSDC_PAD_CTL0_CLKSMT (0x1 << 18) /* RW */
#define MSDC_PAD_CTL0_CLKIES (0x1 << 19) /* RW */
#define MSDC_PAD_CTL0_CLKTDSEL (0xf << 20) /* RW */
#define MSDC_PAD_CTL0_CLKRDSEL (0xffUL << 24) /* RW */
/* MSDC_PAD_CTL1 mask */
#define MSDC_PAD_CTL1_CMDDRVN (0x7 << 0) /* RW */
#define MSDC_PAD_CTL1_CMDDRVP (0x7 << 4) /* RW */
#define MSDC_PAD_CTL1_CMDSR (0x1 << 8) /* RW */
#define MSDC_PAD_CTL1_CMDPD (0x1 << 16) /* RW */
#define MSDC_PAD_CTL1_CMDPU (0x1 << 17) /* RW */
#define MSDC_PAD_CTL1_CMDSMT (0x1 << 18) /* RW */
#define MSDC_PAD_CTL1_CMDIES (0x1 << 19) /* RW */
#define MSDC_PAD_CTL1_CMDTDSEL (0xf << 20) /* RW */
#define MSDC_PAD_CTL1_CMDRDSEL (0xffUL << 24) /* RW */
/* MSDC_PAD_CTL2 mask */
#define MSDC_PAD_CTL2_DATDRVN (0x7 << 0) /* RW */
#define MSDC_PAD_CTL2_DATDRVP (0x7 << 4) /* RW */
#define MSDC_PAD_CTL2_DATSR (0x1 << 8) /* RW */
#define MSDC_PAD_CTL2_DATPD (0x1 << 16) /* RW */
#define MSDC_PAD_CTL2_DATPU (0x1 << 17) /* RW */
#define MSDC_PAD_CTL2_DATIES (0x1 << 19) /* RW */
#define MSDC_PAD_CTL2_DATSMT (0x1 << 18) /* RW */
#define MSDC_PAD_CTL2_DATTDSEL (0xf << 20) /* RW */
#define MSDC_PAD_CTL2_DATRDSEL (0xffUL << 24) /* RW */
/* MSDC_PAD_TUNE mask */
#define MSDC_PAD_TUNE_DATWRDLY (0x1F << 0) /* RW */
#define MSDC_PAD_TUNE_DATRRDLY (0x1F << 8) /* RW */
#define MSDC_PAD_TUNE_CMDRDLY (0x1F << 16) /* RW */
#define MSDC_PAD_TUNE_CMDRRDLY (0x1FUL << 22) /* RW */
#define MSDC_PAD_TUNE_CLKTXDLY (0x1FUL << 27) /* RW */
/* MSDC_DAT_RDDLY0/1 mask */
#define MSDC_DAT_RDDLY0_D0 (0x1F << 0) /* RW */
#define MSDC_DAT_RDDLY0_D1 (0x1F << 8) /* RW */
#define MSDC_DAT_RDDLY0_D2 (0x1F << 16) /* RW */
#define MSDC_DAT_RDDLY0_D3 (0x1F << 24) /* RW */
#define MSDC_DAT_RDDLY1_D4 (0x1F << 0) /* RW */
#define MSDC_DAT_RDDLY1_D5 (0x1F << 8) /* RW */
#define MSDC_DAT_RDDLY1_D6 (0x1F << 16) /* RW */
#define MSDC_DAT_RDDLY1_D7 (0x1F << 24) /* RW */
#define MSDC_CKGEN_MSDC_DLY_SEL (0x1F << 10)
#define MSDC_INT_DAT_LATCH_CK_SEL (0x7 << 7)
#define MSDC_CKGEN_MSDC_CK_SEL (0x1 << 6)
#define CARD_READY_FOR_DATA BIT(8)
#define CARD_CURRENT_STATE(x) ((x & 0x00001E00) >> 9)
/*--------------------------------------------------------------------------*/
/* Descriptor Structure */
/*--------------------------------------------------------------------------*/
struct gpd {
u32 hwo:1; /* could be changed by hw */
u32 bdp:1;
u32 rsv0:6;
u32 chksum:8;
u32 intr:1;
u32 rsv1:15;
void *next;
void *ptr;
u32 buflen:16;
u32 extlen:8;
u32 rsv2:8;
u32 arg;
u32 blknum;
u32 cmd;
};
struct bd {
u32 eol:1;
u32 rsv0:7;
u32 chksum:8;
u32 rsv1:1;
u32 blkpad:1;
u32 dwpad:1;
u32 rsv2:13;
void *next;
void *ptr;
u32 buflen:16;
u32 rsv3:16;
};
struct msdc_dma {
struct gpd *gpd; /* pointer to gpd array */
struct bd *bd; /* pointer to bd array */
dma_addr_t gpd_addr; /* the physical address of gpd array */
dma_addr_t bd_addr; /* the physical address of bd array */
};
struct msdc_host {
struct msdc_hw *hw;
struct mmc_host *mmc; /* mmc structure */
struct mmc_command *cmd;
struct mmc_data *data;
struct mmc_request *mrq;
int cmd_rsp;
int error;
spinlock_t lock; /* mutex */
u32 blksz; /* host block size */
void __iomem *base; /* host base address */
int id; /* host id */
int pwr_ref; /* core power reference count */
u32 xfer_size; /* total transferred size */
struct msdc_dma dma; /* dma channel */
u32 dma_xfer_size; /* dma transfer size in bytes */
u32 timeout_ns; /* data timeout ns */
u32 timeout_clks; /* data timeout clks */
int irq; /* host interrupt */
struct delayed_work card_delaywork;
struct completion cmd_done;
struct completion xfer_done;
struct pm_message pm_state;
u32 mclk; /* mmc subsystem clock */
u32 hclk; /* host clock speed */
u32 sclk; /* SD/MS clock speed */
u8 core_clkon; /* Host core clock on ? */
u8 card_clkon; /* Card clock on ? */
u8 core_power; /* core power */
u8 power_mode; /* host power mode */
u8 card_inserted; /* card inserted ? */
u8 suspend; /* host suspended ? */
u8 app_cmd; /* for app command */
u32 app_cmd_arg;
};
static inline void sdr_set_bits(void __iomem *reg, u32 bs)
{
u32 val = readl(reg);
val |= bs;
writel(val, reg);
}
static inline void sdr_clr_bits(void __iomem *reg, u32 bs)
{
u32 val = readl(reg);
val &= ~bs;
writel(val, reg);
}
static inline void sdr_set_field(void __iomem *reg, u32 field, u32 val)
{
unsigned int tv = readl(reg);
tv &= ~field;
tv |= ((val) << (ffs((unsigned int)field) - 1));
writel(tv, reg);
}
static inline void sdr_get_field(void __iomem *reg, u32 field, u32 *val)
{
unsigned int tv = readl(reg);
*val = ((tv & field) >> (ffs((unsigned int)field) - 1));
}
#endif
/* Copyright Statement:
*
* This software/firmware and related documentation ("MediaTek Software") are
* protected under relevant copyright laws. The information contained herein
* is confidential and proprietary to MediaTek Inc. and/or its licensors.
* Without the prior written permission of MediaTek inc. and/or its licensors,
* any reproduction, modification, use or disclosure of MediaTek Software,
* and information contained herein, in whole or in part, shall be strictly prohibited.
*
* MediaTek Inc. (C) 2010. All rights reserved.
*
* BY OPENING THIS FILE, RECEIVER HEREBY UNEQUIVOCALLY ACKNOWLEDGES AND AGREES
* THAT THE SOFTWARE/FIRMWARE AND ITS DOCUMENTATIONS ("MEDIATEK SOFTWARE")
* RECEIVED FROM MEDIATEK AND/OR ITS REPRESENTATIVES ARE PROVIDED TO RECEIVER ON
* AN "AS-IS" BASIS ONLY. MEDIATEK EXPRESSLY DISCLAIMS ANY AND ALL WARRANTIES,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT.
* NEITHER DOES MEDIATEK PROVIDE ANY WARRANTY WHATSOEVER WITH RESPECT TO THE
* SOFTWARE OF ANY THIRD PARTY WHICH MAY BE USED BY, INCORPORATED IN, OR
* SUPPLIED WITH THE MEDIATEK SOFTWARE, AND RECEIVER AGREES TO LOOK ONLY TO SUCH
* THIRD PARTY FOR ANY WARRANTY CLAIM RELATING THERETO. RECEIVER EXPRESSLY ACKNOWLEDGES
* THAT IT IS RECEIVER'S SOLE RESPONSIBILITY TO OBTAIN FROM ANY THIRD PARTY ALL PROPER LICENSES
* CONTAINED IN MEDIATEK SOFTWARE. MEDIATEK SHALL ALSO NOT BE RESPONSIBLE FOR ANY MEDIATEK
* SOFTWARE RELEASES MADE TO RECEIVER'S SPECIFICATION OR TO CONFORM TO A PARTICULAR
* STANDARD OR OPEN FORUM. RECEIVER'S SOLE AND EXCLUSIVE REMEDY AND MEDIATEK'S ENTIRE AND
* CUMULATIVE LIABILITY WITH RESPECT TO THE MEDIATEK SOFTWARE RELEASED HEREUNDER WILL BE,
* AT MEDIATEK'S OPTION, TO REVISE OR REPLACE THE MEDIATEK SOFTWARE AT ISSUE,
* OR REFUND ANY SOFTWARE LICENSE FEES OR SERVICE CHARGE PAID BY RECEIVER TO
* MEDIATEK FOR SUCH MEDIATEK SOFTWARE AT ISSUE.
*
* The following software/firmware and/or related documentation ("MediaTek Software")
* have been modified by MediaTek Inc. All revisions are subject to any receiver's
* applicable license agreements with MediaTek Inc.
*/
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/spinlock.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/mmc/host.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/sd.h>
#include <linux/mmc/sdio.h>
#include <asm/mach-ralink/ralink_regs.h>
#include "board.h"
#include "dbg.h"
#include "mt6575_sd.h"
#ifdef CONFIG_SOC_MT7621
#define RALINK_SYSCTL_BASE 0xbe000000
#else
#define RALINK_SYSCTL_BASE 0xb0000000
#endif
#define DRV_NAME "mtk-sd"
#if defined(CONFIG_SOC_MT7620)
#define HOST_MAX_MCLK (48000000) /* +/- by chhung */
#elif defined(CONFIG_SOC_MT7621)
#define HOST_MAX_MCLK (50000000) /* +/- by chhung */
#endif
#define HOST_MIN_MCLK (260000)
#define HOST_MAX_BLKSZ (2048)
#define MSDC_OCR_AVAIL (MMC_VDD_28_29 | MMC_VDD_29_30 | MMC_VDD_30_31 | MMC_VDD_31_32 | MMC_VDD_32_33)
#define GPIO_PULL_DOWN (0)
#define GPIO_PULL_UP (1)
#define DEFAULT_DEBOUNCE (8) /* 8 cycles */
#define DEFAULT_DTOC (40) /* data timeout counter. 65536x40 sclk. */
#define CMD_TIMEOUT (HZ / 10) /* 100ms */
#define DAT_TIMEOUT (HZ / 2 * 5) /* 500ms x5 */
#define MAX_DMA_CNT (64 * 1024 - 512) /* a single transaction for WIFI may be 50K*/
#define MAX_GPD_NUM (1 + 1) /* one null gpd */
#define MAX_BD_NUM (1024)
#define MAX_HW_SGMTS (MAX_BD_NUM)
#define MAX_SGMT_SZ (MAX_DMA_CNT)
#define MAX_REQ_SZ (MAX_SGMT_SZ * 8)
static int cd_active_low = 1;
//=================================
#define PERI_MSDC0_PDN (15)
//#define PERI_MSDC1_PDN (16)
//#define PERI_MSDC2_PDN (17)
//#define PERI_MSDC3_PDN (18)
/* +++ by chhung */
struct msdc_hw msdc0_hw = {
.clk_src = 0,
.flags = MSDC_CD_PIN_EN | MSDC_REMOVABLE,
};
/* end of +++ */
static int msdc_rsp[] = {
0, /* RESP_NONE */
1, /* RESP_R1 */
2, /* RESP_R2 */
3, /* RESP_R3 */
4, /* RESP_R4 */
1, /* RESP_R5 */
1, /* RESP_R6 */
1, /* RESP_R7 */
7, /* RESP_R1b */
};
#define msdc_dma_on() sdr_clr_bits(host->base + MSDC_CFG, MSDC_CFG_PIO)
static void msdc_reset_hw(struct msdc_host *host)
{
sdr_set_bits(host->base + MSDC_CFG, MSDC_CFG_RST);
while (readl(host->base + MSDC_CFG) & MSDC_CFG_RST)
cpu_relax();
}
#define msdc_clr_int() \
do { \
volatile u32 val = readl(host->base + MSDC_INT); \
writel(val, host->base + MSDC_INT); \
} while (0)
static void msdc_clr_fifo(struct msdc_host *host)
{
sdr_set_bits(host->base + MSDC_FIFOCS, MSDC_FIFOCS_CLR);
while (readl(host->base + MSDC_FIFOCS) & MSDC_FIFOCS_CLR)
cpu_relax();
}
#define msdc_irq_save(val) \
do { \
val = readl(host->base + MSDC_INTEN); \
sdr_clr_bits(host->base + MSDC_INTEN, val); \
} while (0)
/* clock source for host: global */
#if defined(CONFIG_SOC_MT7620)
static u32 hclks[] = {48000000}; /* +/- by chhung */
#elif defined(CONFIG_SOC_MT7621)
static u32 hclks[] = {50000000}; /* +/- by chhung */
#endif
#define sdc_is_busy() (readl(host->base + SDC_STS) & SDC_STS_SDCBUSY)
#define sdc_is_cmd_busy() (readl(host->base + SDC_STS) & SDC_STS_CMDBUSY)
#define sdc_send_cmd(cmd, arg) \
do { \
writel((arg), host->base + SDC_ARG); \
writel((cmd), host->base + SDC_CMD); \
} while (0)
/* +++ by chhung */
#ifndef __ASSEMBLY__
#define PHYSADDR(a) (((unsigned long)(a)) & 0x1fffffff)
#else
#define PHYSADDR(a) ((a) & 0x1fffffff)
#endif
/* end of +++ */
static int msdc_do_command(struct msdc_host *host,
struct mmc_command *cmd,
int tune,
unsigned long timeout);
static int msdc_tune_cmdrsp(struct msdc_host *host, struct mmc_command *cmd);
static void msdc_set_timeout(struct msdc_host *host, u32 ns, u32 clks)
{
u32 timeout, clk_ns;
host->timeout_ns = ns;
host->timeout_clks = clks;
clk_ns = 1000000000UL / host->sclk;
timeout = ns / clk_ns + clks;
timeout = timeout >> 16; /* in 65536 sclk cycle unit */
timeout = timeout > 1 ? timeout - 1 : 0;
timeout = timeout > 255 ? 255 : timeout;
sdr_set_field(host->base + SDC_CFG, SDC_CFG_DTOC, timeout);
}
static void msdc_tasklet_card(struct work_struct *work)
{
struct msdc_host *host = (struct msdc_host *)container_of(work,
struct msdc_host, card_delaywork.work);
u32 inserted;
u32 status = 0;
spin_lock(&host->lock);
status = readl(host->base + MSDC_PS);
if (cd_active_low)
inserted = (status & MSDC_PS_CDSTS) ? 0 : 1;
else
inserted = (status & MSDC_PS_CDSTS) ? 1 : 0;
/* Make sure: handle the last interrupt */
host->card_inserted = inserted;
if (!host->suspend) {
mmc_detect_change(host->mmc, msecs_to_jiffies(20));
}
spin_unlock(&host->lock);
}
static void msdc_set_mclk(struct msdc_host *host, int ddr, unsigned int hz)
{
//struct msdc_hw *hw = host->hw;
u32 mode;
u32 flags;
u32 div;
u32 sclk;
u32 hclk = host->hclk;
//u8 clksrc = hw->clk_src;
if (!hz) { // set mmc system clock to 0 ?
msdc_reset_hw(host);
return;
}
msdc_irq_save(flags);
if (ddr) {
mode = 0x2; /* ddr mode and use divisor */
if (hz >= (hclk >> 2)) {
div = 1; /* mean div = 1/4 */
sclk = hclk >> 2; /* sclk = clk / 4 */
} else {
div = (hclk + ((hz << 2) - 1)) / (hz << 2);
sclk = (hclk >> 2) / div;
}
} else if (hz >= hclk) { /* bug fix */
mode = 0x1; /* no divisor and divisor is ignored */
div = 0;
sclk = hclk;
} else {
mode = 0x0; /* use divisor */
if (hz >= (hclk >> 1)) {
div = 0; /* mean div = 1/2 */
sclk = hclk >> 1; /* sclk = clk / 2 */
} else {
div = (hclk + ((hz << 2) - 1)) / (hz << 2);
sclk = (hclk >> 2) / div;
}
}
/* set clock mode and divisor */
sdr_set_field(host->base + MSDC_CFG, MSDC_CFG_CKMOD, mode);
sdr_set_field(host->base + MSDC_CFG, MSDC_CFG_CKDIV, div);
/* wait clock stable */
while (!(readl(host->base + MSDC_CFG) & MSDC_CFG_CKSTB))
cpu_relax();
host->sclk = sclk;
host->mclk = hz;
msdc_set_timeout(host, host->timeout_ns, host->timeout_clks); // need?
sdr_set_bits(host->base + MSDC_INTEN, flags);
}
/* Fix me. when need to abort */
static void msdc_abort_data(struct msdc_host *host)
{
struct mmc_command *stop = host->mrq->stop;
dev_err(mmc_dev(host->mmc), "%d -> Need to Abort.\n", host->id);
msdc_reset_hw(host);
msdc_clr_fifo(host);
msdc_clr_int();
// need to check FIFO count 0 ?
if (stop) { /* try to stop, but may not success */
dev_err(mmc_dev(host->mmc), "%d -> stop when abort CMD<%d>\n",
host->id, stop->opcode);
(void)msdc_do_command(host, stop, 0, CMD_TIMEOUT);
}
//if (host->mclk >= 25000000) {
// msdc_set_mclk(host, 0, host->mclk >> 1);
//}
}
#ifdef CONFIG_PM
/*
* register as callback function of WIFI(combo_sdio_register_pm) .
* can called by msdc_drv_suspend/resume too.
*/
static void msdc_pm(pm_message_t state, void *data)
{
struct msdc_host *host = (struct msdc_host *)data;
int evt = state.event;
if (evt == PM_EVENT_SUSPEND || evt == PM_EVENT_USER_SUSPEND) {
if (host->suspend) /* already suspend */ /* default 0*/
return;
/* for memory card. already power off by mmc */
if (evt == PM_EVENT_SUSPEND && host->power_mode == MMC_POWER_OFF)
return;
host->suspend = 1;
host->pm_state = state; /* default PMSG_RESUME */
} else if (evt == PM_EVENT_RESUME || evt == PM_EVENT_USER_RESUME) {
if (!host->suspend)
return;
/* No PM resume when USR suspend */
if (evt == PM_EVENT_RESUME && host->pm_state.event == PM_EVENT_USER_SUSPEND) {
dev_err(mmc_dev(host->mmc),
"%d -> PM Resume when in USR Suspend\n",
host->id); /* won't happen. */
return;
}
host->suspend = 0;
host->pm_state = state;
}
}
#endif
static inline u32 msdc_cmd_find_resp(struct mmc_command *cmd)
{
u32 opcode = cmd->opcode;
u32 resp;
if (opcode == MMC_SET_RELATIVE_ADDR) {
resp = (mmc_cmd_type(cmd) == MMC_CMD_BCR) ? RESP_R6 : RESP_R1;
} else if (opcode == MMC_FAST_IO) {
resp = RESP_R4;
} else if (opcode == MMC_GO_IRQ_STATE) {
resp = RESP_R5;
} else if (opcode == MMC_SELECT_CARD) {
resp = (cmd->arg != 0) ? RESP_R1B : RESP_NONE;
} else if (opcode == SD_IO_RW_DIRECT || opcode == SD_IO_RW_EXTENDED) {
resp = RESP_R1; /* SDIO workaround. */
} else if (opcode == SD_SEND_IF_COND && (mmc_cmd_type(cmd) == MMC_CMD_BCR)) {
resp = RESP_R1;
} else {
switch (mmc_resp_type(cmd)) {
case MMC_RSP_R1:
resp = RESP_R1;
break;
case MMC_RSP_R1B:
resp = RESP_R1B;
break;
case MMC_RSP_R2:
resp = RESP_R2;
break;
case MMC_RSP_R3:
resp = RESP_R3;
break;
case MMC_RSP_NONE:
default:
resp = RESP_NONE;
break;
}
}
return resp;
}
/*--------------------------------------------------------------------------*/
/* mmc_host_ops members */
/*--------------------------------------------------------------------------*/
static unsigned int msdc_command_start(struct msdc_host *host,
struct mmc_command *cmd,
unsigned long timeout)
{
u32 opcode = cmd->opcode;
u32 rawcmd;
u32 wints = MSDC_INT_CMDRDY | MSDC_INT_RSPCRCERR | MSDC_INT_CMDTMO |
MSDC_INT_ACMDRDY | MSDC_INT_ACMDCRCERR | MSDC_INT_ACMDTMO |
MSDC_INT_ACMD19_DONE;
u32 resp;
unsigned long tmo;
/* Protocol layer does not provide response type, but our hardware needs
* to know exact type, not just size!
*/
resp = msdc_cmd_find_resp(cmd);
cmd->error = 0;
/* rawcmd :
* vol_swt << 30 | auto_cmd << 28 | blklen << 16 | go_irq << 15 |
* stop << 14 | rw << 13 | dtype << 11 | rsptyp << 7 | brk << 6 | opcode
*/
rawcmd = opcode | msdc_rsp[resp] << 7 | host->blksz << 16;
if (opcode == MMC_READ_MULTIPLE_BLOCK) {
rawcmd |= (2 << 11);
} else if (opcode == MMC_READ_SINGLE_BLOCK) {
rawcmd |= (1 << 11);
} else if (opcode == MMC_WRITE_MULTIPLE_BLOCK) {
rawcmd |= ((2 << 11) | (1 << 13));
} else if (opcode == MMC_WRITE_BLOCK) {
rawcmd |= ((1 << 11) | (1 << 13));
} else if (opcode == SD_IO_RW_EXTENDED) {
if (cmd->data->flags & MMC_DATA_WRITE)
rawcmd |= (1 << 13);
if (cmd->data->blocks > 1)
rawcmd |= (2 << 11);
else
rawcmd |= (1 << 11);
} else if (opcode == SD_IO_RW_DIRECT && cmd->flags == (unsigned int)-1) {
rawcmd |= (1 << 14);
} else if ((opcode == SD_APP_SEND_SCR) ||
(opcode == SD_APP_SEND_NUM_WR_BLKS) ||
(opcode == SD_SWITCH && (mmc_cmd_type(cmd) == MMC_CMD_ADTC)) ||
(opcode == SD_APP_SD_STATUS && (mmc_cmd_type(cmd) == MMC_CMD_ADTC)) ||
(opcode == MMC_SEND_EXT_CSD && (mmc_cmd_type(cmd) == MMC_CMD_ADTC))) {
rawcmd |= (1 << 11);
} else if (opcode == MMC_STOP_TRANSMISSION) {
rawcmd |= (1 << 14);
rawcmd &= ~(0x0FFF << 16);
}
tmo = jiffies + timeout;
if (opcode == MMC_SEND_STATUS) {
for (;;) {
if (!sdc_is_cmd_busy())
break;
if (time_after(jiffies, tmo)) {
dev_err(mmc_dev(host->mmc),
"%d -> XXX cmd_busy timeout: before CMD<%d>\n",
host->id, opcode);
cmd->error = -ETIMEDOUT;
msdc_reset_hw(host);
goto end;
}
}
} else {
for (;;) {
if (!sdc_is_busy())
break;
if (time_after(jiffies, tmo)) {
dev_err(mmc_dev(host->mmc),
"%d -> XXX sdc_busy timeout: before CMD<%d>\n",
host->id, opcode);
cmd->error = -ETIMEDOUT;
msdc_reset_hw(host);
goto end;
}
}
}
//BUG_ON(in_interrupt());
host->cmd = cmd;
host->cmd_rsp = resp;
// The completion should have been consumed by the previous command
// response handler, because the mmc requests should be serialized
if (completion_done(&host->cmd_done))
dev_err(mmc_dev(host->mmc),
"previous command was not handled\n");
sdr_set_bits(host->base + MSDC_INTEN, wints);
sdc_send_cmd(rawcmd, cmd->arg);
end:
return cmd->error;
}
static unsigned int msdc_command_resp(struct msdc_host *host,
struct mmc_command *cmd,
int tune,
unsigned long timeout)
__must_hold(&host->lock)
{
u32 opcode = cmd->opcode;
//u32 rawcmd;
u32 wints = MSDC_INT_CMDRDY | MSDC_INT_RSPCRCERR | MSDC_INT_CMDTMO |
MSDC_INT_ACMDRDY | MSDC_INT_ACMDCRCERR | MSDC_INT_ACMDTMO |
MSDC_INT_ACMD19_DONE;
BUG_ON(in_interrupt());
//sdr_set_bits(host->base + MSDC_INTEN, wints);
spin_unlock(&host->lock);
if (!wait_for_completion_io_timeout(&host->cmd_done, 10 * timeout)) {
dev_err(mmc_dev(host->mmc),
"%d -> XXX CMD<%d> wait_for_completion timeout ARG<0x%.8x>\n",
host->id, opcode, cmd->arg);
cmd->error = -ETIMEDOUT;
msdc_reset_hw(host);
}
spin_lock(&host->lock);
sdr_clr_bits(host->base + MSDC_INTEN, wints);
host->cmd = NULL;
//end:
/* do we need to save card's RCA when SD_SEND_RELATIVE_ADDR */
if (!tune)
return cmd->error;
/* memory card CRC */
if (host->hw->flags & MSDC_REMOVABLE && cmd->error == -EIO) {
/* check if has data phase */
if (readl(host->base + SDC_CMD) & 0x1800) {
msdc_abort_data(host);
} else {
/* do basic: reset*/
msdc_reset_hw(host);
msdc_clr_fifo(host);
msdc_clr_int();
}
cmd->error = msdc_tune_cmdrsp(host, cmd);
}
// check DAT0
/* if (resp == RESP_R1B) {
while ((readl(host->base + MSDC_PS) & 0x10000) != 0x10000);
} */
/* CMD12 Error Handle */
return cmd->error;
}
static int msdc_do_command(struct msdc_host *host,
struct mmc_command *cmd,
int tune,
unsigned long timeout)
{
if (msdc_command_start(host, cmd, timeout))
goto end;
if (msdc_command_resp(host, cmd, tune, timeout))
goto end;
end:
return cmd->error;
}
static void msdc_dma_start(struct msdc_host *host)
{
u32 wints = MSDC_INTEN_XFER_COMPL | MSDC_INTEN_DATTMO | MSDC_INTEN_DATCRCERR;
sdr_set_bits(host->base + MSDC_INTEN, wints);
//dsb(); /* --- by chhung */
sdr_set_field(host->base + MSDC_DMA_CTRL, MSDC_DMA_CTRL_START, 1);
}
static void msdc_dma_stop(struct msdc_host *host)
{
//u32 retries=500;
u32 wints = MSDC_INTEN_XFER_COMPL | MSDC_INTEN_DATTMO | MSDC_INTEN_DATCRCERR;
//while (readl(host->base + MSDC_DMA_CFG) & MSDC_DMA_CFG_STS);
sdr_set_field(host->base + MSDC_DMA_CTRL, MSDC_DMA_CTRL_STOP, 1);
while (readl(host->base + MSDC_DMA_CFG) & MSDC_DMA_CFG_STS)
;
//dsb(); /* --- by chhung */
sdr_clr_bits(host->base + MSDC_INTEN, wints); /* Not just xfer_comp */
}
/* calc checksum */
static u8 msdc_dma_calcs(u8 *buf, u32 len)
{
u32 i, sum = 0;
for (i = 0; i < len; i++)
sum += buf[i];
return 0xFF - (u8)sum;
}
static void msdc_dma_setup(struct msdc_host *host, struct msdc_dma *dma,
struct scatterlist *sg_cmd, unsigned int sglen)
{
struct scatterlist *sg;
struct gpd *gpd;
struct bd *bd;
u32 j;
gpd = dma->gpd;
bd = dma->bd;
/* modify gpd*/
//gpd->intr = 0;
gpd->hwo = 1; /* hw will clear it */
gpd->bdp = 1;
gpd->chksum = 0; /* need to clear first. */
gpd->chksum = msdc_dma_calcs((u8 *)gpd, 16);
/* modify bd*/
for_each_sg(sg_cmd, sg, sglen, j) {
bd[j].blkpad = 0;
bd[j].dwpad = 0;
bd[j].ptr = (void *)sg_dma_address(sg);
bd[j].buflen = sg_dma_len(sg);
if (j == sglen - 1)
bd[j].eol = 1; /* the last bd */
else
bd[j].eol = 0;
bd[j].chksum = 0; /* checksume need to clear first */
bd[j].chksum = msdc_dma_calcs((u8 *)(&bd[j]), 16);
}
sdr_set_field(host->base + MSDC_DMA_CFG, MSDC_DMA_CFG_DECSEN, 1);
sdr_set_field(host->base + MSDC_DMA_CTRL, MSDC_DMA_CTRL_BURSTSZ,
MSDC_BURST_64B);
sdr_set_field(host->base + MSDC_DMA_CTRL, MSDC_DMA_CTRL_MODE, 1);
writel(PHYSADDR((u32)dma->gpd_addr), host->base + MSDC_DMA_SA);
}
static int msdc_do_request(struct mmc_host *mmc, struct mmc_request *mrq)
__must_hold(&host->lock)
{
struct msdc_host *host = mmc_priv(mmc);
struct mmc_command *cmd;
struct mmc_data *data;
//u32 intsts = 0;
int read = 1, send_type = 0;
#define SND_DAT 0
#define SND_CMD 1
BUG_ON(!mmc);
BUG_ON(!mrq);
host->error = 0;
cmd = mrq->cmd;
data = mrq->cmd->data;
if (!data) {
send_type = SND_CMD;
if (msdc_do_command(host, cmd, 1, CMD_TIMEOUT) != 0)
goto done;
} else {
BUG_ON(data->blksz > HOST_MAX_BLKSZ);
send_type = SND_DAT;
data->error = 0;
read = data->flags & MMC_DATA_READ ? 1 : 0;
host->data = data;
host->xfer_size = data->blocks * data->blksz;
host->blksz = data->blksz;
if (read) {
if ((host->timeout_ns != data->timeout_ns) ||
(host->timeout_clks != data->timeout_clks)) {
msdc_set_timeout(host, data->timeout_ns, data->timeout_clks);
}
}
writel(data->blocks, host->base + SDC_BLK_NUM);
//msdc_clr_fifo(host); /* no need */
msdc_dma_on(); /* enable DMA mode first!! */
// The completion should have been consumed by the previous
// xfer response handler, because the mmc requests should be
// serialized
if (completion_done(&host->cmd_done))
dev_err(mmc_dev(host->mmc),
"previous transfer was not handled\n");
/* start the command first*/
if (msdc_command_start(host, cmd, CMD_TIMEOUT) != 0)
goto done;
data->sg_count = dma_map_sg(mmc_dev(mmc), data->sg,
data->sg_len,
mmc_get_dma_dir(data));
if (data->sg_count == 0) {
dev_err(mmc_dev(host->mmc), "failed to map DMA for transfer\n");
data->error = -ENOMEM;
goto done;
}
msdc_dma_setup(host, &host->dma, data->sg,
data->sg_count);
/* then wait command done */
if (msdc_command_resp(host, cmd, 1, CMD_TIMEOUT) != 0)
goto unmap;
/* for read, the data coming too fast, then CRC error
* start DMA no business with CRC.
*/
msdc_dma_start(host);
spin_unlock(&host->lock);
if (!wait_for_completion_io_timeout(&host->xfer_done, DAT_TIMEOUT)) {
dev_err(mmc_dev(host->mmc),
"%d -> XXX CMD<%d> wait xfer_done<%d> timeout!!\n",
host->id, cmd->opcode,
data->blocks * data->blksz);
dev_err(mmc_dev(host->mmc),
"%d -> DMA_SA = 0x%x\n",
host->id, readl(host->base + MSDC_DMA_SA));
dev_err(mmc_dev(host->mmc),
"%d -> DMA_CA = 0x%x\n",
host->id, readl(host->base + MSDC_DMA_CA));
dev_err(mmc_dev(host->mmc),
"%d -> DMA_CTRL = 0x%x\n",
host->id, readl(host->base + MSDC_DMA_CTRL));
dev_err(mmc_dev(host->mmc),
"%d -> DMA_CFG = 0x%x\n",
host->id, readl(host->base + MSDC_DMA_CFG));
data->error = -ETIMEDOUT;
msdc_reset_hw(host);
msdc_clr_fifo(host);
msdc_clr_int();
}
spin_lock(&host->lock);
msdc_dma_stop(host);
/* Last: stop transfer */
if (data->stop) {
if (msdc_do_command(host, data->stop, 0, CMD_TIMEOUT) != 0)
goto unmap;
}
unmap:
host->data = NULL;
dma_unmap_sg(mmc_dev(mmc), data->sg, data->sg_len,
mmc_get_dma_dir(data));
host->blksz = 0;
}
done:
if (mrq->cmd->error)
host->error = 0x001;
if (mrq->data && mrq->data->error)
host->error |= 0x010;
if (mrq->stop && mrq->stop->error)
host->error |= 0x100;
return host->error;
}
static int msdc_app_cmd(struct mmc_host *mmc, struct msdc_host *host)
{
struct mmc_command cmd;
struct mmc_request mrq;
memset(&cmd, 0, sizeof(struct mmc_command));
cmd.opcode = MMC_APP_CMD;
cmd.arg = host->app_cmd_arg;
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_R1 | MMC_CMD_AC;
memset(&mrq, 0, sizeof(struct mmc_request));
mrq.cmd = &cmd; cmd.mrq = &mrq;
cmd.data = NULL;
return msdc_do_command(host, &cmd, 0, CMD_TIMEOUT);
}
static int msdc_tune_cmdrsp(struct msdc_host *host, struct mmc_command *cmd)
{
int result = -1;
u32 rsmpl, cur_rsmpl, orig_rsmpl;
u32 rrdly, cur_rrdly = 0xffffffff, orig_rrdly;
u32 skip = 1;
/* ==== don't support 3.0 now ====
* 1: R_SMPL[1]
* 2: PAD_CMD_RESP_RXDLY[26:22]
* ==========================
*/
// save the previous tune result
sdr_get_field(host->base + MSDC_IOCON, MSDC_IOCON_RSPL, &orig_rsmpl);
sdr_get_field(host->base + MSDC_PAD_TUNE, MSDC_PAD_TUNE_CMDRRDLY,
&orig_rrdly);
rrdly = 0;
do {
for (rsmpl = 0; rsmpl < 2; rsmpl++) {
/* Lv1: R_SMPL[1] */
cur_rsmpl = (orig_rsmpl + rsmpl) % 2;
if (skip == 1) {
skip = 0;
continue;
}
sdr_set_field(host->base + MSDC_IOCON, MSDC_IOCON_RSPL,
cur_rsmpl);
if (host->app_cmd) {
result = msdc_app_cmd(host->mmc, host);
if (result) {
dev_err(mmc_dev(host->mmc),
"%d -> TUNE_CMD app_cmd<%d> failed: RESP_RXDLY<%d>,R_SMPL<%d>\n",
host->id,
host->mrq->cmd->opcode,
cur_rrdly, cur_rsmpl);
continue;
}
}
result = msdc_do_command(host, cmd, 0, CMD_TIMEOUT); // not tune.
dev_err(mmc_dev(host->mmc),
"%d -> TUNE_CMD<%d> %s PAD_CMD_RESP_RXDLY[26:22]<%d> R_SMPL[1]<%d>\n",
host->id, cmd->opcode,
(result == 0) ? "PASS" : "FAIL", cur_rrdly,
cur_rsmpl);
if (result == 0)
return 0;
if (result != -EIO) {
dev_err(mmc_dev(host->mmc),
"%d -> TUNE_CMD<%d> Error<%d> not -EIO\n",
host->id, cmd->opcode, result);
return result;
}
/* should be EIO */
/* check if has data phase */
if (readl(host->base + SDC_CMD) & 0x1800)
msdc_abort_data(host);
}
/* Lv2: PAD_CMD_RESP_RXDLY[26:22] */
cur_rrdly = (orig_rrdly + rrdly + 1) % 32;
sdr_set_field(host->base + MSDC_PAD_TUNE,
MSDC_PAD_TUNE_CMDRRDLY, cur_rrdly);
} while (++rrdly < 32);
return result;
}
/* Support SD2.0 Only */
static int msdc_tune_bread(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct msdc_host *host = mmc_priv(mmc);
u32 ddr = 0;
u32 dcrc = 0;
u32 rxdly, cur_rxdly0, cur_rxdly1;
u32 dsmpl, cur_dsmpl, orig_dsmpl;
u32 cur_dat0, cur_dat1, cur_dat2, cur_dat3;
u32 cur_dat4, cur_dat5, cur_dat6, cur_dat7;
u32 orig_dat0, orig_dat1, orig_dat2, orig_dat3;
u32 orig_dat4, orig_dat5, orig_dat6, orig_dat7;
int result = -1;
u32 skip = 1;
sdr_get_field(host->base + MSDC_IOCON, MSDC_IOCON_DSPL, &orig_dsmpl);
/* Tune Method 2. */
sdr_set_field(host->base + MSDC_IOCON, MSDC_IOCON_DDLSEL, 1);
rxdly = 0;
do {
for (dsmpl = 0; dsmpl < 2; dsmpl++) {
cur_dsmpl = (orig_dsmpl + dsmpl) % 2;
if (skip == 1) {
skip = 0;
continue;
}
sdr_set_field(host->base + MSDC_IOCON, MSDC_IOCON_DSPL,
cur_dsmpl);
if (host->app_cmd) {
result = msdc_app_cmd(host->mmc, host);
if (result) {
dev_err(mmc_dev(host->mmc),
"%d -> TUNE_BREAD app_cmd<%d> failed\n",
host->id,
host->mrq->cmd->opcode);
continue;
}
}
result = msdc_do_request(mmc, mrq);
sdr_get_field(host->base + SDC_DCRC_STS,
SDC_DCRC_STS_POS | SDC_DCRC_STS_NEG,
&dcrc); /* RO */
if (!ddr)
dcrc &= ~SDC_DCRC_STS_NEG;
dev_err(mmc_dev(host->mmc),
"%d -> TUNE_BREAD<%s> dcrc<0x%x> DATRDDLY0/1<0x%x><0x%x> dsmpl<0x%x>\n",
host->id,
(result == 0 && dcrc == 0) ? "PASS" : "FAIL",
dcrc, readl(host->base + MSDC_DAT_RDDLY0),
readl(host->base + MSDC_DAT_RDDLY1),
cur_dsmpl);
/* Fix me: result is 0, but dcrc is still exist */
if (result == 0 && dcrc == 0) {
goto done;
} else {
/* there is a case: command timeout, and data phase not processed */
if (mrq->data->error != 0 &&
mrq->data->error != -EIO) {
dev_err(mmc_dev(host->mmc),
"%d -> TUNE_READ: result<0x%x> cmd_error<%d> data_error<%d>\n",
host->id, result,
mrq->cmd->error,
mrq->data->error);
goto done;
}
}
}
cur_rxdly0 = readl(host->base + MSDC_DAT_RDDLY0);
cur_rxdly1 = readl(host->base + MSDC_DAT_RDDLY1);
/* E1 ECO. YD: Reverse */
if (readl(host->base + MSDC_ECO_VER) >= 4) {
orig_dat0 = (cur_rxdly0 >> 24) & 0x1F;
orig_dat1 = (cur_rxdly0 >> 16) & 0x1F;
orig_dat2 = (cur_rxdly0 >> 8) & 0x1F;
orig_dat3 = (cur_rxdly0 >> 0) & 0x1F;
orig_dat4 = (cur_rxdly1 >> 24) & 0x1F;
orig_dat5 = (cur_rxdly1 >> 16) & 0x1F;
orig_dat6 = (cur_rxdly1 >> 8) & 0x1F;
orig_dat7 = (cur_rxdly1 >> 0) & 0x1F;
} else {
orig_dat0 = (cur_rxdly0 >> 0) & 0x1F;
orig_dat1 = (cur_rxdly0 >> 8) & 0x1F;
orig_dat2 = (cur_rxdly0 >> 16) & 0x1F;
orig_dat3 = (cur_rxdly0 >> 24) & 0x1F;
orig_dat4 = (cur_rxdly1 >> 0) & 0x1F;
orig_dat5 = (cur_rxdly1 >> 8) & 0x1F;
orig_dat6 = (cur_rxdly1 >> 16) & 0x1F;
orig_dat7 = (cur_rxdly1 >> 24) & 0x1F;
}
if (ddr) {
cur_dat0 = (dcrc & (1 << 0) || dcrc & (1 << 8)) ? ((orig_dat0 + 1) % 32) : orig_dat0;
cur_dat1 = (dcrc & (1 << 1) || dcrc & (1 << 9)) ? ((orig_dat1 + 1) % 32) : orig_dat1;
cur_dat2 = (dcrc & (1 << 2) || dcrc & (1 << 10)) ? ((orig_dat2 + 1) % 32) : orig_dat2;
cur_dat3 = (dcrc & (1 << 3) || dcrc & (1 << 11)) ? ((orig_dat3 + 1) % 32) : orig_dat3;
} else {
cur_dat0 = (dcrc & (1 << 0)) ? ((orig_dat0 + 1) % 32) : orig_dat0;
cur_dat1 = (dcrc & (1 << 1)) ? ((orig_dat1 + 1) % 32) : orig_dat1;
cur_dat2 = (dcrc & (1 << 2)) ? ((orig_dat2 + 1) % 32) : orig_dat2;
cur_dat3 = (dcrc & (1 << 3)) ? ((orig_dat3 + 1) % 32) : orig_dat3;
}
cur_dat4 = (dcrc & (1 << 4)) ? ((orig_dat4 + 1) % 32) : orig_dat4;
cur_dat5 = (dcrc & (1 << 5)) ? ((orig_dat5 + 1) % 32) : orig_dat5;
cur_dat6 = (dcrc & (1 << 6)) ? ((orig_dat6 + 1) % 32) : orig_dat6;
cur_dat7 = (dcrc & (1 << 7)) ? ((orig_dat7 + 1) % 32) : orig_dat7;
cur_rxdly0 = (cur_dat0 << 24) | (cur_dat1 << 16) | (cur_dat2 << 8) | (cur_dat3 << 0);
cur_rxdly1 = (cur_dat4 << 24) | (cur_dat5 << 16) | (cur_dat6 << 8) | (cur_dat7 << 0);
writel(cur_rxdly0, host->base + MSDC_DAT_RDDLY0);
writel(cur_rxdly1, host->base + MSDC_DAT_RDDLY1);
} while (++rxdly < 32);
done:
return result;
}
static int msdc_tune_bwrite(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct msdc_host *host = mmc_priv(mmc);
u32 wrrdly, cur_wrrdly = 0xffffffff, orig_wrrdly;
u32 dsmpl, cur_dsmpl, orig_dsmpl;
u32 rxdly, cur_rxdly0;
u32 orig_dat0, orig_dat1, orig_dat2, orig_dat3;
u32 cur_dat0, cur_dat1, cur_dat2, cur_dat3;
int result = -1;
u32 skip = 1;
// MSDC_IOCON_DDR50CKD need to check. [Fix me]
sdr_get_field(host->base + MSDC_PAD_TUNE, MSDC_PAD_TUNE_DATWRDLY,
&orig_wrrdly);
sdr_get_field(host->base + MSDC_IOCON, MSDC_IOCON_DSPL, &orig_dsmpl);
/* Tune Method 2. just DAT0 */
sdr_set_field(host->base + MSDC_IOCON, MSDC_IOCON_DDLSEL, 1);
cur_rxdly0 = readl(host->base + MSDC_DAT_RDDLY0);
/* E1 ECO. YD: Reverse */
if (readl(host->base + MSDC_ECO_VER) >= 4) {
orig_dat0 = (cur_rxdly0 >> 24) & 0x1F;
orig_dat1 = (cur_rxdly0 >> 16) & 0x1F;
orig_dat2 = (cur_rxdly0 >> 8) & 0x1F;
orig_dat3 = (cur_rxdly0 >> 0) & 0x1F;
} else {
orig_dat0 = (cur_rxdly0 >> 0) & 0x1F;
orig_dat1 = (cur_rxdly0 >> 8) & 0x1F;
orig_dat2 = (cur_rxdly0 >> 16) & 0x1F;
orig_dat3 = (cur_rxdly0 >> 24) & 0x1F;
}
rxdly = 0;
do {
wrrdly = 0;
do {
for (dsmpl = 0; dsmpl < 2; dsmpl++) {
cur_dsmpl = (orig_dsmpl + dsmpl) % 2;
if (skip == 1) {
skip = 0;
continue;
}
sdr_set_field(host->base + MSDC_IOCON,
MSDC_IOCON_DSPL, cur_dsmpl);
if (host->app_cmd) {
result = msdc_app_cmd(host->mmc, host);
if (result) {
dev_err(mmc_dev(host->mmc),
"%d -> TUNE_BWRITE app_cmd<%d> failed\n",
host->id,
host->mrq->cmd->opcode);
continue;
}
}
result = msdc_do_request(mmc, mrq);
dev_err(mmc_dev(host->mmc),
"%d -> TUNE_BWRITE<%s> DSPL<%d> DATWRDLY<%d> MSDC_DAT_RDDLY0<0x%x>\n",
host->id,
result == 0 ? "PASS" : "FAIL",
cur_dsmpl, cur_wrrdly, cur_rxdly0);
if (result == 0) {
goto done;
} else {
/* there is a case: command timeout, and data phase not processed */
if (mrq->data->error != -EIO) {
dev_err(mmc_dev(host->mmc),
"%d -> TUNE_READ: result<0x%x> cmd_error<%d> data_error<%d>\n",
host->id, result,
mrq->cmd->error,
mrq->data->error);
goto done;
}
}
}
cur_wrrdly = (orig_wrrdly + wrrdly + 1) % 32;
sdr_set_field(host->base + MSDC_PAD_TUNE,
MSDC_PAD_TUNE_DATWRDLY, cur_wrrdly);
} while (++wrrdly < 32);
cur_dat0 = (orig_dat0 + rxdly) % 32; /* only adjust bit-1 for crc */
cur_dat1 = orig_dat1;
cur_dat2 = orig_dat2;
cur_dat3 = orig_dat3;
cur_rxdly0 = (cur_dat0 << 24) | (cur_dat1 << 16) | (cur_dat2 << 8) | (cur_dat3 << 0);
writel(cur_rxdly0, host->base + MSDC_DAT_RDDLY0);
} while (++rxdly < 32);
done:
return result;
}
static int msdc_get_card_status(struct mmc_host *mmc, struct msdc_host *host, u32 *status)
{
struct mmc_command cmd;
struct mmc_request mrq;
u32 err;
memset(&cmd, 0, sizeof(struct mmc_command));
cmd.opcode = MMC_SEND_STATUS;
if (mmc->card) {
cmd.arg = mmc->card->rca << 16;
} else {
dev_err(mmc_dev(host->mmc), "%d -> cmd13 mmc card is null\n",
host->id);
cmd.arg = host->app_cmd_arg;
}
cmd.flags = MMC_RSP_SPI_R2 | MMC_RSP_R1 | MMC_CMD_AC;
memset(&mrq, 0, sizeof(struct mmc_request));
mrq.cmd = &cmd; cmd.mrq = &mrq;
cmd.data = NULL;
err = msdc_do_command(host, &cmd, 1, CMD_TIMEOUT);
if (status)
*status = cmd.resp[0];
return err;
}
static int msdc_check_busy(struct mmc_host *mmc, struct msdc_host *host)
{
u32 err = 0;
u32 status = 0;
do {
err = msdc_get_card_status(mmc, host, &status);
if (err)
return err;
/* need cmd12? */
dev_err(mmc_dev(host->mmc), "%d -> cmd<13> resp<0x%x>\n",
host->id, status);
} while (R1_CURRENT_STATE(status) == 7);
return err;
}
/* failed when msdc_do_request */
static int msdc_tune_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct msdc_host *host = mmc_priv(mmc);
struct mmc_data *data;
//u32 base = host->base;
int ret = 0, read;
data = mrq->cmd->data;
read = data->flags & MMC_DATA_READ ? 1 : 0;
if (read) {
if (data->error == -EIO)
ret = msdc_tune_bread(mmc, mrq);
} else {
ret = msdc_check_busy(mmc, host);
if (ret) {
dev_err(mmc_dev(host->mmc),
"%d -> XXX cmd13 wait program done failed\n",
host->id);
return ret;
}
/* CRC and TO */
/* Fix me: don't care card status? */
ret = msdc_tune_bwrite(mmc, mrq);
}
return ret;
}
/* ops.request */
static void msdc_ops_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct msdc_host *host = mmc_priv(mmc);
WARN_ON(host->mrq);
/* start to process */
spin_lock(&host->lock);
host->mrq = mrq;
if (msdc_do_request(mmc, mrq)) {
if (host->hw->flags & MSDC_REMOVABLE && ralink_soc == MT762X_SOC_MT7621AT && mrq->data && mrq->data->error)
msdc_tune_request(mmc, mrq);
}
/* ==== when request done, check if app_cmd ==== */
if (mrq->cmd->opcode == MMC_APP_CMD) {
host->app_cmd = 1;
host->app_cmd_arg = mrq->cmd->arg; /* save the RCA */
} else {
host->app_cmd = 0;
//host->app_cmd_arg = 0;
}
host->mrq = NULL;
spin_unlock(&host->lock);
mmc_request_done(mmc, mrq);
}
/* called by ops.set_ios */
static void msdc_set_buswidth(struct msdc_host *host, u32 width)
{
u32 val = readl(host->base + SDC_CFG);
val &= ~SDC_CFG_BUSWIDTH;
switch (width) {
default:
case MMC_BUS_WIDTH_1:
width = 1;
val |= (MSDC_BUS_1BITS << 16);
break;
case MMC_BUS_WIDTH_4:
val |= (MSDC_BUS_4BITS << 16);
break;
case MMC_BUS_WIDTH_8:
val |= (MSDC_BUS_8BITS << 16);
break;
}
writel(val, host->base + SDC_CFG);
}
/* ops.set_ios */
static void msdc_ops_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
{
struct msdc_host *host = mmc_priv(mmc);
u32 ddr = 0;
#ifdef MT6575_SD_DEBUG
static const char * const vdd[] = {
"1.50v", "1.55v", "1.60v", "1.65v", "1.70v", "1.80v", "1.90v",
"2.00v", "2.10v", "2.20v", "2.30v", "2.40v", "2.50v", "2.60v",
"2.70v", "2.80v", "2.90v", "3.00v", "3.10v", "3.20v", "3.30v",
"3.40v", "3.50v", "3.60v"
};
static const char * const power_mode[] = {
"OFF", "UP", "ON"
};
static const char * const bus_mode[] = {
"UNKNOWN", "OPENDRAIN", "PUSHPULL"
};
static const char * const timing[] = {
"LEGACY", "MMC_HS", "SD_HS"
};
printk("SET_IOS: CLK(%dkHz), BUS(%s), BW(%u), PWR(%s), VDD(%s), TIMING(%s)",
ios->clock / 1000, bus_mode[ios->bus_mode],
(ios->bus_width == MMC_BUS_WIDTH_4) ? 4 : 1,
power_mode[ios->power_mode], vdd[ios->vdd], timing[ios->timing]);
#endif
msdc_set_buswidth(host, ios->bus_width);
/* Power control ??? */
switch (ios->power_mode) {
case MMC_POWER_OFF:
case MMC_POWER_UP:
break;
case MMC_POWER_ON:
host->power_mode = MMC_POWER_ON;
break;
default:
break;
}
/* Clock control */
if (host->mclk != ios->clock) {
if (ios->clock > 25000000) {
//if (!(host->hw->flags & MSDC_REMOVABLE)) {
sdr_set_field(host->base + MSDC_IOCON, MSDC_IOCON_RSPL,
MSDC_SMPL_FALLING);
sdr_set_field(host->base + MSDC_IOCON, MSDC_IOCON_DSPL,
MSDC_SMPL_FALLING);
//} /* for tuning debug */
} else { /* default value */
writel(0x00000000, host->base + MSDC_IOCON);
// writel(0x00000000, host->base + MSDC_DAT_RDDLY0);
// for MT7620 E2 and afterward
writel(0x10101010, host->base + MSDC_DAT_RDDLY0);
writel(0x00000000, host->base + MSDC_DAT_RDDLY1);
// writel(0x00000000, host->base + MSDC_PAD_TUNE);
// for MT7620 E2 and afterward
writel(0x84101010, host->base + MSDC_PAD_TUNE);
}
msdc_set_mclk(host, ddr, ios->clock);
}
}
/* ops.get_ro */
static int msdc_ops_get_ro(struct mmc_host *mmc)
{
struct msdc_host *host = mmc_priv(mmc);
unsigned long flags;
int ro = 0;
if (host->hw->flags & MSDC_WP_PIN_EN) { /* set for card */
spin_lock_irqsave(&host->lock, flags);
ro = (readl(host->base + MSDC_PS) >> 31);
spin_unlock_irqrestore(&host->lock, flags);
}
return ro;
}
/* ops.get_cd */
static int msdc_ops_get_cd(struct mmc_host *mmc)
{
struct msdc_host *host = mmc_priv(mmc);
unsigned long flags;
int present = 1;
/* for sdio, MSDC_REMOVABLE not set, always return 1 */
if (!(host->hw->flags & MSDC_REMOVABLE)) {
/* For sdio, read H/W always get<1>, but may timeout some times */
#if 1
host->card_inserted = 1;
return 1;
#else
host->card_inserted = (host->pm_state.event == PM_EVENT_USER_RESUME) ? 1 : 0;
return host->card_inserted;
#endif
}
/* MSDC_CD_PIN_EN set for card */
if (host->hw->flags & MSDC_CD_PIN_EN) {
spin_lock_irqsave(&host->lock, flags);
// CD
present = readl(host->base + MSDC_PS) & MSDC_PS_CDSTS;
if (cd_active_low)
present = present ? 0 : 1;
else
present = present ? 1 : 0;
host->card_inserted = present;
spin_unlock_irqrestore(&host->lock, flags);
} else {
present = 0; /* TODO? Check DAT3 pins for card detection */
}
return present;
}
static struct mmc_host_ops mt_msdc_ops = {
.request = msdc_ops_request,
.set_ios = msdc_ops_set_ios,
.get_ro = msdc_ops_get_ro,
.get_cd = msdc_ops_get_cd,
};
/*--------------------------------------------------------------------------*/
/* interrupt handler */
/*--------------------------------------------------------------------------*/
static irqreturn_t msdc_irq(int irq, void *dev_id)
{
struct msdc_host *host = (struct msdc_host *)dev_id;
struct mmc_data *data = host->data;
struct mmc_command *cmd = host->cmd;
u32 cmdsts = MSDC_INT_RSPCRCERR | MSDC_INT_CMDTMO | MSDC_INT_CMDRDY |
MSDC_INT_ACMDCRCERR | MSDC_INT_ACMDTMO | MSDC_INT_ACMDRDY |
MSDC_INT_ACMD19_DONE;
u32 datsts = MSDC_INT_DATCRCERR | MSDC_INT_DATTMO;
u32 intsts = readl(host->base + MSDC_INT);
u32 inten = readl(host->base + MSDC_INTEN); inten &= intsts;
writel(intsts, host->base + MSDC_INT); /* clear interrupts */
/* MSG will cause fatal error */
/* card change interrupt */
if (intsts & MSDC_INT_CDSC) {
if (host->mmc->caps & MMC_CAP_NEEDS_POLL)
return IRQ_HANDLED;
schedule_delayed_work(&host->card_delaywork, 0);
/* tuning when plug card ? */
}
/* transfer complete interrupt */
if (data) {
if (inten & MSDC_INT_XFER_COMPL) {
data->bytes_xfered = host->xfer_size;
complete(&host->xfer_done);
}
if (intsts & datsts) {
/* do basic reset, or stop command will sdc_busy */
msdc_reset_hw(host);
msdc_clr_fifo(host);
msdc_clr_int();
if (intsts & MSDC_INT_DATTMO)
data->error = -ETIMEDOUT;
else if (intsts & MSDC_INT_DATCRCERR)
data->error = -EIO;
//if(readl(MSDC_INTEN) & MSDC_INT_XFER_COMPL) {
complete(&host->xfer_done); /* Read CRC come fast, XFER_COMPL not enabled */
}
}
/* command interrupts */
if (cmd && (intsts & cmdsts)) {
if ((intsts & MSDC_INT_CMDRDY) || (intsts & MSDC_INT_ACMDRDY) ||
(intsts & MSDC_INT_ACMD19_DONE)) {
u32 *rsp = &cmd->resp[0];
switch (host->cmd_rsp) {
case RESP_NONE:
break;
case RESP_R2:
*rsp++ = readl(host->base + SDC_RESP3);
*rsp++ = readl(host->base + SDC_RESP2);
*rsp++ = readl(host->base + SDC_RESP1);
*rsp++ = readl(host->base + SDC_RESP0);
break;
default: /* Response types 1, 3, 4, 5, 6, 7(1b) */
if ((intsts & MSDC_INT_ACMDRDY) || (intsts & MSDC_INT_ACMD19_DONE))
*rsp = readl(host->base + SDC_ACMD_RESP);
else
*rsp = readl(host->base + SDC_RESP0);
break;
}
} else if ((intsts & MSDC_INT_RSPCRCERR) || (intsts & MSDC_INT_ACMDCRCERR)) {
cmd->error = -EIO;
} else if ((intsts & MSDC_INT_CMDTMO) || (intsts & MSDC_INT_ACMDTMO)) {
cmd->error = -ETIMEDOUT;
msdc_reset_hw(host);
msdc_clr_fifo(host);
msdc_clr_int();
}
complete(&host->cmd_done);
}
/* mmc irq interrupts */
if (intsts & MSDC_INT_MMCIRQ)
dev_info(mmc_dev(host->mmc), "msdc[%d] MMCIRQ: SDC_CSTS=0x%.8x\r\n",
host->id, readl(host->base + SDC_CSTS));
return IRQ_HANDLED;
}
/*--------------------------------------------------------------------------*/
/* platform_driver members */
/*--------------------------------------------------------------------------*/
/* called by msdc_drv_probe/remove */
static void msdc_enable_cd_irq(struct msdc_host *host, int enable)
{
struct msdc_hw *hw = host->hw;
/* for sdio, not set */
if ((hw->flags & MSDC_CD_PIN_EN) == 0) {
/* Pull down card detection pin since it is not available */
/*
* if (hw->config_gpio_pin)
* hw->config_gpio_pin(MSDC_CD_PIN, GPIO_PULL_DOWN);
*/
sdr_clr_bits(host->base + MSDC_PS, MSDC_PS_CDEN);
sdr_clr_bits(host->base + MSDC_INTEN, MSDC_INTEN_CDSC);
sdr_clr_bits(host->base + SDC_CFG, SDC_CFG_INSWKUP);
return;
}
if (enable) {
/* card detection circuit relies on the core power so that the core power
* shouldn't be turned off. Here adds a reference count to keep
* the core power alive.
*/
if (hw->config_gpio_pin) /* NULL */
hw->config_gpio_pin(MSDC_CD_PIN, GPIO_PULL_UP);
sdr_set_field(host->base + MSDC_PS, MSDC_PS_CDDEBOUNCE,
DEFAULT_DEBOUNCE);
sdr_set_bits(host->base + MSDC_PS, MSDC_PS_CDEN);
sdr_set_bits(host->base + MSDC_INTEN, MSDC_INTEN_CDSC);
/* not in document! Fix me */
sdr_set_bits(host->base + SDC_CFG, SDC_CFG_INSWKUP);
} else {
if (hw->config_gpio_pin) /* NULL */
hw->config_gpio_pin(MSDC_CD_PIN, GPIO_PULL_DOWN);
sdr_clr_bits(host->base + SDC_CFG, SDC_CFG_INSWKUP);
sdr_clr_bits(host->base + MSDC_PS, MSDC_PS_CDEN);
sdr_clr_bits(host->base + MSDC_INTEN, MSDC_INTEN_CDSC);
/* Here decreases a reference count to core power since card
* detection circuit is shutdown.
*/
}
}
/* called by msdc_drv_probe */
static void msdc_init_hw(struct msdc_host *host)
{
/* Configure to MMC/SD mode */
sdr_set_field(host->base + MSDC_CFG, MSDC_CFG_MODE, MSDC_SDMMC);
/* Reset */
msdc_reset_hw(host);
msdc_clr_fifo(host);
/* Disable card detection */
sdr_clr_bits(host->base + MSDC_PS, MSDC_PS_CDEN);
/* Disable and clear all interrupts */
sdr_clr_bits(host->base + MSDC_INTEN, readl(host->base + MSDC_INTEN));
writel(readl(host->base + MSDC_INT), host->base + MSDC_INT);
#if 1
/* reset tuning parameter */
writel(0x00090000, host->base + MSDC_PAD_CTL0);
writel(0x000A0000, host->base + MSDC_PAD_CTL1);
writel(0x000A0000, host->base + MSDC_PAD_CTL2);
// writel( 0x00000000, host->base + MSDC_PAD_TUNE);
// for MT7620 E2 and afterward
writel(0x84101010, host->base + MSDC_PAD_TUNE);
// writel(0x00000000, host->base + MSDC_DAT_RDDLY0);
// for MT7620 E2 and afterward
writel(0x10101010, host->base + MSDC_DAT_RDDLY0);
writel(0x00000000, host->base + MSDC_DAT_RDDLY1);
writel(0x00000000, host->base + MSDC_IOCON);
if (readl(host->base + MSDC_ECO_VER) >= 4) {
if (host->id == 1) {
sdr_set_field(host->base + MSDC_PATCH_BIT1,
MSDC_PATCH_BIT1_WRDAT_CRCS, 1);
sdr_set_field(host->base + MSDC_PATCH_BIT1,
MSDC_PATCH_BIT1_CMD_RSP, 1);
/* internal clock: latch read data */
sdr_set_bits(host->base + MSDC_PATCH_BIT0,
MSDC_PATCH_BIT_CKGEN_CK);
}
}
#endif
/* for safety, should clear SDC_CFG.SDIO_INT_DET_EN & set SDC_CFG.SDIO in
* pre-loader,uboot,kernel drivers. and SDC_CFG.SDIO_INT_DET_EN will be only
* set when kernel driver wants to use SDIO bus interrupt
*/
/* Configure to enable SDIO mode. it's must otherwise sdio cmd5 failed */
sdr_set_bits(host->base + SDC_CFG, SDC_CFG_SDIO);
/* disable detect SDIO device interrupt function */
sdr_clr_bits(host->base + SDC_CFG, SDC_CFG_SDIOIDE);
/* eneable SMT for glitch filter */
sdr_set_bits(host->base + MSDC_PAD_CTL0, MSDC_PAD_CTL0_CLKSMT);
sdr_set_bits(host->base + MSDC_PAD_CTL1, MSDC_PAD_CTL1_CMDSMT);
sdr_set_bits(host->base + MSDC_PAD_CTL2, MSDC_PAD_CTL2_DATSMT);
#if 1
/* set clk, cmd, dat pad driving */
sdr_set_field(host->base + MSDC_PAD_CTL0, MSDC_PAD_CTL0_CLKDRVN, 4);
sdr_set_field(host->base + MSDC_PAD_CTL0, MSDC_PAD_CTL0_CLKDRVP, 4);
sdr_set_field(host->base + MSDC_PAD_CTL1, MSDC_PAD_CTL1_CMDDRVN, 4);
sdr_set_field(host->base + MSDC_PAD_CTL1, MSDC_PAD_CTL1_CMDDRVP, 4);
sdr_set_field(host->base + MSDC_PAD_CTL2, MSDC_PAD_CTL2_DATDRVN, 4);
sdr_set_field(host->base + MSDC_PAD_CTL2, MSDC_PAD_CTL2_DATDRVP, 4);
#else
sdr_set_field(host->base + MSDC_PAD_CTL0, MSDC_PAD_CTL0_CLKDRVN, 0);
sdr_set_field(host->base + MSDC_PAD_CTL0, MSDC_PAD_CTL0_CLKDRVP, 0);
sdr_set_field(host->base + MSDC_PAD_CTL1, MSDC_PAD_CTL1_CMDDRVN, 0);
sdr_set_field(host->base + MSDC_PAD_CTL1, MSDC_PAD_CTL1_CMDDRVP, 0);
sdr_set_field(host->base + MSDC_PAD_CTL2, MSDC_PAD_CTL2_DATDRVN, 0);
sdr_set_field(host->base + MSDC_PAD_CTL2, MSDC_PAD_CTL2_DATDRVP, 0);
#endif
/* set sampling edge */
/* write crc timeout detection */
sdr_set_field(host->base + MSDC_PATCH_BIT0, 1 << 30, 1);
/* Configure to default data timeout */
sdr_set_field(host->base + SDC_CFG, SDC_CFG_DTOC, DEFAULT_DTOC);
msdc_set_buswidth(host, MMC_BUS_WIDTH_1);
}
/* called by msdc_drv_remove */
static void msdc_deinit_hw(struct msdc_host *host)
{
/* Disable and clear all interrupts */
sdr_clr_bits(host->base + MSDC_INTEN, readl(host->base + MSDC_INTEN));
writel(readl(host->base + MSDC_INT), host->base + MSDC_INT);
/* Disable card detection */
msdc_enable_cd_irq(host, 0);
}
/* init gpd and bd list in msdc_drv_probe */
static void msdc_init_gpd_bd(struct msdc_host *host, struct msdc_dma *dma)
{
struct gpd *gpd = dma->gpd;
struct bd *bd = dma->bd;
int i;
/* we just support one gpd, but gpd->next must be set for desc
* DMA. That's why we alloc 2 gpd structurs.
*/
memset(gpd, 0, sizeof(struct gpd) * 2);
gpd->bdp = 1; /* hwo, cs, bd pointer */
gpd->ptr = (void *)dma->bd_addr; /* physical address */
gpd->next = (void *)((u32)dma->gpd_addr + sizeof(struct gpd));
memset(bd, 0, sizeof(struct bd) * MAX_BD_NUM);
for (i = 0; i < (MAX_BD_NUM - 1); i++)
bd[i].next = (void *)(dma->bd_addr + sizeof(*bd) * (i + 1));
}
static int msdc_drv_probe(struct platform_device *pdev)
{
struct resource *res;
__iomem void *base;
struct mmc_host *mmc;
struct msdc_host *host;
struct msdc_hw *hw;
int ret;
hw = &msdc0_hw;
if (of_property_read_bool(pdev->dev.of_node, "mtk,wp-en"))
msdc0_hw.flags |= MSDC_WP_PIN_EN;
/* Allocate MMC host for this device */
mmc = mmc_alloc_host(sizeof(struct msdc_host), &pdev->dev);
if (!mmc)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base)) {
ret = PTR_ERR(base);
goto host_free;
}
/* Set host parameters to mmc */
mmc->ops = &mt_msdc_ops;
mmc->f_min = HOST_MIN_MCLK;
mmc->f_max = HOST_MAX_MCLK;
mmc->ocr_avail = MSDC_OCR_AVAIL;
mmc->caps = MMC_CAP_MMC_HIGHSPEED | MMC_CAP_SD_HIGHSPEED;
//TODO: read this as bus-width from dt (via mmc_of_parse)
mmc->caps |= MMC_CAP_4_BIT_DATA;
cd_active_low = !of_property_read_bool(pdev->dev.of_node, "mediatek,cd-high");
if (of_property_read_bool(pdev->dev.of_node, "mediatek,cd-poll"))
mmc->caps |= MMC_CAP_NEEDS_POLL;
/* MMC core transfer sizes tunable parameters */
mmc->max_segs = MAX_HW_SGMTS;
mmc->max_seg_size = MAX_SGMT_SZ;
mmc->max_blk_size = HOST_MAX_BLKSZ;
mmc->max_req_size = MAX_REQ_SZ;
mmc->max_blk_count = mmc->max_req_size;
host = mmc_priv(mmc);
host->hw = hw;
host->mmc = mmc;
host->id = pdev->id;
if (host->id < 0 || host->id >= 4)
host->id = 0;
host->error = 0;
host->irq = platform_get_irq(pdev, 0);
if (host->irq < 0) {
ret = -EINVAL;
goto host_free;
}
host->base = base;
host->mclk = 0; /* mclk: the request clock of mmc sub-system */
host->hclk = hclks[hw->clk_src]; /* hclk: clock of clock source to msdc controller */
host->sclk = 0; /* sclk: the really clock after divition */
host->pm_state = PMSG_RESUME;
host->suspend = 0;
host->core_clkon = 0;
host->card_clkon = 0;
host->core_power = 0;
host->power_mode = MMC_POWER_OFF;
// host->card_inserted = hw->flags & MSDC_REMOVABLE ? 0 : 1;
host->timeout_ns = 0;
host->timeout_clks = DEFAULT_DTOC * 65536;
host->mrq = NULL;
dma_coerce_mask_and_coherent(mmc_dev(mmc), DMA_BIT_MASK(32));
/* using dma_alloc_coherent*/ /* todo: using 1, for all 4 slots */
host->dma.gpd = dma_alloc_coherent(&pdev->dev,
MAX_GPD_NUM * sizeof(struct gpd),
&host->dma.gpd_addr, GFP_KERNEL);
host->dma.bd = dma_alloc_coherent(&pdev->dev,
MAX_BD_NUM * sizeof(struct bd),
&host->dma.bd_addr, GFP_KERNEL);
if (!host->dma.gpd || !host->dma.bd) {
ret = -ENOMEM;
goto release_mem;
}
msdc_init_gpd_bd(host, &host->dma);
init_completion(&host->cmd_done);
init_completion(&host->xfer_done);
INIT_DELAYED_WORK(&host->card_delaywork, msdc_tasklet_card);
spin_lock_init(&host->lock);
msdc_init_hw(host);
/* TODO check weather flags 0 is correct, the mtk-sd driver uses
* IRQF_TRIGGER_LOW | IRQF_ONESHOT for flags
*
* for flags 0 the trigger polarity is determined by the
* device tree, but not the oneshot flag, but maybe it is also
* not needed because the soc could be oneshot safe.
*/
ret = devm_request_irq(&pdev->dev, host->irq, msdc_irq, 0, pdev->name,
host);
if (ret)
goto release;
platform_set_drvdata(pdev, mmc);
ret = mmc_add_host(mmc);
if (ret)
goto release;
/* Config card detection pin and enable interrupts */
if (hw->flags & MSDC_CD_PIN_EN) { /* set for card */
msdc_enable_cd_irq(host, 1);
} else {
msdc_enable_cd_irq(host, 0);
}
return 0;
release:
platform_set_drvdata(pdev, NULL);
msdc_deinit_hw(host);
cancel_delayed_work_sync(&host->card_delaywork);
release_mem:
if (host->dma.gpd)
dma_free_coherent(&pdev->dev, MAX_GPD_NUM * sizeof(struct gpd),
host->dma.gpd, host->dma.gpd_addr);
if (host->dma.bd)
dma_free_coherent(&pdev->dev, MAX_BD_NUM * sizeof(struct bd),
host->dma.bd, host->dma.bd_addr);
host_free:
mmc_free_host(mmc);
return ret;
}
/* 4 device share one driver, using "drvdata" to show difference */
static int msdc_drv_remove(struct platform_device *pdev)
{
struct mmc_host *mmc;
struct msdc_host *host;
mmc = platform_get_drvdata(pdev);
BUG_ON(!mmc);
host = mmc_priv(mmc);
BUG_ON(!host);
dev_err(mmc_dev(host->mmc), "%d -> removed !!!\n",
host->id);
platform_set_drvdata(pdev, NULL);
mmc_remove_host(host->mmc);
msdc_deinit_hw(host);
cancel_delayed_work_sync(&host->card_delaywork);
dma_free_coherent(&pdev->dev, MAX_GPD_NUM * sizeof(struct gpd),
host->dma.gpd, host->dma.gpd_addr);
dma_free_coherent(&pdev->dev, MAX_BD_NUM * sizeof(struct bd),
host->dma.bd, host->dma.bd_addr);
mmc_free_host(host->mmc);
return 0;
}
/* Fix me: Power Flow */
#ifdef CONFIG_PM
static void msdc_drv_pm(struct platform_device *pdev, pm_message_t state)
{
struct mmc_host *mmc = platform_get_drvdata(pdev);
if (mmc) {
struct msdc_host *host = mmc_priv(mmc);
msdc_pm(state, (void *)host);
}
}
static int msdc_drv_suspend(struct platform_device *pdev, pm_message_t state)
{
if (state.event == PM_EVENT_SUSPEND)
msdc_drv_pm(pdev, state);
return 0;
}
static int msdc_drv_resume(struct platform_device *pdev)
{
struct pm_message state;
state.event = PM_EVENT_RESUME;
msdc_drv_pm(pdev, state);
return 0;
}
#endif
static const struct of_device_id mt7620_sdhci_match[] = {
{ .compatible = "ralink,mt7620-sdhci" },
{},
};
MODULE_DEVICE_TABLE(of, mt7620_sdhci_match);
static struct platform_driver mt_msdc_driver = {
.probe = msdc_drv_probe,
.remove = msdc_drv_remove,
#ifdef CONFIG_PM
.suspend = msdc_drv_suspend,
.resume = msdc_drv_resume,
#endif
.driver = {
.name = DRV_NAME,
.of_match_table = mt7620_sdhci_match,
},
};
/*--------------------------------------------------------------------------*/
/* module init/exit */
/*--------------------------------------------------------------------------*/
static int __init mt_msdc_init(void)
{
int ret;
ret = platform_driver_register(&mt_msdc_driver);
if (ret) {
pr_err("%s: Can't register driver", DRV_NAME);
return ret;
}
msdc_debug_proc_init();
return 0;
}
static void __exit mt_msdc_exit(void)
{
msdc_debug_proc_deinit();
platform_driver_unregister(&mt_msdc_driver);
}
module_init(mt_msdc_init);
module_exit(mt_msdc_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("MediaTek MT6575 SD/MMC Card Driver");
MODULE_AUTHOR("Infinity Chen <infinity.chen@mediatek.com>");
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment