]>
Commit | Line | Data |
---|---|---|
caab277b | 1 | // SPDX-License-Identifier: GPL-2.0-only |
3e87599b RC |
2 | /* |
3 | * Copyright (C) 2014 Red Hat | |
4 | * Author: Rob Clark <robdclark@gmail.com> | |
3e87599b RC |
5 | */ |
6 | ||
7 | #include <linux/clk.h> | |
8 | #include <linux/clk-provider.h> | |
9 | ||
10 | #include "mdp4_kms.h" | |
11 | ||
12 | struct mdp4_lvds_pll { | |
13 | struct clk_hw pll_hw; | |
14 | struct drm_device *dev; | |
15 | unsigned long pixclk; | |
16 | }; | |
17 | #define to_mdp4_lvds_pll(x) container_of(x, struct mdp4_lvds_pll, pll_hw) | |
18 | ||
19 | static struct mdp4_kms *get_kms(struct mdp4_lvds_pll *lvds_pll) | |
20 | { | |
21 | struct msm_drm_private *priv = lvds_pll->dev->dev_private; | |
22 | return to_mdp4_kms(to_mdp_kms(priv->kms)); | |
23 | } | |
24 | ||
25 | struct pll_rate { | |
26 | unsigned long rate; | |
27 | struct { | |
28 | uint32_t val; | |
29 | uint32_t reg; | |
30 | } conf[32]; | |
31 | }; | |
32 | ||
33 | /* NOTE: keep sorted highest freq to lowest: */ | |
34 | static const struct pll_rate freqtbl[] = { | |
35 | { 72000000, { | |
36 | { 0x8f, REG_MDP4_LVDS_PHY_PLL_CTRL_1 }, | |
37 | { 0x30, REG_MDP4_LVDS_PHY_PLL_CTRL_2 }, | |
38 | { 0xc6, REG_MDP4_LVDS_PHY_PLL_CTRL_3 }, | |
39 | { 0x10, REG_MDP4_LVDS_PHY_PLL_CTRL_5 }, | |
40 | { 0x07, REG_MDP4_LVDS_PHY_PLL_CTRL_6 }, | |
41 | { 0x62, REG_MDP4_LVDS_PHY_PLL_CTRL_7 }, | |
42 | { 0x41, REG_MDP4_LVDS_PHY_PLL_CTRL_8 }, | |
43 | { 0x0d, REG_MDP4_LVDS_PHY_PLL_CTRL_9 }, | |
44 | { 0, 0 } } | |
45 | }, | |
46 | }; | |
47 | ||
48 | static const struct pll_rate *find_rate(unsigned long rate) | |
49 | { | |
50 | int i; | |
51 | for (i = 1; i < ARRAY_SIZE(freqtbl); i++) | |
52 | if (rate > freqtbl[i].rate) | |
53 | return &freqtbl[i-1]; | |
54 | return &freqtbl[i-1]; | |
55 | } | |
56 | ||
57 | static int mpd4_lvds_pll_enable(struct clk_hw *hw) | |
58 | { | |
59 | struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw); | |
60 | struct mdp4_kms *mdp4_kms = get_kms(lvds_pll); | |
61 | const struct pll_rate *pll_rate = find_rate(lvds_pll->pixclk); | |
62 | int i; | |
63 | ||
64 | DBG("pixclk=%lu (%lu)", lvds_pll->pixclk, pll_rate->rate); | |
65 | ||
66 | if (WARN_ON(!pll_rate)) | |
67 | return -EINVAL; | |
68 | ||
69 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_PHY_RESET, 0x33); | |
70 | ||
71 | for (i = 0; pll_rate->conf[i].reg; i++) | |
72 | mdp4_write(mdp4_kms, pll_rate->conf[i].reg, pll_rate->conf[i].val); | |
73 | ||
74 | mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_PLL_CTRL_0, 0x01); | |
75 | ||
76 | /* Wait until LVDS PLL is locked and ready */ | |
77 | while (!mdp4_read(mdp4_kms, REG_MDP4_LVDS_PHY_PLL_LOCKED)) | |
78 | cpu_relax(); | |
79 | ||
80 | return 0; | |
81 | } | |
82 | ||
83 | static void mpd4_lvds_pll_disable(struct clk_hw *hw) | |
84 | { | |
85 | struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw); | |
86 | struct mdp4_kms *mdp4_kms = get_kms(lvds_pll); | |
87 | ||
88 | DBG(""); | |
89 | ||
90 | mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG0, 0x0); | |
91 | mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_PLL_CTRL_0, 0x0); | |
92 | } | |
93 | ||
94 | static unsigned long mpd4_lvds_pll_recalc_rate(struct clk_hw *hw, | |
95 | unsigned long parent_rate) | |
96 | { | |
97 | struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw); | |
98 | return lvds_pll->pixclk; | |
99 | } | |
100 | ||
101 | static long mpd4_lvds_pll_round_rate(struct clk_hw *hw, unsigned long rate, | |
102 | unsigned long *parent_rate) | |
103 | { | |
104 | const struct pll_rate *pll_rate = find_rate(rate); | |
105 | return pll_rate->rate; | |
106 | } | |
107 | ||
108 | static int mpd4_lvds_pll_set_rate(struct clk_hw *hw, unsigned long rate, | |
109 | unsigned long parent_rate) | |
110 | { | |
111 | struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw); | |
112 | lvds_pll->pixclk = rate; | |
113 | return 0; | |
114 | } | |
115 | ||
116 | ||
117 | static const struct clk_ops mpd4_lvds_pll_ops = { | |
118 | .enable = mpd4_lvds_pll_enable, | |
119 | .disable = mpd4_lvds_pll_disable, | |
120 | .recalc_rate = mpd4_lvds_pll_recalc_rate, | |
121 | .round_rate = mpd4_lvds_pll_round_rate, | |
122 | .set_rate = mpd4_lvds_pll_set_rate, | |
123 | }; | |
124 | ||
125 | static const char *mpd4_lvds_pll_parents[] = { | |
126 | "pxo", | |
127 | }; | |
128 | ||
129 | static struct clk_init_data pll_init = { | |
130 | .name = "mpd4_lvds_pll", | |
131 | .ops = &mpd4_lvds_pll_ops, | |
132 | .parent_names = mpd4_lvds_pll_parents, | |
133 | .num_parents = ARRAY_SIZE(mpd4_lvds_pll_parents), | |
134 | }; | |
135 | ||
136 | struct clk *mpd4_lvds_pll_init(struct drm_device *dev) | |
137 | { | |
138 | struct mdp4_lvds_pll *lvds_pll; | |
139 | struct clk *clk; | |
140 | int ret; | |
141 | ||
142 | lvds_pll = devm_kzalloc(dev->dev, sizeof(*lvds_pll), GFP_KERNEL); | |
143 | if (!lvds_pll) { | |
144 | ret = -ENOMEM; | |
145 | goto fail; | |
146 | } | |
147 | ||
148 | lvds_pll->dev = dev; | |
149 | ||
150 | lvds_pll->pll_hw.init = &pll_init; | |
151 | clk = devm_clk_register(dev->dev, &lvds_pll->pll_hw); | |
152 | if (IS_ERR(clk)) { | |
153 | ret = PTR_ERR(clk); | |
154 | goto fail; | |
155 | } | |
156 | ||
157 | return clk; | |
158 | ||
159 | fail: | |
160 | return ERR_PTR(ret); | |
161 | } |