summaryrefslogtreecommitdiffstats
path: root/drivers/mmc/host/meson-mx-sdhc-clkc.c
blob: e1f29b2791235178d665b24547896d6bacd57c47 (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
// SPDX-License-Identifier: GPL-2.0+
/*
 * Amlogic Meson SDHC clock controller
 *
 * Copyright (C) 2020 Martin Blumenstingl <martin.blumenstingl@googlemail.com>
 */

#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/device.h>
#include <linux/platform_device.h>

#include "meson-mx-sdhc.h"

#define MESON_SDHC_NUM_BUILTIN_CLKS	6

struct meson_mx_sdhc_clkc {
	struct clk_mux			src_sel;
	struct clk_divider		div;
	struct clk_gate			mod_clk_en;
	struct clk_gate			tx_clk_en;
	struct clk_gate			rx_clk_en;
	struct clk_gate			sd_clk_en;
};

static const struct clk_parent_data meson_mx_sdhc_src_sel_parents[4] = {
	{ .fw_name = "clkin0" },
	{ .fw_name = "clkin1" },
	{ .fw_name = "clkin2" },
	{ .fw_name = "clkin3" },
};

static const struct clk_div_table meson_mx_sdhc_div_table[] = {
	{ .div = 6, .val = 5, },
	{ .div = 8, .val = 7, },
	{ .div = 9, .val = 8, },
	{ .div = 10, .val = 9, },
	{ .div = 12, .val = 11, },
	{ .div = 16, .val = 15, },
	{ .div = 18, .val = 17, },
	{ .div = 34, .val = 33, },
	{ .div = 142, .val = 141, },
	{ .div = 850, .val = 849, },
	{ .div = 2126, .val = 2125, },
	{ .div = 4096, .val = 4095, },
	{ /* sentinel */ }
};

static int meson_mx_sdhc_clk_hw_register(struct device *dev,
					 const char *name_suffix,
					 const struct clk_parent_data *parents,
					 unsigned int num_parents,
					 const struct clk_ops *ops,
					 struct clk_hw *hw)
{
	struct clk_init_data init = { };
	char clk_name[32];

	snprintf(clk_name, sizeof(clk_name), "%s#%s", dev_name(dev),
		 name_suffix);

	init.name = clk_name;
	init.ops = ops;
	init.flags = CLK_SET_RATE_PARENT;
	init.parent_data = parents;
	init.num_parents = num_parents;

	hw->init = &init;

	return devm_clk_hw_register(dev, hw);
}

static int meson_mx_sdhc_gate_clk_hw_register(struct device *dev,
					      const char *name_suffix,
					      struct clk_hw *parent,
					      struct clk_hw *hw)
{
	struct clk_parent_data parent_data = { .hw = parent };

	return meson_mx_sdhc_clk_hw_register(dev, name_suffix, &parent_data, 1,
					     &clk_gate_ops, hw);
}

int meson_mx_sdhc_register_clkc(struct device *dev, void __iomem *base,
				struct clk_bulk_data *clk_bulk_data)
{
	struct clk_parent_data div_parent = { };
	struct meson_mx_sdhc_clkc *clkc_data;
	int ret;

	clkc_data = devm_kzalloc(dev, sizeof(*clkc_data), GFP_KERNEL);
	if (!clkc_data)
		return -ENOMEM;

	clkc_data->src_sel.reg = base + MESON_SDHC_CLKC;
	clkc_data->src_sel.mask = 0x3;
	clkc_data->src_sel.shift = 16;
	ret = meson_mx_sdhc_clk_hw_register(dev, "src_sel",
					    meson_mx_sdhc_src_sel_parents, 4,
					    &clk_mux_ops,
					    &clkc_data->src_sel.hw);
	if (ret)
		return ret;

	clkc_data->div.reg = base + MESON_SDHC_CLKC;
	clkc_data->div.shift = 0;
	clkc_data->div.width = 12;
	clkc_data->div.table = meson_mx_sdhc_div_table;
	div_parent.hw = &clkc_data->src_sel.hw;
	ret = meson_mx_sdhc_clk_hw_register(dev, "div", &div_parent, 1,
					    &clk_divider_ops,
					    &clkc_data->div.hw);
	if (ret)
		return ret;

	clkc_data->mod_clk_en.reg = base + MESON_SDHC_CLKC;
	clkc_data->mod_clk_en.bit_idx = 15;
	ret = meson_mx_sdhc_gate_clk_hw_register(dev, "mod_clk_on",
						 &clkc_data->div.hw,
						 &clkc_data->mod_clk_en.hw);
	if (ret)
		return ret;

	clkc_data->tx_clk_en.reg = base + MESON_SDHC_CLKC;
	clkc_data->tx_clk_en.bit_idx = 14;
	ret = meson_mx_sdhc_gate_clk_hw_register(dev, "tx_clk_on",
						 &clkc_data->div.hw,
						 &clkc_data->tx_clk_en.hw);
	if (ret)
		return ret;

	clkc_data->rx_clk_en.reg = base + MESON_SDHC_CLKC;
	clkc_data->rx_clk_en.bit_idx = 13;
	ret = meson_mx_sdhc_gate_clk_hw_register(dev, "rx_clk_on",
						 &clkc_data->div.hw,
						 &clkc_data->rx_clk_en.hw);
	if (ret)
		return ret;

	clkc_data->sd_clk_en.reg = base + MESON_SDHC_CLKC;
	clkc_data->sd_clk_en.bit_idx = 12;
	ret = meson_mx_sdhc_gate_clk_hw_register(dev, "sd_clk_on",
						 &clkc_data->div.hw,
						 &clkc_data->sd_clk_en.hw);
	if (ret)
		return ret;

	/*
	 * TODO: Replace clk_hw.clk with devm_clk_hw_get_clk() once that is
	 * available.
	 */
	clk_bulk_data[0].clk = clkc_data->mod_clk_en.hw.clk;
	clk_bulk_data[1].clk = clkc_data->sd_clk_en.hw.clk;
	clk_bulk_data[2].clk = clkc_data->tx_clk_en.hw.clk;
	clk_bulk_data[3].clk = clkc_data->rx_clk_en.hw.clk;

	return 0;
}