1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * (C) Copyright 2012 SAMSUNG Electronics
4 * Jaehoon Chung <jh80.chung@samsung.com>
5 */
6
7#include <dm.h>
8#include <log.h>
9#include <malloc.h>
10#include <sdhci.h>
11#include <fdtdec.h>
12#include <asm/global_data.h>
13#include <linux/libfdt.h>
14#include <asm/gpio.h>
15#include <asm/arch/mmc.h>
16#include <asm/arch/clk.h>
17#include <errno.h>
18#include <asm/arch/pinmux.h>
19
20#ifdef CONFIG_DM_MMC
21struct s5p_sdhci_plat {
22	struct mmc_config cfg;
23	struct mmc mmc;
24};
25
26DECLARE_GLOBAL_DATA_PTR;
27#endif
28
29static char *S5P_NAME = "SAMSUNG SDHCI";
30static void s5p_sdhci_set_control_reg(struct sdhci_host *host)
31{
32	unsigned long val, ctrl;
33	/*
34	 * SELCLKPADDS[17:16]
35	 * 00 = 2mA
36	 * 01 = 4mA
37	 * 10 = 7mA
38	 * 11 = 9mA
39	 */
40	sdhci_writel(host, SDHCI_CTRL4_DRIVE_MASK(0x3), SDHCI_CONTROL4);
41
42	val = sdhci_readl(host, SDHCI_CONTROL2);
43	val &= SDHCI_CTRL2_SELBASECLK_MASK(3);
44
45	val |=	SDHCI_CTRL2_ENSTAASYNCCLR |
46		SDHCI_CTRL2_ENCMDCNFMSK |
47		SDHCI_CTRL2_ENFBCLKRX |
48		SDHCI_CTRL2_ENCLKOUTHOLD;
49
50	sdhci_writel(host, val, SDHCI_CONTROL2);
51
52	/*
53	 * FCSEL3[31] FCSEL2[23] FCSEL1[15] FCSEL0[7]
54	 * FCSel[1:0] : Rx Feedback Clock Delay Control
55	 *	Inverter delay means10ns delay if SDCLK 50MHz setting
56	 *	01 = Delay1 (basic delay)
57	 *	11 = Delay2 (basic delay + 2ns)
58	 *	00 = Delay3 (inverter delay)
59	 *	10 = Delay4 (inverter delay + 2ns)
60	 */
61	val = SDHCI_CTRL3_FCSEL0 | SDHCI_CTRL3_FCSEL1;
62	sdhci_writel(host, val, SDHCI_CONTROL3);
63
64	/*
65	 * SELBASECLK[5:4]
66	 * 00/01 = HCLK
67	 * 10 = EPLL
68	 * 11 = XTI or XEXTCLK
69	 */
70	ctrl = sdhci_readl(host, SDHCI_CONTROL2);
71	ctrl &= ~SDHCI_CTRL2_SELBASECLK_MASK(0x3);
72	ctrl |= SDHCI_CTRL2_SELBASECLK_MASK(0x2);
73	sdhci_writel(host, ctrl, SDHCI_CONTROL2);
74}
75
76static void s5p_set_clock(struct sdhci_host *host, u32 div)
77{
78	/* ToDo : Use the Clock Framework */
79	set_mmc_clk(host->index, div);
80}
81
82static const struct sdhci_ops s5p_sdhci_ops = {
83	.set_clock	= &s5p_set_clock,
84	.set_control_reg = &s5p_sdhci_set_control_reg,
85};
86
87static int s5p_sdhci_core_init(struct sdhci_host *host)
88{
89	host->name = S5P_NAME;
90
91	host->quirks = SDHCI_QUIRK_NO_HISPD_BIT | SDHCI_QUIRK_BROKEN_VOLTAGE |
92		SDHCI_QUIRK_BROKEN_R1B | SDHCI_QUIRK_32BIT_DMA_ADDR |
93		SDHCI_QUIRK_WAIT_SEND_CMD | SDHCI_QUIRK_USE_WIDE8;
94	host->max_clk = 52000000;
95	host->voltages = MMC_VDD_32_33 | MMC_VDD_33_34 | MMC_VDD_165_195;
96	host->ops = &s5p_sdhci_ops;
97
98	if (host->bus_width == 8)
99		host->host_caps |= MMC_MODE_8BIT;
100
101#ifndef CONFIG_BLK
102	return add_sdhci(host, 0, 400000);
103#else
104	return 0;
105#endif
106}
107
108int s5p_sdhci_init(u32 regbase, int index, int bus_width)
109{
110	struct sdhci_host *host = calloc(1, sizeof(struct sdhci_host));
111	if (!host) {
112		printf("sdhci__host allocation fail!\n");
113		return -ENOMEM;
114	}
115	host->ioaddr = (void *)regbase;
116	host->index = index;
117	host->bus_width = bus_width;
118
119	return s5p_sdhci_core_init(host);
120}
121
122static int do_sdhci_init(struct sdhci_host *host)
123{
124	int dev_id, flag, ret;
125
126	flag = host->bus_width == 8 ? PINMUX_FLAG_8BIT_MODE : PINMUX_FLAG_NONE;
127	dev_id = host->index + PERIPH_ID_SDMMC0;
128
129	ret = exynos_pinmux_config(dev_id, flag);
130	if (ret) {
131		printf("external SD not configured\n");
132		return ret;
133	}
134
135	if (dm_gpio_is_valid(&host->pwr_gpio)) {
136		dm_gpio_set_value(&host->pwr_gpio, 1);
137		ret = exynos_pinmux_config(dev_id, flag);
138		if (ret) {
139			debug("MMC not configured\n");
140			return ret;
141		}
142	}
143
144	if (dm_gpio_is_valid(&host->cd_gpio)) {
145		ret = dm_gpio_get_value(&host->cd_gpio);
146		if (ret) {
147			debug("no SD card detected (%d)\n", ret);
148			return -ENODEV;
149		}
150	}
151
152	return s5p_sdhci_core_init(host);
153}
154
155static int sdhci_get_config(const void *blob, int node, struct sdhci_host *host)
156{
157	int bus_width, dev_id;
158	unsigned int base;
159
160	/* Get device id */
161	dev_id = pinmux_decode_periph_id(blob, node);
162	if (dev_id < PERIPH_ID_SDMMC0 || dev_id > PERIPH_ID_SDMMC3) {
163		debug("MMC: Can't get device id\n");
164		return -EINVAL;
165	}
166	host->index = dev_id - PERIPH_ID_SDMMC0;
167
168	/* Get bus width */
169	bus_width = fdtdec_get_int(blob, node, "samsung,bus-width", 0);
170	if (bus_width <= 0) {
171		debug("MMC: Can't get bus-width\n");
172		return -EINVAL;
173	}
174	host->bus_width = bus_width;
175
176	/* Get the base address from the device node */
177	base = fdtdec_get_addr(blob, node, "reg");
178	if (!base) {
179		debug("MMC: Can't get base address\n");
180		return -EINVAL;
181	}
182	host->ioaddr = (void *)base;
183
184	gpio_request_by_name_nodev(offset_to_ofnode(node), "pwr-gpios", 0,
185				   &host->pwr_gpio, GPIOD_IS_OUT);
186	gpio_request_by_name_nodev(offset_to_ofnode(node), "cd-gpios", 0,
187				   &host->cd_gpio, GPIOD_IS_IN);
188
189	return 0;
190}
191
192#ifdef CONFIG_DM_MMC
193static int s5p_sdhci_probe(struct udevice *dev)
194{
195	struct s5p_sdhci_plat *plat = dev_get_plat(dev);
196	struct mmc_uclass_priv *upriv = dev_get_uclass_priv(dev);
197	struct sdhci_host *host = dev_get_priv(dev);
198	int ret;
199
200	ret = sdhci_get_config(gd->fdt_blob, dev_of_offset(dev), host);
201	if (ret)
202		return ret;
203
204	ret = do_sdhci_init(host);
205	if (ret)
206		return ret;
207
208	ret = mmc_of_parse(dev, &plat->cfg);
209	if (ret)
210		return ret;
211
212	host->mmc = &plat->mmc;
213	host->mmc->dev = dev;
214
215	ret = sdhci_setup_cfg(&plat->cfg, host, 0, 400000);
216	if (ret)
217		return ret;
218
219	host->mmc->priv = host;
220	upriv->mmc = host->mmc;
221
222	return sdhci_probe(dev);
223}
224
225static int s5p_sdhci_bind(struct udevice *dev)
226{
227	struct s5p_sdhci_plat *plat = dev_get_plat(dev);
228	int ret;
229
230	ret = sdhci_bind(dev, &plat->mmc, &plat->cfg);
231	if (ret)
232		return ret;
233
234	return 0;
235}
236
237static const struct udevice_id s5p_sdhci_ids[] = {
238	{ .compatible = "samsung,exynos4412-sdhci"},
239	{ }
240};
241
242U_BOOT_DRIVER(s5p_sdhci_drv) = {
243	.name		= "s5p_sdhci",
244	.id		= UCLASS_MMC,
245	.of_match	= s5p_sdhci_ids,
246	.bind		= s5p_sdhci_bind,
247	.ops		= &sdhci_ops,
248	.probe		= s5p_sdhci_probe,
249	.priv_auto	= sizeof(struct sdhci_host),
250	.plat_auto	= sizeof(struct s5p_sdhci_plat),
251};
252#endif /* CONFIG_DM_MMC */
253