c785282693e64145f01727f77372fcee81048aad
[linux-3.10.git] / drivers / video / tegra / dc / clock.c
1 /*
2  * drivers/video/tegra/dc/clock.c
3  *
4  * Copyright (C) 2010 Google, Inc.
5  *
6  * Copyright (c) 2010-2012, NVIDIA CORPORATION, All rights reserved.
7  *
8  * This software is licensed under the terms of the GNU General Public
9  * License version 2, as published by the Free Software Foundation, and
10  * may be copied, distributed, and modified under those terms.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  */
18
19 #include <linux/err.h>
20 #include <linux/types.h>
21 #include <linux/clk.h>
22
23 #include <mach/clk.h>
24 #include <mach/dc.h>
25
26 #include "dc_reg.h"
27 #include "dc_priv.h"
28
29 unsigned long tegra_dc_pclk_round_rate(struct tegra_dc *dc, int pclk)
30 {
31         unsigned long rate;
32         unsigned long div;
33
34         rate = tegra_dc_clk_get_rate(dc);
35
36         div = DIV_ROUND_CLOSEST(rate * 2, pclk);
37
38         if (div < 2)
39                 return 0;
40
41         return rate * 2 / div;
42 }
43
44 static unsigned long tegra_dc_pclk_predict_rate(struct clk *parent, int pclk)
45 {
46         unsigned long rate;
47         unsigned long div;
48
49         rate = clk_get_rate(parent);
50
51         div = DIV_ROUND_CLOSEST(rate * 2, pclk);
52
53         if (div < 2)
54                 return 0;
55
56         return rate * 2 / div;
57 }
58
59 void tegra_dc_setup_clk(struct tegra_dc *dc, struct clk *clk)
60 {
61         int pclk;
62
63         if (dc->out->type == TEGRA_DC_OUT_RGB) {
64                 unsigned long rate;
65                 struct clk *parent_clk =
66                         clk_get_sys(NULL, dc->out->parent_clk ? : "pll_p");
67
68                 if (dc->out->parent_clk_backup &&
69                     (parent_clk == clk_get_sys(NULL, "pll_p"))) {
70                         rate = tegra_dc_pclk_predict_rate(
71                                 parent_clk, dc->mode.pclk);
72                         /* use pll_d as last resort */
73                         if (rate < (dc->mode.pclk / 100 * 99) ||
74                             rate > (dc->mode.pclk / 100 * 109))
75                                 parent_clk = clk_get_sys(
76                                         NULL, dc->out->parent_clk_backup);
77                 }
78
79                 if (clk_get_parent(clk) != parent_clk)
80                         clk_set_parent(clk, parent_clk);
81
82                 if (parent_clk != clk_get_sys(NULL, "pll_p")) {
83                         struct clk *base_clk = clk_get_parent(parent_clk);
84
85                         /* Assuming either pll_d or pll_d2 is used */
86                         rate = dc->mode.pclk * 2;
87
88                         if (rate != clk_get_rate(base_clk))
89                                 clk_set_rate(base_clk, rate);
90                 }
91         }
92
93         if (dc->out->type == TEGRA_DC_OUT_HDMI) {
94                 unsigned long rate;
95                 struct clk *parent_clk = clk_get_sys(NULL,
96                         dc->out->parent_clk ? : "pll_d_out0");
97                 struct clk *base_clk = clk_get_parent(parent_clk);
98
99                 /*
100                  * Providing dynamic frequency rate setting for T20/T30 HDMI.
101                  * The required rate needs to be setup at 4x multiplier,
102                  * as out0 is 1/2 of the actual PLL output.
103                  */
104
105                 rate = dc->mode.pclk * 4;
106                 if (rate != clk_get_rate(base_clk))
107                         clk_set_rate(base_clk, rate);
108
109                 if (clk_get_parent(clk) != parent_clk)
110                         clk_set_parent(clk, parent_clk);
111         }
112
113         if (dc->out->type == TEGRA_DC_OUT_DSI) {
114                 unsigned long rate;
115                 struct clk *parent_clk;
116                 struct clk *base_clk;
117
118                 if (clk == dc->clk) {
119                         parent_clk = clk_get_sys(NULL,
120                                         dc->out->parent_clk ? : "pll_d_out0");
121                         base_clk = clk_get_parent(parent_clk);
122                         tegra_clk_cfg_ex(base_clk,
123                                         TEGRA_CLK_PLLD_DSI_OUT_ENB, 1);
124                 } else {
125                         if (dc->pdata->default_out->dsi->dsi_instance) {
126                                 parent_clk = clk_get_sys(NULL,
127                                         dc->out->parent_clk ? : "pll_d2_out0");
128                                 base_clk = clk_get_parent(parent_clk);
129                                 tegra_clk_cfg_ex(base_clk,
130                                                 TEGRA_CLK_PLLD_CSI_OUT_ENB, 1);
131                         } else {
132                                 parent_clk = clk_get_sys(NULL,
133                                         dc->out->parent_clk ? : "pll_d_out0");
134                                 base_clk = clk_get_parent(parent_clk);
135                                 tegra_clk_cfg_ex(base_clk,
136                                                 TEGRA_CLK_PLLD_DSI_OUT_ENB, 1);
137                         }
138                 }
139
140                 rate = dc->mode.pclk * dc->shift_clk_div * 2;
141                 if (rate != clk_get_rate(base_clk))
142                         clk_set_rate(base_clk, rate);
143
144                 if (clk_get_parent(clk) != parent_clk)
145                         clk_set_parent(clk, parent_clk);
146         }
147
148         pclk = tegra_dc_pclk_round_rate(dc, dc->mode.pclk);
149         tegra_dvfs_set_rate(clk, pclk);
150 }