1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (c) 2012, 2013, NVIDIA CORPORATION. All rights reserved. |
4 | */ |
5 | |
6 | #include <linux/io.h> |
7 | #include <linux/clk-provider.h> |
8 | #include <linux/of.h> |
9 | #include <linux/of_address.h> |
10 | #include <linux/delay.h> |
11 | #include <linux/export.h> |
12 | #include <linux/clk/tegra.h> |
13 | |
14 | #include "clk.h" |
15 | #include "clk-id.h" |
16 | |
17 | #define OSC_CTRL 0x50 |
18 | #define OSC_CTRL_OSC_FREQ_SHIFT 28 |
19 | #define OSC_CTRL_PLL_REF_DIV_SHIFT 26 |
20 | #define OSC_CTRL_MASK (0x3f2 | \ |
21 | (0xf << OSC_CTRL_OSC_FREQ_SHIFT)) |
22 | |
23 | static u32 osc_ctrl_ctx; |
24 | |
25 | int __init tegra_osc_clk_init(void __iomem *clk_base, struct tegra_clk *clks, |
26 | unsigned long *input_freqs, unsigned int num, |
27 | unsigned int clk_m_div, unsigned long *osc_freq, |
28 | unsigned long *pll_ref_freq) |
29 | { |
30 | struct clk *clk, *osc; |
31 | struct clk **dt_clk; |
32 | u32 val, pll_ref_div; |
33 | unsigned osc_idx; |
34 | |
35 | val = readl_relaxed(clk_base + OSC_CTRL); |
36 | osc_ctrl_ctx = val & OSC_CTRL_MASK; |
37 | osc_idx = val >> OSC_CTRL_OSC_FREQ_SHIFT; |
38 | |
39 | if (osc_idx < num) |
40 | *osc_freq = input_freqs[osc_idx]; |
41 | else |
42 | *osc_freq = 0; |
43 | |
44 | if (!*osc_freq) { |
45 | WARN_ON(1); |
46 | return -EINVAL; |
47 | } |
48 | |
49 | dt_clk = tegra_lookup_dt_id(clk_id: tegra_clk_osc, tegra_clk: clks); |
50 | if (!dt_clk) |
51 | return 0; |
52 | |
53 | osc = clk_register_fixed_rate(NULL, name: "osc" , NULL, flags: 0, fixed_rate: *osc_freq); |
54 | *dt_clk = osc; |
55 | |
56 | /* osc_div2 */ |
57 | dt_clk = tegra_lookup_dt_id(clk_id: tegra_clk_osc_div2, tegra_clk: clks); |
58 | if (dt_clk) { |
59 | clk = clk_register_fixed_factor(NULL, name: "osc_div2" , parent_name: "osc" , |
60 | flags: 0, mult: 1, div: 2); |
61 | *dt_clk = clk; |
62 | } |
63 | |
64 | /* osc_div4 */ |
65 | dt_clk = tegra_lookup_dt_id(clk_id: tegra_clk_osc_div4, tegra_clk: clks); |
66 | if (dt_clk) { |
67 | clk = clk_register_fixed_factor(NULL, name: "osc_div4" , parent_name: "osc" , |
68 | flags: 0, mult: 1, div: 4); |
69 | *dt_clk = clk; |
70 | } |
71 | |
72 | dt_clk = tegra_lookup_dt_id(clk_id: tegra_clk_clk_m, tegra_clk: clks); |
73 | if (!dt_clk) |
74 | return 0; |
75 | |
76 | clk = clk_register_fixed_factor(NULL, name: "clk_m" , parent_name: "osc" , |
77 | flags: 0, mult: 1, div: clk_m_div); |
78 | *dt_clk = clk; |
79 | |
80 | /* pll_ref */ |
81 | val = (val >> OSC_CTRL_PLL_REF_DIV_SHIFT) & 3; |
82 | pll_ref_div = 1 << val; |
83 | dt_clk = tegra_lookup_dt_id(clk_id: tegra_clk_pll_ref, tegra_clk: clks); |
84 | if (!dt_clk) |
85 | return 0; |
86 | |
87 | clk = clk_register_fixed_factor(NULL, name: "pll_ref" , parent_name: "osc" , |
88 | flags: 0, mult: 1, div: pll_ref_div); |
89 | *dt_clk = clk; |
90 | |
91 | if (pll_ref_freq) |
92 | *pll_ref_freq = *osc_freq / pll_ref_div; |
93 | |
94 | return 0; |
95 | } |
96 | |
97 | void __init tegra_fixed_clk_init(struct tegra_clk *tegra_clks) |
98 | { |
99 | struct clk *clk; |
100 | struct clk **dt_clk; |
101 | |
102 | /* clk_32k */ |
103 | dt_clk = tegra_lookup_dt_id(clk_id: tegra_clk_clk_32k, tegra_clk: tegra_clks); |
104 | if (dt_clk) { |
105 | clk = clk_register_fixed_rate(NULL, name: "clk_32k" , NULL, flags: 0, fixed_rate: 32768); |
106 | *dt_clk = clk; |
107 | } |
108 | } |
109 | |
110 | void tegra_clk_osc_resume(void __iomem *clk_base) |
111 | { |
112 | u32 val; |
113 | |
114 | val = readl_relaxed(clk_base + OSC_CTRL) & ~OSC_CTRL_MASK; |
115 | val |= osc_ctrl_ctx; |
116 | writel_relaxed(val, clk_base + OSC_CTRL); |
117 | fence_udelay(2, clk_base); |
118 | } |
119 | |