|
10 | 10 | */
|
11 | 11 | #include <linux/clk.h>
|
12 | 12 | #include <linux/of_address.h>
|
| 13 | +#include <linux/of_device.h> |
| 14 | +#include <linux/pm_runtime.h> |
| 15 | +#include <linux/slab.h> |
13 | 16 |
|
14 | 17 | #include "clk-exynos-arm64.h"
|
15 | 18 |
|
|
21 | 24 | #define GATE_OFF_START 0x2000
|
22 | 25 | #define GATE_OFF_END 0x2fff
|
23 | 26 |
|
| 27 | +struct exynos_arm64_cmu_data { |
| 28 | + struct samsung_clk_reg_dump *clk_save; |
| 29 | + unsigned int nr_clk_save; |
| 30 | + const struct samsung_clk_reg_dump *clk_suspend; |
| 31 | + unsigned int nr_clk_suspend; |
| 32 | + |
| 33 | + struct clk *clk; |
| 34 | + struct clk **pclks; |
| 35 | + int nr_pclks; |
| 36 | + |
| 37 | + struct samsung_clk_provider *ctx; |
| 38 | +}; |
| 39 | + |
24 | 40 | /**
|
25 | 41 | * exynos_arm64_init_clocks - Set clocks initial configuration
|
26 | 42 | * @np: CMU device tree node with "reg" property (CMU addr)
|
@@ -76,17 +92,63 @@ static int __init exynos_arm64_enable_bus_clk(struct device *dev,
|
76 | 92 | if (!cmu->clk_name)
|
77 | 93 | return 0;
|
78 | 94 |
|
79 |
| - if (dev) |
| 95 | + if (dev) { |
| 96 | + struct exynos_arm64_cmu_data *data; |
| 97 | + |
80 | 98 | parent_clk = clk_get(dev, cmu->clk_name);
|
81 |
| - else |
| 99 | + data = dev_get_drvdata(dev); |
| 100 | + if (data) |
| 101 | + data->clk = parent_clk; |
| 102 | + } else { |
82 | 103 | parent_clk = of_clk_get_by_name(np, cmu->clk_name);
|
| 104 | + } |
83 | 105 |
|
84 | 106 | if (IS_ERR(parent_clk))
|
85 | 107 | return PTR_ERR(parent_clk);
|
86 | 108 |
|
87 | 109 | return clk_prepare_enable(parent_clk);
|
88 | 110 | }
|
89 | 111 |
|
| 112 | +static int __init exynos_arm64_cmu_prepare_pm(struct device *dev, |
| 113 | + const struct samsung_cmu_info *cmu) |
| 114 | +{ |
| 115 | + struct exynos_arm64_cmu_data *data = dev_get_drvdata(dev); |
| 116 | + int i; |
| 117 | + |
| 118 | + data->clk_save = samsung_clk_alloc_reg_dump(cmu->clk_regs, |
| 119 | + cmu->nr_clk_regs); |
| 120 | + if (!data->clk_save) |
| 121 | + return -ENOMEM; |
| 122 | + |
| 123 | + data->nr_clk_save = cmu->nr_clk_regs; |
| 124 | + data->clk_suspend = cmu->suspend_regs; |
| 125 | + data->nr_clk_suspend = cmu->nr_suspend_regs; |
| 126 | + data->nr_pclks = of_clk_get_parent_count(dev->of_node); |
| 127 | + if (!data->nr_pclks) |
| 128 | + return 0; |
| 129 | + |
| 130 | + data->pclks = devm_kcalloc(dev, sizeof(struct clk *), data->nr_pclks, |
| 131 | + GFP_KERNEL); |
| 132 | + if (!data->pclks) { |
| 133 | + kfree(data->clk_save); |
| 134 | + return -ENOMEM; |
| 135 | + } |
| 136 | + |
| 137 | + for (i = 0; i < data->nr_pclks; i++) { |
| 138 | + struct clk *clk = of_clk_get(dev->of_node, i); |
| 139 | + |
| 140 | + if (IS_ERR(clk)) { |
| 141 | + kfree(data->clk_save); |
| 142 | + while (--i >= 0) |
| 143 | + clk_put(data->pclks[i]); |
| 144 | + return PTR_ERR(clk); |
| 145 | + } |
| 146 | + data->pclks[i] = clk; |
| 147 | + } |
| 148 | + |
| 149 | + return 0; |
| 150 | +} |
| 151 | + |
90 | 152 | /**
|
91 | 153 | * exynos_arm64_register_cmu - Register specified Exynos CMU domain
|
92 | 154 | * @dev: Device object; may be NULL if this function is not being
|
@@ -117,3 +179,113 @@ void __init exynos_arm64_register_cmu(struct device *dev,
|
117 | 179 | exynos_arm64_init_clocks(np, cmu->clk_regs, cmu->nr_clk_regs);
|
118 | 180 | samsung_cmu_register_one(np, cmu);
|
119 | 181 | }
|
| 182 | + |
| 183 | +/** |
| 184 | + * exynos_arm64_register_cmu_pm - Register Exynos CMU domain with PM support |
| 185 | + * |
| 186 | + * @pdev: Platform device object |
| 187 | + * @set_manual: If true, set gate clocks to manual mode |
| 188 | + * |
| 189 | + * It's a version of exynos_arm64_register_cmu() with PM support. Should be |
| 190 | + * called from probe function of platform driver. |
| 191 | + * |
| 192 | + * Return: 0 on success, or negative error code on error. |
| 193 | + */ |
| 194 | +int __init exynos_arm64_register_cmu_pm(struct platform_device *pdev, |
| 195 | + bool set_manual) |
| 196 | +{ |
| 197 | + const struct samsung_cmu_info *cmu; |
| 198 | + struct device *dev = &pdev->dev; |
| 199 | + struct device_node *np = dev->of_node; |
| 200 | + struct exynos_arm64_cmu_data *data; |
| 201 | + void __iomem *reg_base; |
| 202 | + int ret; |
| 203 | + |
| 204 | + cmu = of_device_get_match_data(dev); |
| 205 | + |
| 206 | + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); |
| 207 | + if (!data) |
| 208 | + return -ENOMEM; |
| 209 | + |
| 210 | + platform_set_drvdata(pdev, data); |
| 211 | + |
| 212 | + ret = exynos_arm64_cmu_prepare_pm(dev, cmu); |
| 213 | + if (ret) |
| 214 | + return ret; |
| 215 | + |
| 216 | + /* |
| 217 | + * Try to boot even if the parent clock enablement fails, as it might be |
| 218 | + * already enabled by bootloader. |
| 219 | + */ |
| 220 | + ret = exynos_arm64_enable_bus_clk(dev, NULL, cmu); |
| 221 | + if (ret) |
| 222 | + dev_err(dev, "%s: could not enable bus clock %s; err = %d\n", |
| 223 | + __func__, cmu->clk_name, ret); |
| 224 | + |
| 225 | + if (set_manual) |
| 226 | + exynos_arm64_init_clocks(np, cmu->clk_regs, cmu->nr_clk_regs); |
| 227 | + |
| 228 | + reg_base = devm_platform_ioremap_resource(pdev, 0); |
| 229 | + if (IS_ERR(reg_base)) |
| 230 | + return PTR_ERR(reg_base); |
| 231 | + |
| 232 | + data->ctx = samsung_clk_init(dev, reg_base, cmu->nr_clk_ids); |
| 233 | + |
| 234 | + /* |
| 235 | + * Enable runtime PM here to allow the clock core using runtime PM |
| 236 | + * for the registered clocks. Additionally, we increase the runtime |
| 237 | + * PM usage count before registering the clocks, to prevent the |
| 238 | + * clock core from runtime suspending the device. |
| 239 | + */ |
| 240 | + pm_runtime_get_noresume(dev); |
| 241 | + pm_runtime_set_active(dev); |
| 242 | + pm_runtime_enable(dev); |
| 243 | + |
| 244 | + samsung_cmu_register_clocks(data->ctx, cmu); |
| 245 | + samsung_clk_of_add_provider(dev->of_node, data->ctx); |
| 246 | + pm_runtime_put_sync(dev); |
| 247 | + |
| 248 | + return 0; |
| 249 | +} |
| 250 | + |
| 251 | +int exynos_arm64_cmu_suspend(struct device *dev) |
| 252 | +{ |
| 253 | + struct exynos_arm64_cmu_data *data = dev_get_drvdata(dev); |
| 254 | + int i; |
| 255 | + |
| 256 | + samsung_clk_save(data->ctx->reg_base, data->clk_save, |
| 257 | + data->nr_clk_save); |
| 258 | + |
| 259 | + for (i = 0; i < data->nr_pclks; i++) |
| 260 | + clk_prepare_enable(data->pclks[i]); |
| 261 | + |
| 262 | + /* For suspend some registers have to be set to certain values */ |
| 263 | + samsung_clk_restore(data->ctx->reg_base, data->clk_suspend, |
| 264 | + data->nr_clk_suspend); |
| 265 | + |
| 266 | + for (i = 0; i < data->nr_pclks; i++) |
| 267 | + clk_disable_unprepare(data->pclks[i]); |
| 268 | + |
| 269 | + clk_disable_unprepare(data->clk); |
| 270 | + |
| 271 | + return 0; |
| 272 | +} |
| 273 | + |
| 274 | +int exynos_arm64_cmu_resume(struct device *dev) |
| 275 | +{ |
| 276 | + struct exynos_arm64_cmu_data *data = dev_get_drvdata(dev); |
| 277 | + int i; |
| 278 | + |
| 279 | + clk_prepare_enable(data->clk); |
| 280 | + |
| 281 | + for (i = 0; i < data->nr_pclks; i++) |
| 282 | + clk_prepare_enable(data->pclks[i]); |
| 283 | + |
| 284 | + samsung_clk_restore(data->ctx->reg_base, data->clk_save, |
| 285 | + data->nr_clk_save); |
| 286 | + |
| 287 | + for (i = 0; i < data->nr_pclks; i++) |
| 288 | + clk_disable_unprepare(data->pclks[i]); |
| 289 | + |
| 290 | + return 0; |
| 291 | +} |
0 commit comments