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