Commit | Line | Data |
---|---|---|
c942fddf | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
7a6fca87 CYT |
2 | /* |
3 | * Copyright 2015 Chen-Yu Tsai | |
4 | * | |
5 | * Chen-Yu Tsai <wens@csie.org> | |
7a6fca87 CYT |
6 | */ |
7 | ||
9dfefe8c | 8 | #include <linux/clk.h> |
7a6fca87 | 9 | #include <linux/clk-provider.h> |
61d2f2a0 | 10 | #include <linux/delay.h> |
439a36d7 | 11 | #include <linux/init.h> |
62e59c4e | 12 | #include <linux/io.h> |
7a6fca87 | 13 | #include <linux/of.h> |
7a6fca87 CYT |
14 | #include <linux/reset.h> |
15 | #include <linux/platform_device.h> | |
16 | #include <linux/reset-controller.h> | |
9dfefe8c | 17 | #include <linux/slab.h> |
7a6fca87 CYT |
18 | #include <linux/spinlock.h> |
19 | ||
20 | #define SUN9I_MMC_WIDTH 4 | |
21 | ||
22 | #define SUN9I_MMC_GATE_BIT 16 | |
23 | #define SUN9I_MMC_RESET_BIT 18 | |
24 | ||
25 | struct sun9i_mmc_clk_data { | |
26 | spinlock_t lock; | |
27 | void __iomem *membase; | |
28 | struct clk *clk; | |
29 | struct reset_control *reset; | |
30 | struct clk_onecell_data clk_data; | |
31 | struct reset_controller_dev rcdev; | |
32 | }; | |
33 | ||
34 | static int sun9i_mmc_reset_assert(struct reset_controller_dev *rcdev, | |
35 | unsigned long id) | |
36 | { | |
37 | struct sun9i_mmc_clk_data *data = container_of(rcdev, | |
38 | struct sun9i_mmc_clk_data, | |
39 | rcdev); | |
40 | unsigned long flags; | |
41 | void __iomem *reg = data->membase + SUN9I_MMC_WIDTH * id; | |
42 | u32 val; | |
43 | ||
44 | clk_prepare_enable(data->clk); | |
45 | spin_lock_irqsave(&data->lock, flags); | |
46 | ||
47 | val = readl(reg); | |
48 | writel(val & ~BIT(SUN9I_MMC_RESET_BIT), reg); | |
49 | ||
50 | spin_unlock_irqrestore(&data->lock, flags); | |
51 | clk_disable_unprepare(data->clk); | |
52 | ||
53 | return 0; | |
54 | } | |
55 | ||
56 | static int sun9i_mmc_reset_deassert(struct reset_controller_dev *rcdev, | |
57 | unsigned long id) | |
58 | { | |
59 | struct sun9i_mmc_clk_data *data = container_of(rcdev, | |
60 | struct sun9i_mmc_clk_data, | |
61 | rcdev); | |
62 | unsigned long flags; | |
63 | void __iomem *reg = data->membase + SUN9I_MMC_WIDTH * id; | |
64 | u32 val; | |
65 | ||
66 | clk_prepare_enable(data->clk); | |
67 | spin_lock_irqsave(&data->lock, flags); | |
68 | ||
69 | val = readl(reg); | |
70 | writel(val | BIT(SUN9I_MMC_RESET_BIT), reg); | |
71 | ||
72 | spin_unlock_irqrestore(&data->lock, flags); | |
73 | clk_disable_unprepare(data->clk); | |
74 | ||
75 | return 0; | |
76 | } | |
77 | ||
61d2f2a0 CYT |
78 | static int sun9i_mmc_reset_reset(struct reset_controller_dev *rcdev, |
79 | unsigned long id) | |
80 | { | |
81 | sun9i_mmc_reset_assert(rcdev, id); | |
82 | udelay(10); | |
83 | sun9i_mmc_reset_deassert(rcdev, id); | |
84 | ||
85 | return 0; | |
86 | } | |
87 | ||
5e7bc9c6 | 88 | static const struct reset_control_ops sun9i_mmc_reset_ops = { |
7a6fca87 CYT |
89 | .assert = sun9i_mmc_reset_assert, |
90 | .deassert = sun9i_mmc_reset_deassert, | |
61d2f2a0 | 91 | .reset = sun9i_mmc_reset_reset, |
7a6fca87 CYT |
92 | }; |
93 | ||
94 | static int sun9i_a80_mmc_config_clk_probe(struct platform_device *pdev) | |
95 | { | |
96 | struct device_node *np = pdev->dev.of_node; | |
97 | struct sun9i_mmc_clk_data *data; | |
98 | struct clk_onecell_data *clk_data; | |
99 | const char *clk_name = np->name; | |
100 | const char *clk_parent; | |
101 | struct resource *r; | |
102 | int count, i, ret; | |
103 | ||
104 | data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); | |
105 | if (!data) | |
106 | return -ENOMEM; | |
107 | ||
108 | spin_lock_init(&data->lock); | |
109 | ||
170d5882 | 110 | data->membase = devm_platform_get_and_ioremap_resource(pdev, 0, &r); |
7a6fca87 CYT |
111 | if (IS_ERR(data->membase)) |
112 | return PTR_ERR(data->membase); | |
113 | ||
170d5882 YL |
114 | /* one clock/reset pair per word */ |
115 | count = DIV_ROUND_UP((resource_size(r)), SUN9I_MMC_WIDTH); | |
116 | ||
7a6fca87 CYT |
117 | clk_data = &data->clk_data; |
118 | clk_data->clk_num = count; | |
119 | clk_data->clks = devm_kcalloc(&pdev->dev, count, sizeof(struct clk *), | |
120 | GFP_KERNEL); | |
121 | if (!clk_data->clks) | |
122 | return -ENOMEM; | |
123 | ||
124 | data->clk = devm_clk_get(&pdev->dev, NULL); | |
125 | if (IS_ERR(data->clk)) { | |
126 | dev_err(&pdev->dev, "Could not get clock\n"); | |
127 | return PTR_ERR(data->clk); | |
128 | } | |
129 | ||
cb92a19b | 130 | data->reset = devm_reset_control_get_exclusive(&pdev->dev, NULL); |
7a6fca87 CYT |
131 | if (IS_ERR(data->reset)) { |
132 | dev_err(&pdev->dev, "Could not get reset control\n"); | |
133 | return PTR_ERR(data->reset); | |
134 | } | |
135 | ||
136 | ret = reset_control_deassert(data->reset); | |
137 | if (ret) { | |
138 | dev_err(&pdev->dev, "Reset deassert err %d\n", ret); | |
139 | return ret; | |
140 | } | |
141 | ||
142 | clk_parent = __clk_get_name(data->clk); | |
143 | for (i = 0; i < count; i++) { | |
144 | of_property_read_string_index(np, "clock-output-names", | |
145 | i, &clk_name); | |
146 | ||
147 | clk_data->clks[i] = clk_register_gate(&pdev->dev, clk_name, | |
148 | clk_parent, 0, | |
149 | data->membase + SUN9I_MMC_WIDTH * i, | |
150 | SUN9I_MMC_GATE_BIT, 0, | |
151 | &data->lock); | |
152 | ||
153 | if (IS_ERR(clk_data->clks[i])) { | |
154 | ret = PTR_ERR(clk_data->clks[i]); | |
155 | goto err_clk_register; | |
156 | } | |
157 | } | |
158 | ||
159 | ret = of_clk_add_provider(np, of_clk_src_onecell_get, clk_data); | |
160 | if (ret) | |
161 | goto err_clk_provider; | |
162 | ||
163 | data->rcdev.owner = THIS_MODULE; | |
164 | data->rcdev.nr_resets = count; | |
165 | data->rcdev.ops = &sun9i_mmc_reset_ops; | |
166 | data->rcdev.of_node = pdev->dev.of_node; | |
167 | ||
168 | ret = reset_controller_register(&data->rcdev); | |
169 | if (ret) | |
170 | goto err_rc_reg; | |
171 | ||
172 | platform_set_drvdata(pdev, data); | |
173 | ||
174 | return 0; | |
175 | ||
176 | err_rc_reg: | |
177 | of_clk_del_provider(np); | |
178 | ||
179 | err_clk_provider: | |
180 | for (i = 0; i < count; i++) | |
181 | clk_unregister(clk_data->clks[i]); | |
182 | ||
183 | err_clk_register: | |
184 | reset_control_assert(data->reset); | |
185 | ||
186 | return ret; | |
187 | } | |
188 | ||
7a6fca87 CYT |
189 | static const struct of_device_id sun9i_a80_mmc_config_clk_dt_ids[] = { |
190 | { .compatible = "allwinner,sun9i-a80-mmc-config-clk" }, | |
191 | { /* sentinel */ } | |
192 | }; | |
193 | ||
194 | static struct platform_driver sun9i_a80_mmc_config_clk_driver = { | |
195 | .driver = { | |
196 | .name = "sun9i-a80-mmc-config-clk", | |
439a36d7 | 197 | .suppress_bind_attrs = true, |
7a6fca87 CYT |
198 | .of_match_table = sun9i_a80_mmc_config_clk_dt_ids, |
199 | }, | |
200 | .probe = sun9i_a80_mmc_config_clk_probe, | |
7a6fca87 | 201 | }; |
439a36d7 | 202 | builtin_platform_driver(sun9i_a80_mmc_config_clk_driver); |