summaryrefslogtreecommitdiff
path: root/drivers/mmc/socfpga_dw_mmc.c
blob: 8076761e06e0d44bcc0924864d94c40c450a434e (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
/*
 * (C) Copyright 2013 Altera Corporation <www.altera.com>
 *
 * SPDX-License-Identifier:	GPL-2.0+
 */

#include <common.h>
#include <malloc.h>
#include <fdtdec.h>
#include <libfdt.h>
#include <dwmmc.h>
#include <errno.h>
#include <asm/arch/dwmmc.h>
#include <asm/arch/clock_manager.h>
#include <asm/arch/system_manager.h>

static const struct socfpga_clock_manager *clock_manager_base =
		(void *)SOCFPGA_CLKMGR_ADDRESS;
static const struct socfpga_system_manager *system_manager_base =
		(void *)SOCFPGA_SYSMGR_ADDRESS;

static void socfpga_dwmci_clksel(struct dwmci_host *host)
{
	unsigned int drvsel;
	unsigned int smplsel;

	/* Disable SDMMC clock. */
	clrbits_le32(&clock_manager_base->per_pll.en,
		CLKMGR_PERPLLGRP_EN_SDMMCCLK_MASK);

	/* Configures drv_sel and smpl_sel */
	drvsel = CONFIG_SOCFPGA_DWMMC_DRVSEL;
	smplsel = CONFIG_SOCFPGA_DWMMC_SMPSEL;

	debug("%s: drvsel %d smplsel %d\n", __func__, drvsel, smplsel);
	writel(SYSMGR_SDMMC_CTRL_SET(smplsel, drvsel),
		&system_manager_base->sdmmcgrp_ctrl);

	debug("%s: SYSMGR_SDMMCGRP_CTRL_REG = 0x%x\n", __func__,
		readl(&system_manager_base->sdmmcgrp_ctrl));

	/* Enable SDMMC clock */
	setbits_le32(&clock_manager_base->per_pll.en,
		CLKMGR_PERPLLGRP_EN_SDMMCCLK_MASK);
}

static int socfpga_dwmci_of_probe(const void *blob, int node, const int idx)
{
	/* FIXME: probe from DT eventually too/ */
	const unsigned long clk = cm_get_mmc_controller_clk_hz();

	struct dwmci_host *host;
	fdt_addr_t reg_base;
	int bus_width, fifo_depth;

	if (clk == 0) {
		printf("DWMMC%d: MMC clock is zero!", idx);
		return -EINVAL;
	}

	/* Get the register address from the device node */
	reg_base = fdtdec_get_addr(blob, node, "reg");
	if (!reg_base) {
		printf("DWMMC%d: Can't get base address\n", idx);
		return -EINVAL;
	}

	/* Get the bus width from the device node */
	bus_width = fdtdec_get_int(blob, node, "bus-width", 0);
	if (bus_width <= 0) {
		printf("DWMMC%d: Can't get bus-width\n", idx);
		return -EINVAL;
	}

	fifo_depth = fdtdec_get_int(blob, node, "fifo-depth", 0);
	if (fifo_depth < 0) {
		printf("DWMMC%d: Can't get FIFO depth\n", idx);
		return -EINVAL;
	}

	/* Allocate the host */
	host = calloc(1, sizeof(*host));
	if (!host)
		return -ENOMEM;

	host->name = "SOCFPGA DWMMC";
	host->ioaddr = (void *)reg_base;
	host->buswidth = bus_width;
	host->clksel = socfpga_dwmci_clksel;
	host->dev_index = idx;
	/* Fixed clock divide by 4 which due to the SDMMC wrapper */
	host->bus_hz = clk;
	host->fifoth_val = MSIZE(0x2) |
		RX_WMARK(fifo_depth / 2 - 1) | TX_WMARK(fifo_depth / 2);

	return add_dwmci(host, host->bus_hz, 400000);
}

static int socfpga_dwmci_process_node(const void *blob, int nodes[],
				      int count)
{
	int i, node, ret;

	for (i = 0; i < count; i++) {
		node = nodes[i];
		if (node <= 0)
			continue;

		ret = socfpga_dwmci_of_probe(blob, node, i);
		if (ret) {
			printf("%s: failed to decode dev %d\n", __func__, i);
			return ret;
		}
	}
	return 0;
}

int socfpga_dwmmc_init(const void *blob)
{
	int nodes[2];	/* Max. two controllers. */
	int ret, count;

	count = fdtdec_find_aliases_for_id(blob, "mmc",
					   COMPAT_ALTERA_SOCFPGA_DWMMC,
					   nodes, ARRAY_SIZE(nodes));

	ret = socfpga_dwmci_process_node(blob, nodes, count);

	return ret;
}