| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Exynos Specific Extensions for Synopsys DW Multimedia Card Interface driver | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (C) 2012, Samsung Electronics Co., Ltd. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software; you can redistribute it and/or modify | 
					
						
							|  |  |  |  * it under the terms of the GNU General Public License as published by | 
					
						
							|  |  |  |  * the Free Software Foundation; either version 2 of the License, or | 
					
						
							|  |  |  |  * (at your option) any later version. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/module.h>
 | 
					
						
							|  |  |  | #include <linux/platform_device.h>
 | 
					
						
							|  |  |  | #include <linux/clk.h>
 | 
					
						
							|  |  |  | #include <linux/mmc/host.h>
 | 
					
						
							|  |  |  | #include <linux/mmc/dw_mmc.h>
 | 
					
						
							| 
									
										
										
										
											2013-08-31 00:12:50 +09:00
										 |  |  | #include <linux/mmc/mmc.h>
 | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | #include <linux/of.h>
 | 
					
						
							|  |  |  | #include <linux/of_gpio.h>
 | 
					
						
							| 
									
										
										
										
											2013-08-31 00:12:50 +09:00
										 |  |  | #include <linux/slab.h>
 | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | #include "dw_mmc.h"
 | 
					
						
							|  |  |  | #include "dw_mmc-pltfm.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define NUM_PINS(x)			(x + 2)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define SDMMC_CLKSEL			0x09C
 | 
					
						
							|  |  |  | #define SDMMC_CLKSEL_CCLK_SAMPLE(x)	(((x) & 7) << 0)
 | 
					
						
							|  |  |  | #define SDMMC_CLKSEL_CCLK_DRIVE(x)	(((x) & 7) << 16)
 | 
					
						
							|  |  |  | #define SDMMC_CLKSEL_CCLK_DIVIDER(x)	(((x) & 7) << 24)
 | 
					
						
							|  |  |  | #define SDMMC_CLKSEL_GET_DRV_WD3(x)	(((x) >> 16) & 0x7)
 | 
					
						
							|  |  |  | #define SDMMC_CLKSEL_TIMING(x, y, z)	(SDMMC_CLKSEL_CCLK_SAMPLE(x) |	\
 | 
					
						
							|  |  |  | 					SDMMC_CLKSEL_CCLK_DRIVE(y) |	\ | 
					
						
							|  |  |  | 					SDMMC_CLKSEL_CCLK_DIVIDER(z)) | 
					
						
							| 
									
										
										
										
											2013-08-31 00:11:21 +09:00
										 |  |  | #define SDMMC_CLKSEL_WAKEUP_INT		BIT(11)
 | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | #define EXYNOS4210_FIXED_CIU_CLK_DIV	2
 | 
					
						
							|  |  |  | #define EXYNOS4412_FIXED_CIU_CLK_DIV	4
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-31 00:12:35 +09:00
										 |  |  | /* Block number in eMMC */ | 
					
						
							|  |  |  | #define DWMCI_BLOCK_NUM		0xFFFFFFFF
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define SDMMC_EMMCP_BASE	0x1000
 | 
					
						
							|  |  |  | #define SDMMC_MPSECURITY	(SDMMC_EMMCP_BASE + 0x0010)
 | 
					
						
							|  |  |  | #define SDMMC_MPSBEGIN0		(SDMMC_EMMCP_BASE + 0x0200)
 | 
					
						
							|  |  |  | #define SDMMC_MPSEND0		(SDMMC_EMMCP_BASE + 0x0204)
 | 
					
						
							|  |  |  | #define SDMMC_MPSCTRL0		(SDMMC_EMMCP_BASE + 0x020C)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* SMU control bits */ | 
					
						
							|  |  |  | #define DWMCI_MPSCTRL_SECURE_READ_BIT		BIT(7)
 | 
					
						
							|  |  |  | #define DWMCI_MPSCTRL_SECURE_WRITE_BIT		BIT(6)
 | 
					
						
							|  |  |  | #define DWMCI_MPSCTRL_NON_SECURE_READ_BIT	BIT(5)
 | 
					
						
							|  |  |  | #define DWMCI_MPSCTRL_NON_SECURE_WRITE_BIT	BIT(4)
 | 
					
						
							|  |  |  | #define DWMCI_MPSCTRL_USE_FUSE_KEY		BIT(3)
 | 
					
						
							|  |  |  | #define DWMCI_MPSCTRL_ECB_MODE			BIT(2)
 | 
					
						
							|  |  |  | #define DWMCI_MPSCTRL_ENCRYPTION		BIT(1)
 | 
					
						
							|  |  |  | #define DWMCI_MPSCTRL_VALID			BIT(0)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-31 00:13:03 +09:00
										 |  |  | #define EXYNOS_CCLKIN_MIN	50000000	/* unit: HZ */
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | /* Variations in Exynos specific dw-mshc controller */ | 
					
						
							|  |  |  | enum dw_mci_exynos_type { | 
					
						
							|  |  |  | 	DW_MCI_TYPE_EXYNOS4210, | 
					
						
							|  |  |  | 	DW_MCI_TYPE_EXYNOS4412, | 
					
						
							|  |  |  | 	DW_MCI_TYPE_EXYNOS5250, | 
					
						
							| 
									
										
										
										
											2013-05-24 15:34:32 +05:30
										 |  |  | 	DW_MCI_TYPE_EXYNOS5420, | 
					
						
							| 
									
										
										
										
											2013-08-31 00:12:35 +09:00
										 |  |  | 	DW_MCI_TYPE_EXYNOS5420_SMU, | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Exynos implementation specific driver private data */ | 
					
						
							|  |  |  | struct dw_mci_exynos_priv_data { | 
					
						
							|  |  |  | 	enum dw_mci_exynos_type		ctrl_type; | 
					
						
							|  |  |  | 	u8				ciu_div; | 
					
						
							|  |  |  | 	u32				sdr_timing; | 
					
						
							|  |  |  | 	u32				ddr_timing; | 
					
						
							| 
									
										
										
										
											2013-08-31 00:13:03 +09:00
										 |  |  | 	u32				cur_speed; | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct dw_mci_exynos_compatible { | 
					
						
							|  |  |  | 	char				*compatible; | 
					
						
							|  |  |  | 	enum dw_mci_exynos_type		ctrl_type; | 
					
						
							|  |  |  | } exynos_compat[] = { | 
					
						
							|  |  |  | 	{ | 
					
						
							|  |  |  | 		.compatible	= "samsung,exynos4210-dw-mshc", | 
					
						
							|  |  |  | 		.ctrl_type	= DW_MCI_TYPE_EXYNOS4210, | 
					
						
							|  |  |  | 	}, { | 
					
						
							|  |  |  | 		.compatible	= "samsung,exynos4412-dw-mshc", | 
					
						
							|  |  |  | 		.ctrl_type	= DW_MCI_TYPE_EXYNOS4412, | 
					
						
							|  |  |  | 	}, { | 
					
						
							|  |  |  | 		.compatible	= "samsung,exynos5250-dw-mshc", | 
					
						
							|  |  |  | 		.ctrl_type	= DW_MCI_TYPE_EXYNOS5250, | 
					
						
							| 
									
										
										
										
											2013-05-24 15:34:32 +05:30
										 |  |  | 	}, { | 
					
						
							|  |  |  | 		.compatible	= "samsung,exynos5420-dw-mshc", | 
					
						
							|  |  |  | 		.ctrl_type	= DW_MCI_TYPE_EXYNOS5420, | 
					
						
							| 
									
										
										
										
											2013-08-31 00:12:35 +09:00
										 |  |  | 	}, { | 
					
						
							|  |  |  | 		.compatible	= "samsung,exynos5420-dw-mshc-smu", | 
					
						
							|  |  |  | 		.ctrl_type	= DW_MCI_TYPE_EXYNOS5420_SMU, | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 	}, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int dw_mci_exynos_priv_init(struct dw_mci *host) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2013-08-31 00:11:57 +09:00
										 |  |  | 	struct dw_mci_exynos_priv_data *priv = host->priv; | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-31 00:12:35 +09:00
										 |  |  | 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS5420_SMU) { | 
					
						
							|  |  |  | 		mci_writel(host, MPSBEGIN0, 0); | 
					
						
							|  |  |  | 		mci_writel(host, MPSEND0, DWMCI_BLOCK_NUM); | 
					
						
							|  |  |  | 		mci_writel(host, MPSCTRL0, DWMCI_MPSCTRL_SECURE_WRITE_BIT | | 
					
						
							|  |  |  | 			   DWMCI_MPSCTRL_NON_SECURE_READ_BIT | | 
					
						
							|  |  |  | 			   DWMCI_MPSCTRL_VALID | | 
					
						
							|  |  |  | 			   DWMCI_MPSCTRL_NON_SECURE_WRITE_BIT); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int dw_mci_exynos_setup_clock(struct dw_mci *host) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct dw_mci_exynos_priv_data *priv = host->priv; | 
					
						
							| 
									
										
										
										
											2013-08-31 00:13:03 +09:00
										 |  |  | 	unsigned long rate = clk_get_rate(host->ciu_clk); | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-31 00:13:03 +09:00
										 |  |  | 	host->bus_hz = rate / (priv->ciu_div + 1); | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-31 00:11:21 +09:00
										 |  |  | #ifdef CONFIG_PM_SLEEP
 | 
					
						
							|  |  |  | static int dw_mci_exynos_suspend(struct device *dev) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct dw_mci *host = dev_get_drvdata(dev); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return dw_mci_suspend(host); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int dw_mci_exynos_resume(struct device *dev) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct dw_mci *host = dev_get_drvdata(dev); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-31 00:12:35 +09:00
										 |  |  | 	dw_mci_exynos_priv_init(host); | 
					
						
							| 
									
										
										
										
											2013-08-31 00:11:21 +09:00
										 |  |  | 	return dw_mci_resume(host); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /**
 | 
					
						
							|  |  |  |  * dw_mci_exynos_resume_noirq - Exynos-specific resume code | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * On exynos5420 there is a silicon errata that will sometimes leave the | 
					
						
							|  |  |  |  * WAKEUP_INT bit in the CLKSEL register asserted.  This bit is 1 to indicate | 
					
						
							|  |  |  |  * that it fired and we can clear it by writing a 1 back.  Clear it to prevent | 
					
						
							|  |  |  |  * interrupts from going off constantly. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * We run this code on all exynos variants because it doesn't hurt. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int dw_mci_exynos_resume_noirq(struct device *dev) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct dw_mci *host = dev_get_drvdata(dev); | 
					
						
							|  |  |  | 	u32 clksel; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	clksel = mci_readl(host, CLKSEL); | 
					
						
							|  |  |  | 	if (clksel & SDMMC_CLKSEL_WAKEUP_INT) | 
					
						
							|  |  |  | 		mci_writel(host, CLKSEL, clksel); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | #else
 | 
					
						
							|  |  |  | #define dw_mci_exynos_suspend		NULL
 | 
					
						
							|  |  |  | #define dw_mci_exynos_resume		NULL
 | 
					
						
							|  |  |  | #define dw_mci_exynos_resume_noirq	NULL
 | 
					
						
							|  |  |  | #endif /* CONFIG_PM_SLEEP */
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | static void dw_mci_exynos_prepare_command(struct dw_mci *host, u32 *cmdr) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * Exynos4412 and Exynos5250 extends the use of CMD register with the | 
					
						
							|  |  |  | 	 * use of bit 29 (which is reserved on standard MSHC controllers) for | 
					
						
							|  |  |  | 	 * optionally bypassing the HOLD register for command and data. The | 
					
						
							|  |  |  | 	 * HOLD register should be bypassed in case there is no phase shift | 
					
						
							|  |  |  | 	 * applied on CMD/DATA that is sent to the card. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	if (SDMMC_CLKSEL_GET_DRV_WD3(mci_readl(host, CLKSEL))) | 
					
						
							|  |  |  | 		*cmdr |= SDMMC_CMD_USE_HOLD_REG; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void dw_mci_exynos_set_ios(struct dw_mci *host, struct mmc_ios *ios) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct dw_mci_exynos_priv_data *priv = host->priv; | 
					
						
							| 
									
										
										
										
											2013-08-31 00:13:03 +09:00
										 |  |  | 	unsigned int wanted = ios->clock; | 
					
						
							|  |  |  | 	unsigned long actual; | 
					
						
							|  |  |  | 	u8 div = priv->ciu_div + 1; | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-31 00:13:03 +09:00
										 |  |  | 	if (ios->timing == MMC_TIMING_UHS_DDR50) { | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 		mci_writel(host, CLKSEL, priv->ddr_timing); | 
					
						
							| 
									
										
										
										
											2013-08-31 00:13:03 +09:00
										 |  |  | 		/* Should be double rate for DDR mode */ | 
					
						
							|  |  |  | 		if (ios->bus_width == MMC_BUS_WIDTH_8) | 
					
						
							|  |  |  | 			wanted <<= 1; | 
					
						
							|  |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 		mci_writel(host, CLKSEL, priv->sdr_timing); | 
					
						
							| 
									
										
										
										
											2013-08-31 00:13:03 +09:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Don't care if wanted clock is zero */ | 
					
						
							|  |  |  | 	if (!wanted) | 
					
						
							|  |  |  | 		return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	/* Guaranteed minimum frequency for cclkin */ | 
					
						
							|  |  |  | 	if (wanted < EXYNOS_CCLKIN_MIN) | 
					
						
							|  |  |  | 		wanted = EXYNOS_CCLKIN_MIN; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (wanted != priv->cur_speed) { | 
					
						
							|  |  |  | 		int ret = clk_set_rate(host->ciu_clk, wanted * div); | 
					
						
							|  |  |  | 		if (ret) | 
					
						
							|  |  |  | 			dev_warn(host->dev, | 
					
						
							|  |  |  | 				"failed to set clk-rate %u error: %d\n", | 
					
						
							|  |  |  | 				 wanted * div, ret); | 
					
						
							|  |  |  | 		actual = clk_get_rate(host->ciu_clk); | 
					
						
							|  |  |  | 		host->bus_hz = actual / div; | 
					
						
							|  |  |  | 		priv->cur_speed = wanted; | 
					
						
							|  |  |  | 		host->current_speed = 0; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int dw_mci_exynos_parse_dt(struct dw_mci *host) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2013-08-31 00:11:57 +09:00
										 |  |  | 	struct dw_mci_exynos_priv_data *priv; | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 	struct device_node *np = host->dev->of_node; | 
					
						
							|  |  |  | 	u32 timing[2]; | 
					
						
							|  |  |  | 	u32 div = 0; | 
					
						
							| 
									
										
										
										
											2013-08-31 00:11:57 +09:00
										 |  |  | 	int idx; | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-31 00:11:57 +09:00
										 |  |  | 	priv = devm_kzalloc(host->dev, sizeof(*priv), GFP_KERNEL); | 
					
						
							|  |  |  | 	if (!priv) { | 
					
						
							|  |  |  | 		dev_err(host->dev, "mem alloc failed for private data\n"); | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (idx = 0; idx < ARRAY_SIZE(exynos_compat); idx++) { | 
					
						
							|  |  |  | 		if (of_device_is_compatible(np, exynos_compat[idx].compatible)) | 
					
						
							|  |  |  | 			priv->ctrl_type = exynos_compat[idx].ctrl_type; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-31 00:13:03 +09:00
										 |  |  | 	if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4412) | 
					
						
							|  |  |  | 		priv->ciu_div = EXYNOS4412_FIXED_CIU_CLK_DIV - 1; | 
					
						
							|  |  |  | 	else if (priv->ctrl_type == DW_MCI_TYPE_EXYNOS4210) | 
					
						
							|  |  |  | 		priv->ciu_div = EXYNOS4210_FIXED_CIU_CLK_DIV - 1; | 
					
						
							|  |  |  | 	else { | 
					
						
							|  |  |  | 		of_property_read_u32(np, "samsung,dw-mshc-ciu-div", &div); | 
					
						
							|  |  |  | 		priv->ciu_div = div; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	ret = of_property_read_u32_array(np, | 
					
						
							|  |  |  | 			"samsung,dw-mshc-sdr-timing", timing, 2); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-10-22 14:41:56 +05:30
										 |  |  | 	priv->sdr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 	ret = of_property_read_u32_array(np, | 
					
						
							|  |  |  | 			"samsung,dw-mshc-ddr-timing", timing, 2); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		return ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	priv->ddr_timing = SDMMC_CLKSEL_TIMING(timing[0], timing[1], div); | 
					
						
							| 
									
										
										
										
											2013-08-31 00:11:57 +09:00
										 |  |  | 	host->priv = priv; | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-31 00:12:50 +09:00
										 |  |  | static inline u8 dw_mci_exynos_get_clksmpl(struct dw_mci *host) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return SDMMC_CLKSEL_CCLK_SAMPLE(mci_readl(host, CLKSEL)); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline void dw_mci_exynos_set_clksmpl(struct dw_mci *host, u8 sample) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	u32 clksel; | 
					
						
							|  |  |  | 	clksel = mci_readl(host, CLKSEL); | 
					
						
							|  |  |  | 	clksel = (clksel & ~0x7) | SDMMC_CLKSEL_CCLK_SAMPLE(sample); | 
					
						
							|  |  |  | 	mci_writel(host, CLKSEL, clksel); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline u8 dw_mci_exynos_move_next_clksmpl(struct dw_mci *host) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	u32 clksel; | 
					
						
							|  |  |  | 	u8 sample; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	clksel = mci_readl(host, CLKSEL); | 
					
						
							|  |  |  | 	sample = (clksel + 1) & 0x7; | 
					
						
							|  |  |  | 	clksel = (clksel & ~0x7) | sample; | 
					
						
							|  |  |  | 	mci_writel(host, CLKSEL, clksel); | 
					
						
							|  |  |  | 	return sample; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static s8 dw_mci_exynos_get_best_clksmpl(u8 candiates) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	const u8 iter = 8; | 
					
						
							|  |  |  | 	u8 __c; | 
					
						
							|  |  |  | 	s8 i, loc = -1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i = 0; i < iter; i++) { | 
					
						
							|  |  |  | 		__c = ror8(candiates, i); | 
					
						
							|  |  |  | 		if ((__c & 0xc7) == 0xc7) { | 
					
						
							|  |  |  | 			loc = i; | 
					
						
							|  |  |  | 			goto out; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for (i = 0; i < iter; i++) { | 
					
						
							|  |  |  | 		__c = ror8(candiates, i); | 
					
						
							|  |  |  | 		if ((__c & 0x83) == 0x83) { | 
					
						
							|  |  |  | 			loc = i; | 
					
						
							|  |  |  | 			goto out; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | out: | 
					
						
							|  |  |  | 	return loc; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int dw_mci_exynos_execute_tuning(struct dw_mci_slot *slot, u32 opcode, | 
					
						
							|  |  |  | 					struct dw_mci_tuning_data *tuning_data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct dw_mci *host = slot->host; | 
					
						
							|  |  |  | 	struct mmc_host *mmc = slot->mmc; | 
					
						
							|  |  |  | 	const u8 *blk_pattern = tuning_data->blk_pattern; | 
					
						
							|  |  |  | 	u8 *blk_test; | 
					
						
							|  |  |  | 	unsigned int blksz = tuning_data->blksz; | 
					
						
							|  |  |  | 	u8 start_smpl, smpl, candiates = 0; | 
					
						
							|  |  |  | 	s8 found = -1; | 
					
						
							|  |  |  | 	int ret = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	blk_test = kmalloc(blksz, GFP_KERNEL); | 
					
						
							|  |  |  | 	if (!blk_test) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	start_smpl = dw_mci_exynos_get_clksmpl(host); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	do { | 
					
						
							|  |  |  | 		struct mmc_request mrq = {NULL}; | 
					
						
							|  |  |  | 		struct mmc_command cmd = {0}; | 
					
						
							|  |  |  | 		struct mmc_command stop = {0}; | 
					
						
							|  |  |  | 		struct mmc_data data = {0}; | 
					
						
							|  |  |  | 		struct scatterlist sg; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		cmd.opcode = opcode; | 
					
						
							|  |  |  | 		cmd.arg = 0; | 
					
						
							|  |  |  | 		cmd.flags = MMC_RSP_R1 | MMC_CMD_ADTC; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		stop.opcode = MMC_STOP_TRANSMISSION; | 
					
						
							|  |  |  | 		stop.arg = 0; | 
					
						
							|  |  |  | 		stop.flags = MMC_RSP_R1B | MMC_CMD_AC; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		data.blksz = blksz; | 
					
						
							|  |  |  | 		data.blocks = 1; | 
					
						
							|  |  |  | 		data.flags = MMC_DATA_READ; | 
					
						
							|  |  |  | 		data.sg = &sg; | 
					
						
							|  |  |  | 		data.sg_len = 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		sg_init_one(&sg, blk_test, blksz); | 
					
						
							|  |  |  | 		mrq.cmd = &cmd; | 
					
						
							|  |  |  | 		mrq.stop = &stop; | 
					
						
							|  |  |  | 		mrq.data = &data; | 
					
						
							|  |  |  | 		host->mrq = &mrq; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		mci_writel(host, TMOUT, ~0); | 
					
						
							|  |  |  | 		smpl = dw_mci_exynos_move_next_clksmpl(host); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		mmc_wait_for_req(mmc, &mrq); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (!cmd.error && !data.error) { | 
					
						
							|  |  |  | 			if (!memcmp(blk_pattern, blk_test, blksz)) | 
					
						
							|  |  |  | 				candiates |= (1 << smpl); | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			dev_dbg(host->dev, | 
					
						
							|  |  |  | 				"Tuning error: cmd.error:%d, data.error:%d\n", | 
					
						
							|  |  |  | 				cmd.error, data.error); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} while (start_smpl != smpl); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	found = dw_mci_exynos_get_best_clksmpl(candiates); | 
					
						
							|  |  |  | 	if (found >= 0) | 
					
						
							|  |  |  | 		dw_mci_exynos_set_clksmpl(host, found); | 
					
						
							|  |  |  | 	else | 
					
						
							|  |  |  | 		ret = -EIO; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	kfree(blk_test); | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-02-23 00:17:45 +09:00
										 |  |  | /* Common capabilities of Exynos4/Exynos5 SoC */ | 
					
						
							|  |  |  | static unsigned long exynos_dwmmc_caps[4] = { | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 	MMC_CAP_UHS_DDR50 | MMC_CAP_1_8V_DDR | | 
					
						
							|  |  |  | 		MMC_CAP_8_BIT_DATA | MMC_CAP_CMD23, | 
					
						
							|  |  |  | 	MMC_CAP_CMD23, | 
					
						
							|  |  |  | 	MMC_CAP_CMD23, | 
					
						
							|  |  |  | 	MMC_CAP_CMD23, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-02-23 00:17:45 +09:00
										 |  |  | static const struct dw_mci_drv_data exynos_drv_data = { | 
					
						
							|  |  |  | 	.caps			= exynos_dwmmc_caps, | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 	.init			= dw_mci_exynos_priv_init, | 
					
						
							|  |  |  | 	.setup_clock		= dw_mci_exynos_setup_clock, | 
					
						
							|  |  |  | 	.prepare_command	= dw_mci_exynos_prepare_command, | 
					
						
							|  |  |  | 	.set_ios		= dw_mci_exynos_set_ios, | 
					
						
							|  |  |  | 	.parse_dt		= dw_mci_exynos_parse_dt, | 
					
						
							| 
									
										
										
										
											2013-08-31 00:12:50 +09:00
										 |  |  | 	.execute_tuning		= dw_mci_exynos_execute_tuning, | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const struct of_device_id dw_mci_exynos_match[] = { | 
					
						
							| 
									
										
										
										
											2013-02-23 00:17:45 +09:00
										 |  |  | 	{ .compatible = "samsung,exynos4412-dw-mshc", | 
					
						
							|  |  |  | 			.data = &exynos_drv_data, }, | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 	{ .compatible = "samsung,exynos5250-dw-mshc", | 
					
						
							| 
									
										
										
										
											2013-02-23 00:17:45 +09:00
										 |  |  | 			.data = &exynos_drv_data, }, | 
					
						
							| 
									
										
										
										
											2013-05-24 15:34:32 +05:30
										 |  |  | 	{ .compatible = "samsung,exynos5420-dw-mshc", | 
					
						
							|  |  |  | 			.data = &exynos_drv_data, }, | 
					
						
							| 
									
										
										
										
											2013-08-31 00:12:35 +09:00
										 |  |  | 	{ .compatible = "samsung,exynos5420-dw-mshc-smu", | 
					
						
							|  |  |  | 			.data = &exynos_drv_data, }, | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 	{}, | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2012-11-06 22:55:30 +01:00
										 |  |  | MODULE_DEVICE_TABLE(of, dw_mci_exynos_match); | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-02-18 14:23:08 +05:30
										 |  |  | static int dw_mci_exynos_probe(struct platform_device *pdev) | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2012-11-06 22:55:31 +01:00
										 |  |  | 	const struct dw_mci_drv_data *drv_data; | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 	const struct of_device_id *match; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	match = of_match_node(dw_mci_exynos_match, pdev->dev.of_node); | 
					
						
							|  |  |  | 	drv_data = match->data; | 
					
						
							|  |  |  | 	return dw_mci_pltfm_register(pdev, drv_data); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-08-31 00:11:21 +09:00
										 |  |  | const struct dev_pm_ops dw_mci_exynos_pmops = { | 
					
						
							|  |  |  | 	SET_SYSTEM_SLEEP_PM_OPS(dw_mci_exynos_suspend, dw_mci_exynos_resume) | 
					
						
							|  |  |  | 	.resume_noirq = dw_mci_exynos_resume_noirq, | 
					
						
							|  |  |  | 	.thaw_noirq = dw_mci_exynos_resume_noirq, | 
					
						
							|  |  |  | 	.restore_noirq = dw_mci_exynos_resume_noirq, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | static struct platform_driver dw_mci_exynos_pltfm_driver = { | 
					
						
							|  |  |  | 	.probe		= dw_mci_exynos_probe, | 
					
						
							|  |  |  | 	.remove		= __exit_p(dw_mci_pltfm_remove), | 
					
						
							|  |  |  | 	.driver		= { | 
					
						
							|  |  |  | 		.name		= "dwmmc_exynos", | 
					
						
							| 
									
										
										
										
											2013-02-18 14:23:09 +05:30
										 |  |  | 		.of_match_table	= dw_mci_exynos_match, | 
					
						
							| 
									
										
										
										
											2013-08-31 00:11:21 +09:00
										 |  |  | 		.pm		= &dw_mci_exynos_pmops, | 
					
						
							| 
									
										
										
										
											2012-09-17 18:16:43 +00:00
										 |  |  | 	}, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | module_platform_driver(dw_mci_exynos_pltfm_driver); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | MODULE_DESCRIPTION("Samsung Specific DW-MSHC Driver Extension"); | 
					
						
							|  |  |  | MODULE_AUTHOR("Thomas Abraham <thomas.ab@samsung.com"); | 
					
						
							|  |  |  | MODULE_LICENSE("GPL v2"); | 
					
						
							|  |  |  | MODULE_ALIAS("platform:dwmmc-exynos"); |