summaryrefslogtreecommitdiff
path: root/sys/dev/fdt/sdhc_fdt.c
blob: e71e00cb85eac1a05bdb101a9e94970c6eaa4f55 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
/*	$OpenBSD: sdhc_fdt.c,v 1.20 2023/04/08 05:40:54 jsg Exp $	*/
/*
 * Copyright (c) 2017 Mark Kettenis
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <sys/param.h>
#include <sys/malloc.h>
#include <sys/systm.h>

#include <machine/bus.h>
#include <machine/fdt.h>
#include <machine/intr.h>

#include <dev/ofw/openfirm.h>
#include <dev/ofw/ofw_clock.h>
#include <dev/ofw/ofw_gpio.h>
#include <dev/ofw/ofw_misc.h>
#include <dev/ofw/ofw_pinctrl.h>
#include <dev/ofw/ofw_regulator.h>
#include <dev/ofw/fdt.h>

#include <dev/sdmmc/sdhcreg.h>
#include <dev/sdmmc/sdhcvar.h>
#include <dev/sdmmc/sdmmcvar.h>

/* RK3399 */
#define GRF_EMMCCORE_CON0_BASECLOCK		0xf000
#define  GRF_EMMCCORE_CON0_BASECLOCK_CLR		(0xff << 24)
#define  GRF_EMMCCORE_CON0_BASECLOCK_VAL(x)		(((x) & 0xff) << 8)
#define GRF_EMMCCORE_CON11			0xf02c
#define  GRF_EMMCCORE_CON11_CLOCKMULT_CLR		(0xff << 16)
#define  GRF_EMMCCORE_CON11_CLOCKMULT_VAL(x)		(((x) & 0xff) << 0)

/* Marvell Xenon */
#define XENON_SYS_OP_CTRL			0x108
#define  XENON_SYS_OP_CTRL_SLOT_ENABLE(x)		(1 << (x))
#define  XENON_SYS_OP_CTRL_SDCLK_IDLEOFF_ENABLE(x)	(1 << ((x) + 8))
#define  XENON_SYS_OP_CTRL_AUTO_CLKGATE_DISABLE		(1 << 20)
#define XENON_SYS_EXT_OP_CTRL			0x10c
#define  XENON_SYS_EXT_OP_CTRL_PARALLEL_TRAN(x)		(1 << (x))
#define  XENON_SYS_EXT_OP_CTRL_MASK_CMD_CONFLICT_ERR	(1 << 8)
#define XENON_SLOT_EMMC_CTRL			0x130
#define  XENON_SLOT_EMMC_CTRL_ENABLE_DATA_STROBE	(1 << 24)
#define  XENON_SLOT_EMMC_CTRL_ENABLE_RESP_STROBE	(1 << 25)
#define XENON_EMMC_PHY_TIMING_ADJUST		0x170
#define  XENON_EMMC_PHY_TIMING_ADJUST_SAMPL_INV_QSP_PHASE_SELECT (1 << 18)
#define  XENON_EMMC_PHY_TIMING_ADJUST_SDIO_MODE		(1 << 28)
#define  XENON_EMMC_PHY_TIMING_ADJUST_SLOW_MODE		(1 << 29)
#define  XENON_EMMC_PHY_TIMING_ADJUST_INIT		(1U << 31)
#define XENON_EMMC_PHY_FUNC_CONTROL		0x174
#define  XENON_EMMC_PHY_FUNC_CONTROL_DQ_ASYNC_MODE	(1 << 4)
#define  XENON_EMMC_PHY_FUNC_CONTROL_DQ_DDR_MODE	(0xff << 8)
#define  XENON_EMMC_PHY_FUNC_CONTROL_CMD_DDR_MODE	(1 << 16)
#define XENON_EMMC_PHY_PAD_CONTROL		0x178
#define  XENON_EMMC_PHY_PAD_CONTROL_FC_DQ_RECEN		(1 << 24)
#define  XENON_EMMC_PHY_PAD_CONTROL_FC_CMD_RECEN	(1 << 25)
#define  XENON_EMMC_PHY_PAD_CONTROL_FC_QSP_RECEN	(1 << 26)
#define  XENON_EMMC_PHY_PAD_CONTROL_FC_QSN_RECEN	(1 << 27)
#define  XENON_EMMC_PHY_PAD_CONTROL_FC_ALL_CMOS_RECVR	0xf000
#define XENON_EMMC_PHY_PAD_CONTROL1		0x17c
#define  XENON_EMMC_PHY_PAD_CONTROL1_FC_CMD_PD		(1 << 8)
#define  XENON_EMMC_PHY_PAD_CONTROL1_FC_QSP_PD		(1 << 9)
#define  XENON_EMMC_PHY_PAD_CONTROL1_FC_CMD_PU		(1 << 24)
#define  XENON_EMMC_PHY_PAD_CONTROL1_FC_QSP_PU		(1 << 25)
#define  XENON_EMMC_PHY_PAD_CONTROL1_FC_DQ_PD		0xff
#define  XENON_EMMC_PHY_PAD_CONTROL1_FC_DQ_PU		(0xff << 16)
#define XENON_EMMC_PHY_PAD_CONTROL2		0x180
#define  XENON_EMMC_PHY_PAD_CONTROL2_ZPR_SHIFT		0
#define  XENON_EMMC_PHY_PAD_CONTROL2_ZPR_MASK		0x1f
#define  XENON_EMMC_PHY_PAD_CONTROL2_ZNR_SHIFT		8
#define  XENON_EMMC_PHY_PAD_CONTROL2_ZNR_MASK		0x1f

#define ARMADA_3700_SOC_PAD_CTL			0
#define  ARMADA_3700_SOC_PAD_CTL_3_3V			0
#define  ARMADA_3700_SOC_PAD_CTL_1_8V			1

struct sdhc_fdt_softc {
	struct sdhc_softc 	sc;
	bus_space_tag_t		sc_iot;
	bus_space_handle_t	sc_ioh;
	bus_space_handle_t	sc_pad_ioh;
	bus_size_t		sc_size;
	void			*sc_ih;
	int			sc_node;
	uint32_t		sc_gpio[3];
	uint32_t		sc_vqmmc;

	/* Marvell Xenon */
	int			sc_sdhc_id;
	int			sc_slow_mode;
	uint32_t		sc_znr;
	uint32_t		sc_zpr;

	struct sdhc_host 	*sc_host;
	struct clock_device	sc_cd;
};

int	sdhc_fdt_match(struct device *, void *, void *);
void	sdhc_fdt_attach(struct device *, struct device *, void *);

const struct cfattach sdhc_fdt_ca = {
	sizeof(struct sdhc_fdt_softc), sdhc_fdt_match, sdhc_fdt_attach
};

int	sdhc_fdt_card_detect(struct sdhc_softc *);
int	sdhc_fdt_signal_voltage(struct sdhc_softc *, int);
uint32_t sdhc_fdt_get_frequency(void *, uint32_t *);

void	sdhc_fdt_xenon_bus_clock_post(struct sdhc_softc *, int, int);

int
sdhc_fdt_match(struct device *parent, void *match, void *aux)
{
	struct fdt_attach_args *faa = aux;

	return (OF_is_compatible(faa->fa_node, "arasan,sdhci-5.1") ||
	    OF_is_compatible(faa->fa_node, "arasan,sdhci-8.9a") ||
	    OF_is_compatible(faa->fa_node, "brcm,bcm2711-emmc2") ||
	    OF_is_compatible(faa->fa_node, "brcm,bcm2835-sdhci") ||
	    OF_is_compatible(faa->fa_node, "marvell,armada-3700-sdhci") ||
	    OF_is_compatible(faa->fa_node, "marvell,armada-ap806-sdhci") ||
	    OF_is_compatible(faa->fa_node, "marvell,armada-cp110-sdhci"));
}

void
sdhc_fdt_attach(struct device *parent, struct device *self, void *aux)
{
	struct sdhc_fdt_softc *sc = (struct sdhc_fdt_softc *)self;
	struct fdt_attach_args *faa = aux;
	struct regmap *rm = NULL;
	uint64_t capmask = 0, capset = 0;
	uint32_t reg, phandle, freq;
	char pad_type[16] = { 0 };

	if (faa->fa_nreg < 1) {
		printf(": no registers\n");
		return;
	}

	sc->sc_iot = faa->fa_iot;
	sc->sc_size = faa->fa_reg[0].size;
	sc->sc_node = faa->fa_node;

	if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
	    faa->fa_reg[0].size, 0, &sc->sc_ioh)) {
		printf(": can't map registers\n");
		return;
	}

	pinctrl_byname(faa->fa_node, "default");

	clock_set_assigned(faa->fa_node);
	clock_enable_all(faa->fa_node);
	reset_deassert_all(faa->fa_node);

	sc->sc_ih = fdt_intr_establish(faa->fa_node, IPL_BIO,
	    sdhc_intr, sc, sc->sc.sc_dev.dv_xname);
	if (sc->sc_ih == NULL) {
		printf(": can't establish interrupt\n");
		goto unmap;
	}

	if (OF_getproplen(faa->fa_node, "cd-gpios") > 0 ||
	    OF_getproplen(faa->fa_node, "non-removable") == 0) {
		OF_getpropintarray(faa->fa_node, "cd-gpios", sc->sc_gpio,
		    sizeof(sc->sc_gpio));
		gpio_controller_config_pin(sc->sc_gpio, GPIO_CONFIG_INPUT);
		sc->sc.sc_card_detect = sdhc_fdt_card_detect;
	}

	sc->sc_vqmmc = OF_getpropint(sc->sc_node, "vqmmc-supply", 0);

	printf("\n");

	sc->sc.sc_host = &sc->sc_host;
	sc->sc.sc_dmat = faa->fa_dmat;

	/*
	 * Arasan controller always uses 1.8V and doesn't like an
	 * explicit switch.
	 */
	if (OF_is_compatible(faa->fa_node, "arasan,sdhci-5.1"))
		sc->sc.sc_signal_voltage = sdhc_fdt_signal_voltage;

	/*
	 * Rockchip RK3399 PHY doesn't like being powered down at low
	 * clock speeds and needs to be powered up explicitly.
	 */
	if (OF_is_compatible(faa->fa_node, "rockchip,rk3399-sdhci-5.1")) {
		/*
		 * The eMMC core's clock multiplier is of no use, so we just
		 * clear it.  Also make sure to set the base clock frequency.
		 */
		freq = clock_get_frequency(faa->fa_node, "clk_xin");
		freq /= 1000 * 1000; /* in MHz */
		phandle = OF_getpropint(faa->fa_node,
		    "arasan,soc-ctl-syscon", 0);
		if (phandle)
			rm = regmap_byphandle(phandle);
		if (rm) {
			regmap_write_4(rm, GRF_EMMCCORE_CON11,
			    GRF_EMMCCORE_CON11_CLOCKMULT_CLR |
			    GRF_EMMCCORE_CON11_CLOCKMULT_VAL(0));
			regmap_write_4(rm, GRF_EMMCCORE_CON0_BASECLOCK,
			    GRF_EMMCCORE_CON0_BASECLOCK_CLR |
			    GRF_EMMCCORE_CON0_BASECLOCK_VAL(freq));
		}
		/* Provide base clock frequency for the PHY driver. */
		sc->sc_cd.cd_node = faa->fa_node;
		sc->sc_cd.cd_cookie = sc;
		sc->sc_cd.cd_get_frequency = sdhc_fdt_get_frequency;
		clock_register(&sc->sc_cd);
		/*
		 * Enable the PHY.  The PHY should be powered on/off in
		 * the bus_clock function, but it's good enough to just
		 * enable it here right away and to keep it powered on.
		 */
		phy_enable(faa->fa_node, "phy_arasan");
		sc->sc.sc_flags |= SDHC_F_NOPWR0;

		/* XXX Doesn't work on Rockchip RK3399. */
		capmask |= (uint64_t)SDHC_DDR50_SUPP << 32;
	}

	if (OF_is_compatible(faa->fa_node, "arasan,sdhci-8.9a")) {
		freq = clock_get_frequency(faa->fa_node, "clk_xin");
		sc->sc.sc_clkbase = freq / 1000;
	}

	if (OF_is_compatible(faa->fa_node, "brcm,bcm2711-emmc2"))
		sc->sc.sc_flags |= SDHC_F_NOPWR0;

	if (OF_is_compatible(faa->fa_node, "brcm,bcm2835-sdhci")) {
		capmask = 0xffffffff;
		capset = SDHC_VOLTAGE_SUPP_3_3V | SDHC_HIGH_SPEED_SUPP;
		capset |= SDHC_MAX_BLK_LEN_1024 << SDHC_MAX_BLK_LEN_SHIFT;

		freq = clock_get_frequency(faa->fa_node, NULL);
		sc->sc.sc_clkbase = freq / 1000;

		sc->sc.sc_flags |= SDHC_F_32BIT_ACCESS;
		sc->sc.sc_flags |= SDHC_F_NO_HS_BIT;
	}

	if (OF_is_compatible(faa->fa_node, "marvell,armada-3700-sdhci") ||
	    OF_is_compatible(faa->fa_node, "marvell,armada-ap806-sdhci") ||
	    OF_is_compatible(faa->fa_node, "marvell,armada-cp110-sdhci")) {
		if (OF_is_compatible(faa->fa_node,
		    "marvell,armada-3700-sdhci")) {
			KASSERT(faa->fa_nreg > 1);
			if (bus_space_map(sc->sc_iot, faa->fa_reg[1].addr,
			    faa->fa_reg[1].size, 0, &sc->sc_pad_ioh)) {
				printf("%s: can't map registers\n",
				    sc->sc.sc_dev.dv_xname);
				return;
			}
			OF_getprop(faa->fa_node, "marvell,pad-type",
			    pad_type, sizeof(pad_type));
			if (!strcmp(pad_type, "fixed-1-8v")) {
				bus_space_write_4(sc->sc_iot, sc->sc_pad_ioh,
				    ARMADA_3700_SOC_PAD_CTL,
				    ARMADA_3700_SOC_PAD_CTL_1_8V);
			} else {
				bus_space_write_4(sc->sc_iot, sc->sc_pad_ioh,
				    ARMADA_3700_SOC_PAD_CTL,
				    ARMADA_3700_SOC_PAD_CTL_3_3V);
				regulator_set_voltage(sc->sc_vqmmc, 3300000);
			}
		}

		if (OF_getpropint(faa->fa_node, "bus-width", 1) != 8)
			capmask |= SDHC_8BIT_MODE_SUPP;
		if (OF_getproplen(faa->fa_node, "no-1-8-v") == 0) {
			capmask |= SDHC_VOLTAGE_SUPP_1_8V;
			capmask |= (uint64_t)SDHC_DDR50_SUPP << 32;
		}
		if (OF_getproplen(faa->fa_node,
		    "marvell,xenon-phy-slow-mode") == 0)
			sc->sc_slow_mode = 1;

		sc->sc_znr = OF_getpropint(faa->fa_node,
		    "marvell,xenon-phy-znr", 0xf);
		sc->sc_znr &= XENON_EMMC_PHY_PAD_CONTROL2_ZNR_MASK;
		sc->sc_zpr = OF_getpropint(faa->fa_node,
		    "marvell,xenon-phy-zpr", 0xf);
		sc->sc_zpr &= XENON_EMMC_PHY_PAD_CONTROL2_ZPR_MASK;
		sc->sc_sdhc_id = OF_getpropint(faa->fa_node,
		    "marvell,xenon-sdhc-id", 0);

		reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
		    XENON_SYS_OP_CTRL);
		reg |= XENON_SYS_OP_CTRL_SLOT_ENABLE(sc->sc_sdhc_id);
		reg &= ~XENON_SYS_OP_CTRL_SDCLK_IDLEOFF_ENABLE(sc->sc_sdhc_id);
		reg &= ~XENON_SYS_OP_CTRL_AUTO_CLKGATE_DISABLE;
		bus_space_write_4(sc->sc_iot, sc->sc_ioh,
		    XENON_SYS_OP_CTRL, reg);
		reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
		    XENON_SYS_EXT_OP_CTRL);
		reg |= XENON_SYS_EXT_OP_CTRL_PARALLEL_TRAN(sc->sc_sdhc_id);
		reg |= XENON_SYS_EXT_OP_CTRL_MASK_CMD_CONFLICT_ERR;
		bus_space_write_4(sc->sc_iot, sc->sc_ioh,
		    XENON_SYS_EXT_OP_CTRL, reg);

		freq = clock_get_frequency(faa->fa_node, NULL);
		sc->sc.sc_clkbase = freq / 1000;
		sc->sc.sc_bus_clock_post = sdhc_fdt_xenon_bus_clock_post;
	}

	sdhc_host_found(&sc->sc, sc->sc_iot, sc->sc_ioh, sc->sc_size, 1,
	    capmask, capset);
	return;

unmap:
	bus_space_unmap(sc->sc_iot, sc->sc_ioh, sc->sc_size);
}

int
sdhc_fdt_card_detect(struct sdhc_softc *ssc)
{
	struct sdhc_fdt_softc *sc = (struct sdhc_fdt_softc *)ssc;

	if (OF_getproplen(sc->sc_node, "non-removable") == 0)
		return 1;

	return gpio_controller_get_pin(sc->sc_gpio);
}

int
sdhc_fdt_signal_voltage(struct sdhc_softc *sc, int signal_voltage)
{
	switch (signal_voltage) {
	case SDMMC_SIGNAL_VOLTAGE_180:
		return 0;
	default:
		return EINVAL;
	}
}

uint32_t
sdhc_fdt_get_frequency(void *cookie, uint32_t *cells)
{
	struct sdhc_fdt_softc *sc = cookie;
	return clock_get_frequency(sc->sc_cd.cd_node, "clk_xin");
}

/* Marvell Xenon */
void
sdhc_fdt_xenon_bus_clock_post(struct sdhc_softc *ssc, int freq, int timing)
{
	struct sdhc_fdt_softc *sc = (struct sdhc_fdt_softc *)ssc;
	uint32_t reg;
	int i;

	if (freq == 0)
		return;

	reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
	    XENON_EMMC_PHY_PAD_CONTROL);
	reg |= (XENON_EMMC_PHY_PAD_CONTROL_FC_DQ_RECEN |
		XENON_EMMC_PHY_PAD_CONTROL_FC_CMD_RECEN |
		XENON_EMMC_PHY_PAD_CONTROL_FC_QSP_RECEN |
		XENON_EMMC_PHY_PAD_CONTROL_FC_QSN_RECEN |
		XENON_EMMC_PHY_PAD_CONTROL_FC_ALL_CMOS_RECVR);
	bus_space_write_4(sc->sc_iot, sc->sc_ioh,
	    XENON_EMMC_PHY_PAD_CONTROL, reg);
	reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
	    XENON_EMMC_PHY_PAD_CONTROL1);
	reg &= ~(XENON_EMMC_PHY_PAD_CONTROL1_FC_CMD_PD |
		XENON_EMMC_PHY_PAD_CONTROL1_FC_DQ_PD);
	reg |= (XENON_EMMC_PHY_PAD_CONTROL1_FC_CMD_PU |
		XENON_EMMC_PHY_PAD_CONTROL1_FC_DQ_PU);
	bus_space_write_4(sc->sc_iot, sc->sc_ioh,
	    XENON_EMMC_PHY_PAD_CONTROL1, reg);

	if (timing == SDMMC_TIMING_LEGACY)
		goto phy_init;

	/* TODO: check for SMF_IO_MODE and set flag */
	reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
	    XENON_EMMC_PHY_TIMING_ADJUST);
	reg &= ~XENON_EMMC_PHY_TIMING_ADJUST_SDIO_MODE;
	bus_space_write_4(sc->sc_iot, sc->sc_ioh,
	    XENON_EMMC_PHY_TIMING_ADJUST, reg);

	reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
	    XENON_EMMC_PHY_PAD_CONTROL2);
	reg &= ~(XENON_EMMC_PHY_PAD_CONTROL2_ZPR_MASK <<
	    XENON_EMMC_PHY_PAD_CONTROL2_ZPR_SHIFT |
	    XENON_EMMC_PHY_PAD_CONTROL2_ZNR_MASK <<
	    XENON_EMMC_PHY_PAD_CONTROL2_ZNR_SHIFT);
	reg |= sc->sc_zpr << XENON_EMMC_PHY_PAD_CONTROL2_ZPR_SHIFT |
	     sc->sc_znr << XENON_EMMC_PHY_PAD_CONTROL2_ZNR_SHIFT;
	bus_space_write_4(sc->sc_iot, sc->sc_ioh,
	    XENON_EMMC_PHY_PAD_CONTROL2, reg);

	reg = bus_space_read_2(sc->sc_iot, sc->sc_ioh, SDHC_CLOCK_CTL);
	reg &= ~SDHC_SDCLK_ENABLE;
	bus_space_write_2(sc->sc_iot, sc->sc_ioh, SDHC_CLOCK_CTL, reg);

	reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
	    XENON_EMMC_PHY_FUNC_CONTROL);
	reg &= ~(XENON_EMMC_PHY_FUNC_CONTROL_DQ_DDR_MODE |
	     XENON_EMMC_PHY_FUNC_CONTROL_CMD_DDR_MODE);
	reg |= XENON_EMMC_PHY_FUNC_CONTROL_DQ_ASYNC_MODE;
	bus_space_write_4(sc->sc_iot, sc->sc_ioh,
	    XENON_EMMC_PHY_FUNC_CONTROL, reg);

	reg = bus_space_read_2(sc->sc_iot, sc->sc_ioh, SDHC_CLOCK_CTL);
	reg |= SDHC_SDCLK_ENABLE;
	bus_space_write_2(sc->sc_iot, sc->sc_ioh, SDHC_CLOCK_CTL, reg);

	reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
	    XENON_SLOT_EMMC_CTRL);
	reg &= ~(XENON_SLOT_EMMC_CTRL_ENABLE_DATA_STROBE |
	    XENON_SLOT_EMMC_CTRL_ENABLE_RESP_STROBE);
	bus_space_write_4(sc->sc_iot, sc->sc_ioh,
	    XENON_SLOT_EMMC_CTRL, reg);

	reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
	    XENON_EMMC_PHY_PAD_CONTROL1);
	reg &= ~(XENON_EMMC_PHY_PAD_CONTROL1_FC_QSP_PD |
		XENON_EMMC_PHY_PAD_CONTROL1_FC_QSP_PU);
	bus_space_write_4(sc->sc_iot, sc->sc_ioh,
	    XENON_EMMC_PHY_PAD_CONTROL1, reg);

phy_init:
	reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
	    XENON_EMMC_PHY_TIMING_ADJUST);
	reg |= XENON_EMMC_PHY_TIMING_ADJUST_SAMPL_INV_QSP_PHASE_SELECT;
	reg &= ~XENON_EMMC_PHY_TIMING_ADJUST_SLOW_MODE;
	if (timing == SDMMC_TIMING_LEGACY ||
	    timing == SDMMC_TIMING_HIGHSPEED || sc->sc_slow_mode)
		reg |= XENON_EMMC_PHY_TIMING_ADJUST_SLOW_MODE;
	bus_space_write_4(sc->sc_iot, sc->sc_ioh,
	    XENON_EMMC_PHY_TIMING_ADJUST, reg);

	reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
	    XENON_EMMC_PHY_TIMING_ADJUST);
	reg |= XENON_EMMC_PHY_TIMING_ADJUST_INIT;
	bus_space_write_4(sc->sc_iot, sc->sc_ioh,
	    XENON_EMMC_PHY_TIMING_ADJUST, reg);

	for (i = 1000; i > 0; i--) {
		reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
		    XENON_EMMC_PHY_TIMING_ADJUST);
		if (!(reg & XENON_EMMC_PHY_TIMING_ADJUST_INIT))
			break;
		delay(10);
	}
	if (i == 0)
		printf("%s: phy initialization timeout\n",
		    sc->sc.sc_dev.dv_xname);

	if (freq > SDMMC_SDCLK_400KHZ) {
		reg = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
		    XENON_SYS_OP_CTRL);
		reg |= XENON_SYS_OP_CTRL_SDCLK_IDLEOFF_ENABLE(sc->sc_sdhc_id);
		bus_space_write_4(sc->sc_iot, sc->sc_ioh,
		    XENON_SYS_OP_CTRL, reg);
	}
}