Skip to content

Commit e4bf1b0

Browse files
xdarklightstorulf
authored andcommitted
mmc: host: meson-mx-sdhc: new driver for the Amlogic Meson SDHC host
The SDHC MMC host controller on Amlogic SoCs provides an eMMC and MMC card interface with 1/4/8-bit bus width. It supports eMMC spec 4.4x/4.5x including HS200 (up to 100MHz clock). The public S805 datasheet [0] contains a short documentation about the registers. Unfortunately it does not describe how to use the registers to make the hardware work. Thus this driver is based on reading (and understanding) the Amlogic 3.10 GPL kernel code. Some hardware details are not easy to see. Jianxin Pan was kind enough to answer my questions: The hardware has built-in busy timeout support. The maximum timeout is 30 seconds. This is only documented in Amlogic's internal documentation. The controller only works with very specific clock configurations. The details are not part of the public datasheet. In my own words the supported configurations are: - 399.812kHz: clkin = 850MHz div = 2126 sd_rx_phase = 63 - 1MHz: clkin = 850MHz div = 850 sd_rx_phase = 55 - 5.986MHz: clkin = 850MHz div = 142 sd_rx_phase = 24 - 25MHz: clkin = 850MHz div = 34 sd_rx_phase = 15 - 47.222MHz: clkin = 850MHz div = 18 sd_rx_phase = 11/15 (SDR50/HS) - 53.125MHz: clkin = 850MHz div = 16 sd_rx_phase = (tuning) - 70.833MHz: clkin = 850MHz div = 12 sd_rx_phase = (tuning) - 85MHz: clkin = 850MHz div = 10 sd_rx_phase = (tuning) - 94.44MHz: clkin = 850MHz div = 9 sd_rx_phase = (tuning) - 106.25MHz: clkin = 850MHz div = 8 sd_rx_phase = (tuning) - 127.5MHz: clkin = 1275MHz div = 10 sd_rx_phase = (tuning) - 141.667MHz: clkin = 850MHz div = 6 sd_rx_phase = (tuning) - 159.375MHz: clkin = 1275MHz div = 8 sd_rx_phase = (tuning) - 212.5MHz: clkin = 1275MHz div = 6 sd_rx_phase = (tuning) - (sd_tx_phase is always 1, 94.44MHz is not listed in the datasheet but this is what the 3.10 BSP kernel on Odroid-C1 actually uses) NOTE: CMD23 support is disabled for now because it results in command timeouts and thus decreases read performance. Tested-by: Wei Wang <[email protected]> Tested-by: Xin Yin <[email protected]> Reviewed-by: Xin Yin <[email protected]> Tested-by: Anand Moon <[email protected]> Signed-off-by: Martin Blumenstingl <[email protected]> Reviewed-by: Jerome Brunet <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Ulf Hansson <[email protected]>
1 parent e5f3137 commit e4bf1b0

File tree

5 files changed

+1221
-0
lines changed

5 files changed

+1221
-0
lines changed

drivers/mmc/host/Kconfig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,20 @@ config MMC_MESON_GX
405405

406406
If you have a controller with this interface, say Y here.
407407

408+
config MMC_MESON_MX_SDHC
409+
tristate "Amlogic Meson SDHC Host Controller support"
410+
depends on (ARM && ARCH_MESON) || COMPILE_TEST
411+
depends on COMMON_CLK
412+
depends on OF
413+
help
414+
This selects support for the SDHC Host Controller on
415+
Amlogic Meson6, Meson8, Meson8b and Meson8m2 SoCs.
416+
The controller supports the SD/SDIO Spec 3.x and eMMC Spec 4.5x
417+
with 1, 4, and 8 bit bus widths.
418+
419+
If you have a controller with this interface, say Y or M here.
420+
If unsure, say N.
421+
408422
config MMC_MESON_MX_SDIO
409423
tristate "Amlogic Meson6/Meson8/Meson8b SD/MMC Host Controller support"
410424
depends on ARCH_MESON || COMPILE_TEST

drivers/mmc/host/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ obj-$(CONFIG_MMC_VUB300) += vub300.o
6868
obj-$(CONFIG_MMC_USHC) += ushc.o
6969
obj-$(CONFIG_MMC_WMT) += wmt-sdmmc.o
7070
obj-$(CONFIG_MMC_MESON_GX) += meson-gx-mmc.o
71+
obj-$(CONFIG_MMC_MESON_MX_SDHC) += meson-mx-sdhc-clkc.o meson-mx-sdhc.o
7172
obj-$(CONFIG_MMC_MESON_MX_SDIO) += meson-mx-sdio.o
7273
obj-$(CONFIG_MMC_MOXART) += moxart-mmc.o
7374
obj-$(CONFIG_MMC_SUNXI) += sunxi-mmc.o

drivers/mmc/host/meson-mx-sdhc-clkc.c

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// SPDX-License-Identifier: GPL-2.0+
2+
/*
3+
* Amlogic Meson SDHC clock controller
4+
*
5+
* Copyright (C) 2020 Martin Blumenstingl <[email protected]>
6+
*/
7+
8+
#include <linux/clk.h>
9+
#include <linux/clk-provider.h>
10+
#include <linux/device.h>
11+
#include <linux/platform_device.h>
12+
13+
#include "meson-mx-sdhc.h"
14+
15+
#define MESON_SDHC_NUM_BUILTIN_CLKS 6
16+
17+
struct meson_mx_sdhc_clkc {
18+
struct clk_mux src_sel;
19+
struct clk_divider div;
20+
struct clk_gate mod_clk_en;
21+
struct clk_gate tx_clk_en;
22+
struct clk_gate rx_clk_en;
23+
struct clk_gate sd_clk_en;
24+
};
25+
26+
static const struct clk_parent_data meson_mx_sdhc_src_sel_parents[4] = {
27+
{ .fw_name = "clkin0" },
28+
{ .fw_name = "clkin1" },
29+
{ .fw_name = "clkin2" },
30+
{ .fw_name = "clkin3" },
31+
};
32+
33+
static const struct clk_div_table meson_mx_sdhc_div_table[] = {
34+
{ .div = 6, .val = 5, },
35+
{ .div = 8, .val = 7, },
36+
{ .div = 9, .val = 8, },
37+
{ .div = 10, .val = 9, },
38+
{ .div = 12, .val = 11, },
39+
{ .div = 16, .val = 15, },
40+
{ .div = 18, .val = 17, },
41+
{ .div = 34, .val = 33, },
42+
{ .div = 142, .val = 141, },
43+
{ .div = 850, .val = 849, },
44+
{ .div = 2126, .val = 2125, },
45+
{ .div = 4096, .val = 4095, },
46+
{ /* sentinel */ }
47+
};
48+
49+
static int meson_mx_sdhc_clk_hw_register(struct device *dev,
50+
const char *name_suffix,
51+
const struct clk_parent_data *parents,
52+
unsigned int num_parents,
53+
const struct clk_ops *ops,
54+
struct clk_hw *hw)
55+
{
56+
struct clk_init_data init = { 0 };
57+
char clk_name[32];
58+
59+
snprintf(clk_name, sizeof(clk_name), "%s#%s", dev_name(dev),
60+
name_suffix);
61+
62+
init.name = clk_name;
63+
init.ops = ops;
64+
init.flags = CLK_SET_RATE_PARENT;
65+
init.parent_data = parents;
66+
init.num_parents = num_parents;
67+
68+
hw->init = &init;
69+
70+
return devm_clk_hw_register(dev, hw);
71+
}
72+
73+
static int meson_mx_sdhc_gate_clk_hw_register(struct device *dev,
74+
const char *name_suffix,
75+
struct clk_hw *parent,
76+
struct clk_hw *hw)
77+
{
78+
struct clk_parent_data parent_data = { .hw = parent };
79+
80+
return meson_mx_sdhc_clk_hw_register(dev, name_suffix, &parent_data, 1,
81+
&clk_gate_ops, hw);
82+
}
83+
84+
int meson_mx_sdhc_register_clkc(struct device *dev, void __iomem *base,
85+
struct clk_bulk_data *clk_bulk_data)
86+
{
87+
struct clk_parent_data div_parent = { 0 };
88+
struct meson_mx_sdhc_clkc *clkc_data;
89+
int ret;
90+
91+
clkc_data = devm_kzalloc(dev, sizeof(*clkc_data), GFP_KERNEL);
92+
if (!clkc_data)
93+
return -ENOMEM;
94+
95+
clkc_data->src_sel.reg = base + MESON_SDHC_CLKC;
96+
clkc_data->src_sel.mask = 0x3;
97+
clkc_data->src_sel.shift = 16;
98+
ret = meson_mx_sdhc_clk_hw_register(dev, "src_sel",
99+
meson_mx_sdhc_src_sel_parents, 4,
100+
&clk_mux_ops,
101+
&clkc_data->src_sel.hw);
102+
if (ret)
103+
return ret;
104+
105+
clkc_data->div.reg = base + MESON_SDHC_CLKC;
106+
clkc_data->div.shift = 0;
107+
clkc_data->div.width = 12;
108+
clkc_data->div.table = meson_mx_sdhc_div_table;
109+
div_parent.hw = &clkc_data->src_sel.hw;
110+
ret = meson_mx_sdhc_clk_hw_register(dev, "div", &div_parent, 1,
111+
&clk_divider_ops,
112+
&clkc_data->div.hw);
113+
if (ret)
114+
return ret;
115+
116+
clkc_data->mod_clk_en.reg = base + MESON_SDHC_CLKC;
117+
clkc_data->mod_clk_en.bit_idx = 15;
118+
ret = meson_mx_sdhc_gate_clk_hw_register(dev, "mod_clk_on",
119+
&clkc_data->div.hw,
120+
&clkc_data->mod_clk_en.hw);
121+
if (ret)
122+
return ret;
123+
124+
clkc_data->tx_clk_en.reg = base + MESON_SDHC_CLKC;
125+
clkc_data->tx_clk_en.bit_idx = 14;
126+
ret = meson_mx_sdhc_gate_clk_hw_register(dev, "tx_clk_on",
127+
&clkc_data->div.hw,
128+
&clkc_data->tx_clk_en.hw);
129+
if (ret)
130+
return ret;
131+
132+
clkc_data->rx_clk_en.reg = base + MESON_SDHC_CLKC;
133+
clkc_data->rx_clk_en.bit_idx = 13;
134+
ret = meson_mx_sdhc_gate_clk_hw_register(dev, "rx_clk_on",
135+
&clkc_data->div.hw,
136+
&clkc_data->rx_clk_en.hw);
137+
if (ret)
138+
return ret;
139+
140+
clkc_data->sd_clk_en.reg = base + MESON_SDHC_CLKC;
141+
clkc_data->sd_clk_en.bit_idx = 12;
142+
ret = meson_mx_sdhc_gate_clk_hw_register(dev, "sd_clk_on",
143+
&clkc_data->div.hw,
144+
&clkc_data->sd_clk_en.hw);
145+
if (ret)
146+
return ret;
147+
148+
/*
149+
* TODO: Replace clk_hw.clk with devm_clk_hw_get_clk() once that is
150+
* available.
151+
*/
152+
clk_bulk_data[0].clk = clkc_data->mod_clk_en.hw.clk;
153+
clk_bulk_data[1].clk = clkc_data->sd_clk_en.hw.clk;
154+
clk_bulk_data[2].clk = clkc_data->tx_clk_en.hw.clk;
155+
clk_bulk_data[3].clk = clkc_data->rx_clk_en.hw.clk;
156+
157+
return 0;
158+
}

0 commit comments

Comments
 (0)