diff options
author | Rob Clark <robdclark@gmail.com> | 2014-08-01 13:08:11 -0400 |
---|---|---|
committer | Rob Clark <robdclark@gmail.com> | 2014-09-10 11:19:07 -0400 |
commit | 3e87599b68e7929a84a32ab65ad17b79a3f271f6 (patch) | |
tree | 073b98e5d2e097be7016f467be3eaaf3c08f2a18 | |
parent | d65bd0e431156f156f43946b6efb524694afb685 (diff) |
drm/msm/mdp4: add LVDS panel support
LVDS panel support uses the LCDC (parallel) encoder. Unlike with HDMI,
there is not a separate LVDS block, so no need to split things into a
bridge+connector. Nor is there is anything re-used with mdp5.
Note that there can be some regulators shared between HDMI and LVDS (in
particular, on apq8064, ext_3v3p), so we should not use the _exclusive()
variants of devm_regulator_get().
The drm_panel framework is used for panel-specific driver.
Signed-off-by: Rob Clark <robdclark@gmail.com>
-rw-r--r-- | drivers/gpu/drm/msm/Kconfig | 1 | ||||
-rw-r--r-- | drivers/gpu/drm/msm/Makefile | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/msm/hdmi/hdmi.c | 4 | ||||
-rw-r--r-- | drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.c | 88 | ||||
-rw-r--r-- | drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.h | 18 | ||||
-rw-r--r-- | drivers/gpu/drm/msm/mdp/mdp4/mdp4_lcdc_encoder.c | 506 | ||||
-rw-r--r-- | drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_connector.c | 151 | ||||
-rw-r--r-- | drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_pll.c | 172 |
8 files changed, 933 insertions, 10 deletions
diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig index c99c50de3226..9d907c526c94 100644 --- a/drivers/gpu/drm/msm/Kconfig +++ b/drivers/gpu/drm/msm/Kconfig | |||
@@ -4,6 +4,7 @@ config DRM_MSM | |||
4 | depends on DRM | 4 | depends on DRM |
5 | depends on ARCH_QCOM || (ARM && COMPILE_TEST) | 5 | depends on ARCH_QCOM || (ARM && COMPILE_TEST) |
6 | select DRM_KMS_HELPER | 6 | select DRM_KMS_HELPER |
7 | select DRM_PANEL | ||
7 | select SHMEM | 8 | select SHMEM |
8 | select TMPFS | 9 | select TMPFS |
9 | default y | 10 | default y |
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile index 93ca49c8df44..48f796056887 100644 --- a/drivers/gpu/drm/msm/Makefile +++ b/drivers/gpu/drm/msm/Makefile | |||
@@ -18,6 +18,8 @@ msm-y := \ | |||
18 | mdp/mdp_kms.o \ | 18 | mdp/mdp_kms.o \ |
19 | mdp/mdp4/mdp4_crtc.o \ | 19 | mdp/mdp4/mdp4_crtc.o \ |
20 | mdp/mdp4/mdp4_dtv_encoder.o \ | 20 | mdp/mdp4/mdp4_dtv_encoder.o \ |
21 | mdp/mdp4/mdp4_lcdc_encoder.o \ | ||
22 | mdp/mdp4/mdp4_lvds_connector.o \ | ||
21 | mdp/mdp4/mdp4_irq.o \ | 23 | mdp/mdp4/mdp4_irq.o \ |
22 | mdp/mdp4/mdp4_kms.o \ | 24 | mdp/mdp4/mdp4_kms.o \ |
23 | mdp/mdp4/mdp4_plane.o \ | 25 | mdp/mdp4/mdp4_plane.o \ |
@@ -39,5 +41,6 @@ msm-y := \ | |||
39 | msm_ringbuffer.o | 41 | msm_ringbuffer.o |
40 | 42 | ||
41 | msm-$(CONFIG_DRM_MSM_FBDEV) += msm_fbdev.o | 43 | msm-$(CONFIG_DRM_MSM_FBDEV) += msm_fbdev.o |
44 | msm-$(CONFIG_COMMON_CLK) += mdp/mdp4/mdp4_lvds_pll.o | ||
42 | 45 | ||
43 | obj-$(CONFIG_DRM_MSM) += msm.o | 46 | obj-$(CONFIG_DRM_MSM) += msm.o |
diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.c b/drivers/gpu/drm/msm/hdmi/hdmi.c index a125a7e32742..2e0eac7cdf89 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi.c | |||
@@ -123,7 +123,7 @@ struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder) | |||
123 | for (i = 0; i < config->hpd_reg_cnt; i++) { | 123 | for (i = 0; i < config->hpd_reg_cnt; i++) { |
124 | struct regulator *reg; | 124 | struct regulator *reg; |
125 | 125 | ||
126 | reg = devm_regulator_get_exclusive(&pdev->dev, | 126 | reg = devm_regulator_get(&pdev->dev, |
127 | config->hpd_reg_names[i]); | 127 | config->hpd_reg_names[i]); |
128 | if (IS_ERR(reg)) { | 128 | if (IS_ERR(reg)) { |
129 | ret = PTR_ERR(reg); | 129 | ret = PTR_ERR(reg); |
@@ -139,7 +139,7 @@ struct hdmi *hdmi_init(struct drm_device *dev, struct drm_encoder *encoder) | |||
139 | for (i = 0; i < config->pwr_reg_cnt; i++) { | 139 | for (i = 0; i < config->pwr_reg_cnt; i++) { |
140 | struct regulator *reg; | 140 | struct regulator *reg; |
141 | 141 | ||
142 | reg = devm_regulator_get_exclusive(&pdev->dev, | 142 | reg = devm_regulator_get(&pdev->dev, |
143 | config->pwr_reg_names[i]); | 143 | config->pwr_reg_names[i]); |
144 | if (IS_ERR(reg)) { | 144 | if (IS_ERR(reg)) { |
145 | ret = PTR_ERR(reg); | 145 | ret = PTR_ERR(reg); |
diff --git a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.c b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.c index af69079082f7..79d804e61cc4 100644 --- a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.c +++ b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.c | |||
@@ -197,6 +197,28 @@ int mdp4_enable(struct mdp4_kms *mdp4_kms) | |||
197 | return 0; | 197 | return 0; |
198 | } | 198 | } |
199 | 199 | ||
200 | #ifdef CONFIG_OF | ||
201 | static struct drm_panel *detect_panel(struct drm_device *dev, const char *name) | ||
202 | { | ||
203 | struct device_node *n; | ||
204 | struct drm_panel *panel = NULL; | ||
205 | |||
206 | n = of_parse_phandle(dev->dev->of_node, name, 0); | ||
207 | if (n) { | ||
208 | panel = of_drm_find_panel(n); | ||
209 | if (!panel) | ||
210 | panel = ERR_PTR(-EPROBE_DEFER); | ||
211 | } | ||
212 | |||
213 | return panel; | ||
214 | } | ||
215 | #else | ||
216 | static struct drm_panel *detect_panel(struct drm_device *dev, const char *name) | ||
217 | { | ||
218 | // ??? maybe use a module param to specify which panel is attached? | ||
219 | } | ||
220 | #endif | ||
221 | |||
200 | static int modeset_init(struct mdp4_kms *mdp4_kms) | 222 | static int modeset_init(struct mdp4_kms *mdp4_kms) |
201 | { | 223 | { |
202 | struct drm_device *dev = mdp4_kms->dev; | 224 | struct drm_device *dev = mdp4_kms->dev; |
@@ -204,14 +226,11 @@ static int modeset_init(struct mdp4_kms *mdp4_kms) | |||
204 | struct drm_plane *plane; | 226 | struct drm_plane *plane; |
205 | struct drm_crtc *crtc; | 227 | struct drm_crtc *crtc; |
206 | struct drm_encoder *encoder; | 228 | struct drm_encoder *encoder; |
229 | struct drm_connector *connector; | ||
230 | struct drm_panel *panel; | ||
207 | struct hdmi *hdmi; | 231 | struct hdmi *hdmi; |
208 | int ret; | 232 | int ret; |
209 | 233 | ||
210 | /* | ||
211 | * NOTE: this is a bit simplistic until we add support | ||
212 | * for more than just RGB1->DMA_E->DTV->HDMI | ||
213 | */ | ||
214 | |||
215 | /* construct non-private planes: */ | 234 | /* construct non-private planes: */ |
216 | plane = mdp4_plane_init(dev, VG1, false); | 235 | plane = mdp4_plane_init(dev, VG1, false); |
217 | if (IS_ERR(plane)) { | 236 | if (IS_ERR(plane)) { |
@@ -229,7 +248,57 @@ static int modeset_init(struct mdp4_kms *mdp4_kms) | |||
229 | } | 248 | } |
230 | priv->planes[priv->num_planes++] = plane; | 249 | priv->planes[priv->num_planes++] = plane; |
231 | 250 | ||
232 | /* the CRTCs get constructed with a private plane: */ | 251 | /* |
252 | * Setup the LCDC/LVDS path: RGB2 -> DMA_P -> LCDC -> LVDS: | ||
253 | */ | ||
254 | |||
255 | panel = detect_panel(dev, "qcom,lvds-panel"); | ||
256 | if (IS_ERR(panel)) { | ||
257 | ret = PTR_ERR(panel); | ||
258 | dev_err(dev->dev, "failed to detect LVDS panel: %d\n", ret); | ||
259 | goto fail; | ||
260 | } | ||
261 | |||
262 | plane = mdp4_plane_init(dev, RGB2, true); | ||
263 | if (IS_ERR(plane)) { | ||
264 | dev_err(dev->dev, "failed to construct plane for RGB2\n"); | ||
265 | ret = PTR_ERR(plane); | ||
266 | goto fail; | ||
267 | } | ||
268 | |||
269 | crtc = mdp4_crtc_init(dev, plane, priv->num_crtcs, 0, DMA_P); | ||
270 | if (IS_ERR(crtc)) { | ||
271 | dev_err(dev->dev, "failed to construct crtc for DMA_P\n"); | ||
272 | ret = PTR_ERR(crtc); | ||
273 | goto fail; | ||
274 | } | ||
275 | |||
276 | encoder = mdp4_lcdc_encoder_init(dev, panel); | ||
277 | if (IS_ERR(encoder)) { | ||
278 | dev_err(dev->dev, "failed to construct LCDC encoder\n"); | ||
279 | ret = PTR_ERR(encoder); | ||
280 | goto fail; | ||
281 | } | ||
282 | |||
283 | /* LCDC can be hooked to DMA_P: */ | ||
284 | encoder->possible_crtcs = 1 << priv->num_crtcs; | ||
285 | |||
286 | priv->crtcs[priv->num_crtcs++] = crtc; | ||
287 | priv->encoders[priv->num_encoders++] = encoder; | ||
288 | |||
289 | connector = mdp4_lvds_connector_init(dev, panel, encoder); | ||
290 | if (IS_ERR(connector)) { | ||
291 | ret = PTR_ERR(connector); | ||
292 | dev_err(dev->dev, "failed to initialize LVDS connector: %d\n", ret); | ||
293 | goto fail; | ||
294 | } | ||
295 | |||
296 | priv->connectors[priv->num_connectors++] = connector; | ||
297 | |||
298 | /* | ||
299 | * Setup DTV/HDMI path: RGB1 -> DMA_E -> DTV -> HDMI: | ||
300 | */ | ||
301 | |||
233 | plane = mdp4_plane_init(dev, RGB1, true); | 302 | plane = mdp4_plane_init(dev, RGB1, true); |
234 | if (IS_ERR(plane)) { | 303 | if (IS_ERR(plane)) { |
235 | dev_err(dev->dev, "failed to construct plane for RGB1\n"); | 304 | dev_err(dev->dev, "failed to construct plane for RGB1\n"); |
@@ -243,7 +312,6 @@ static int modeset_init(struct mdp4_kms *mdp4_kms) | |||
243 | ret = PTR_ERR(crtc); | 312 | ret = PTR_ERR(crtc); |
244 | goto fail; | 313 | goto fail; |
245 | } | 314 | } |
246 | priv->crtcs[priv->num_crtcs++] = crtc; | ||
247 | 315 | ||
248 | encoder = mdp4_dtv_encoder_init(dev); | 316 | encoder = mdp4_dtv_encoder_init(dev); |
249 | if (IS_ERR(encoder)) { | 317 | if (IS_ERR(encoder)) { |
@@ -251,7 +319,11 @@ static int modeset_init(struct mdp4_kms *mdp4_kms) | |||
251 | ret = PTR_ERR(encoder); | 319 | ret = PTR_ERR(encoder); |
252 | goto fail; | 320 | goto fail; |
253 | } | 321 | } |
254 | encoder->possible_crtcs = 0x1; /* DTV can be hooked to DMA_E */ | 322 | |
323 | /* DTV can be hooked to DMA_E: */ | ||
324 | encoder->possible_crtcs = 1 << priv->num_crtcs; | ||
325 | |||
326 | priv->crtcs[priv->num_crtcs++] = crtc; | ||
255 | priv->encoders[priv->num_encoders++] = encoder; | 327 | priv->encoders[priv->num_encoders++] = encoder; |
256 | 328 | ||
257 | hdmi = hdmi_init(dev, encoder); | 329 | hdmi = hdmi_init(dev, encoder); |
diff --git a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.h b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.h index e74146fe2ae6..9ff6e7ccfe90 100644 --- a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.h +++ b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_kms.h | |||
@@ -23,6 +23,8 @@ | |||
23 | #include "mdp/mdp_kms.h" | 23 | #include "mdp/mdp_kms.h" |
24 | #include "mdp4.xml.h" | 24 | #include "mdp4.xml.h" |
25 | 25 | ||
26 | #include "drm_panel.h" | ||
27 | |||
26 | struct mdp4_kms { | 28 | struct mdp4_kms { |
27 | struct mdp_kms base; | 29 | struct mdp_kms base; |
28 | 30 | ||
@@ -217,6 +219,22 @@ struct drm_crtc *mdp4_crtc_init(struct drm_device *dev, | |||
217 | long mdp4_dtv_round_pixclk(struct drm_encoder *encoder, unsigned long rate); | 219 | long mdp4_dtv_round_pixclk(struct drm_encoder *encoder, unsigned long rate); |
218 | struct drm_encoder *mdp4_dtv_encoder_init(struct drm_device *dev); | 220 | struct drm_encoder *mdp4_dtv_encoder_init(struct drm_device *dev); |
219 | 221 | ||
222 | long mdp4_lcdc_round_pixclk(struct drm_encoder *encoder, unsigned long rate); | ||
223 | struct drm_encoder *mdp4_lcdc_encoder_init(struct drm_device *dev, | ||
224 | struct drm_panel *panel); | ||
225 | |||
226 | struct drm_connector *mdp4_lvds_connector_init(struct drm_device *dev, | ||
227 | struct drm_panel *panel, struct drm_encoder *encoder); | ||
228 | |||
229 | #ifdef CONFIG_COMMON_CLK | ||
230 | struct clk *mpd4_lvds_pll_init(struct drm_device *dev); | ||
231 | #else | ||
232 | static inline struct clk *mpd4_lvds_pll_init(struct drm_device *dev) | ||
233 | { | ||
234 | return ERR_PTR(-ENODEV); | ||
235 | } | ||
236 | #endif | ||
237 | |||
220 | #ifdef CONFIG_MSM_BUS_SCALING | 238 | #ifdef CONFIG_MSM_BUS_SCALING |
221 | static inline int match_dev_name(struct device *dev, void *data) | 239 | static inline int match_dev_name(struct device *dev, void *data) |
222 | { | 240 | { |
diff --git a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_lcdc_encoder.c b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_lcdc_encoder.c new file mode 100644 index 000000000000..41f6436754fc --- /dev/null +++ b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_lcdc_encoder.c | |||
@@ -0,0 +1,506 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * Author: Vinay Simha <vinaysimha@inforcecomputing.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms of the GNU General Public License version 2 as published by | ||
8 | * the Free Software Foundation. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
13 | * more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License along with | ||
16 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
17 | */ | ||
18 | |||
19 | #include "mdp4_kms.h" | ||
20 | |||
21 | #include "drm_crtc.h" | ||
22 | #include "drm_crtc_helper.h" | ||
23 | |||
24 | struct mdp4_lcdc_encoder { | ||
25 | struct drm_encoder base; | ||
26 | struct drm_panel *panel; | ||
27 | struct clk *lcdc_clk; | ||
28 | unsigned long int pixclock; | ||
29 | struct regulator *regs[3]; | ||
30 | bool enabled; | ||
31 | uint32_t bsc; | ||
32 | }; | ||
33 | #define to_mdp4_lcdc_encoder(x) container_of(x, struct mdp4_lcdc_encoder, base) | ||
34 | |||
35 | static struct mdp4_kms *get_kms(struct drm_encoder *encoder) | ||
36 | { | ||
37 | struct msm_drm_private *priv = encoder->dev->dev_private; | ||
38 | return to_mdp4_kms(to_mdp_kms(priv->kms)); | ||
39 | } | ||
40 | |||
41 | #ifdef CONFIG_MSM_BUS_SCALING | ||
42 | #include <mach/board.h> | ||
43 | static void bs_init(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder) | ||
44 | { | ||
45 | struct drm_device *dev = mdp4_lcdc_encoder->base.dev; | ||
46 | struct lcdc_platform_data *lcdc_pdata = mdp4_find_pdata("lvds.0"); | ||
47 | |||
48 | if (!lcdc_pdata) { | ||
49 | dev_err(dev->dev, "could not find lvds pdata\n"); | ||
50 | return; | ||
51 | } | ||
52 | |||
53 | if (lcdc_pdata->bus_scale_table) { | ||
54 | mdp4_lcdc_encoder->bsc = msm_bus_scale_register_client( | ||
55 | lcdc_pdata->bus_scale_table); | ||
56 | DBG("lvds : bus scale client: %08x", mdp4_lcdc_encoder->bsc); | ||
57 | } | ||
58 | } | ||
59 | |||
60 | static void bs_fini(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder) | ||
61 | { | ||
62 | if (mdp4_lcdc_encoder->bsc) { | ||
63 | msm_bus_scale_unregister_client(mdp4_lcdc_encoder->bsc); | ||
64 | mdp4_lcdc_encoder->bsc = 0; | ||
65 | } | ||
66 | } | ||
67 | |||
68 | static void bs_set(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder, int idx) | ||
69 | { | ||
70 | if (mdp4_lcdc_encoder->bsc) { | ||
71 | DBG("set bus scaling: %d", idx); | ||
72 | msm_bus_scale_client_update_request(mdp4_lcdc_encoder->bsc, idx); | ||
73 | } | ||
74 | } | ||
75 | #else | ||
76 | static void bs_init(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder) {} | ||
77 | static void bs_fini(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder) {} | ||
78 | static void bs_set(struct mdp4_lcdc_encoder *mdp4_lcdc_encoder, int idx) {} | ||
79 | #endif | ||
80 | |||
81 | static void mdp4_lcdc_encoder_destroy(struct drm_encoder *encoder) | ||
82 | { | ||
83 | struct mdp4_lcdc_encoder *mdp4_lcdc_encoder = | ||
84 | to_mdp4_lcdc_encoder(encoder); | ||
85 | bs_fini(mdp4_lcdc_encoder); | ||
86 | drm_encoder_cleanup(encoder); | ||
87 | kfree(mdp4_lcdc_encoder); | ||
88 | } | ||
89 | |||
90 | static const struct drm_encoder_funcs mdp4_lcdc_encoder_funcs = { | ||
91 | .destroy = mdp4_lcdc_encoder_destroy, | ||
92 | }; | ||
93 | |||
94 | /* this should probably be a helper: */ | ||
95 | struct drm_connector *get_connector(struct drm_encoder *encoder) | ||
96 | { | ||
97 | struct drm_device *dev = encoder->dev; | ||
98 | struct drm_connector *connector; | ||
99 | |||
100 | list_for_each_entry(connector, &dev->mode_config.connector_list, head) | ||
101 | if (connector->encoder == encoder) | ||
102 | return connector; | ||
103 | |||
104 | return NULL; | ||
105 | } | ||
106 | |||
107 | static void setup_phy(struct drm_encoder *encoder) | ||
108 | { | ||
109 | struct drm_device *dev = encoder->dev; | ||
110 | struct drm_connector *connector = get_connector(encoder); | ||
111 | struct mdp4_kms *mdp4_kms = get_kms(encoder); | ||
112 | uint32_t lvds_intf = 0, lvds_phy_cfg0 = 0; | ||
113 | int bpp, nchan, swap; | ||
114 | |||
115 | if (!connector) | ||
116 | return; | ||
117 | |||
118 | bpp = 3 * connector->display_info.bpc; | ||
119 | |||
120 | if (!bpp) | ||
121 | bpp = 18; | ||
122 | |||
123 | /* TODO, these should come from panel somehow: */ | ||
124 | nchan = 1; | ||
125 | swap = 0; | ||
126 | |||
127 | switch (bpp) { | ||
128 | case 24: | ||
129 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(0), | ||
130 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x08) | | ||
131 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x05) | | ||
132 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x04) | | ||
133 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x03)); | ||
134 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(0), | ||
135 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x02) | | ||
136 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x01) | | ||
137 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x00)); | ||
138 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(1), | ||
139 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x11) | | ||
140 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x10) | | ||
141 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x0d) | | ||
142 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x0c)); | ||
143 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(1), | ||
144 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x0b) | | ||
145 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x0a) | | ||
146 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x09)); | ||
147 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(2), | ||
148 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x1a) | | ||
149 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x19) | | ||
150 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x18) | | ||
151 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x15)); | ||
152 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(2), | ||
153 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x14) | | ||
154 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x13) | | ||
155 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x12)); | ||
156 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(3), | ||
157 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x1b) | | ||
158 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x17) | | ||
159 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x16) | | ||
160 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x0f)); | ||
161 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(3), | ||
162 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x0e) | | ||
163 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x07) | | ||
164 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x06)); | ||
165 | if (nchan == 2) { | ||
166 | lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE3_EN | | ||
167 | MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE2_EN | | ||
168 | MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE1_EN | | ||
169 | MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE0_EN | | ||
170 | MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE3_EN | | ||
171 | MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN | | ||
172 | MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN | | ||
173 | MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN; | ||
174 | } else { | ||
175 | lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE3_EN | | ||
176 | MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN | | ||
177 | MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN | | ||
178 | MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN; | ||
179 | } | ||
180 | break; | ||
181 | |||
182 | case 18: | ||
183 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(0), | ||
184 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x0a) | | ||
185 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x07) | | ||
186 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x06) | | ||
187 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x05)); | ||
188 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(0), | ||
189 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x04) | | ||
190 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x03) | | ||
191 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x02)); | ||
192 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(1), | ||
193 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x13) | | ||
194 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x12) | | ||
195 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x0f) | | ||
196 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x0e)); | ||
197 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(1), | ||
198 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x0d) | | ||
199 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x0c) | | ||
200 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x0b)); | ||
201 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(2), | ||
202 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x1a) | | ||
203 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x19) | | ||
204 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x18) | | ||
205 | MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x17)); | ||
206 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(2), | ||
207 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x16) | | ||
208 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x15) | | ||
209 | MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x14)); | ||
210 | if (nchan == 2) { | ||
211 | lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE2_EN | | ||
212 | MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE1_EN | | ||
213 | MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE0_EN | | ||
214 | MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN | | ||
215 | MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN | | ||
216 | MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN; | ||
217 | } else { | ||
218 | lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN | | ||
219 | MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN | | ||
220 | MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN; | ||
221 | } | ||
222 | lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_RGB_OUT; | ||
223 | break; | ||
224 | |||
225 | default: | ||
226 | dev_err(dev->dev, "unknown bpp: %d\n", bpp); | ||
227 | return; | ||
228 | } | ||
229 | |||
230 | switch (nchan) { | ||
231 | case 1: | ||
232 | lvds_phy_cfg0 = MDP4_LVDS_PHY_CFG0_CHANNEL0; | ||
233 | lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH1_CLK_LANE_EN | | ||
234 | MDP4_LCDC_LVDS_INTF_CTL_MODE_SEL; | ||
235 | break; | ||
236 | case 2: | ||
237 | lvds_phy_cfg0 = MDP4_LVDS_PHY_CFG0_CHANNEL0 | | ||
238 | MDP4_LVDS_PHY_CFG0_CHANNEL1; | ||
239 | lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH2_CLK_LANE_EN | | ||
240 | MDP4_LCDC_LVDS_INTF_CTL_CH1_CLK_LANE_EN; | ||
241 | break; | ||
242 | default: | ||
243 | dev_err(dev->dev, "unknown # of channels: %d\n", nchan); | ||
244 | return; | ||
245 | } | ||
246 | |||
247 | if (swap) | ||
248 | lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH_SWAP; | ||
249 | |||
250 | lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_ENABLE; | ||
251 | |||
252 | mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG0, lvds_phy_cfg0); | ||
253 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_INTF_CTL, lvds_intf); | ||
254 | mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG2, 0x30); | ||
255 | |||
256 | mb(); | ||
257 | udelay(1); | ||
258 | lvds_phy_cfg0 |= MDP4_LVDS_PHY_CFG0_SERIALIZATION_ENBLE; | ||
259 | mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG0, lvds_phy_cfg0); | ||
260 | } | ||
261 | |||
262 | static void mdp4_lcdc_encoder_dpms(struct drm_encoder *encoder, int mode) | ||
263 | { | ||
264 | struct drm_device *dev = encoder->dev; | ||
265 | struct mdp4_lcdc_encoder *mdp4_lcdc_encoder = | ||
266 | to_mdp4_lcdc_encoder(encoder); | ||
267 | struct mdp4_kms *mdp4_kms = get_kms(encoder); | ||
268 | struct drm_panel *panel = mdp4_lcdc_encoder->panel; | ||
269 | bool enabled = (mode == DRM_MODE_DPMS_ON); | ||
270 | int i, ret; | ||
271 | |||
272 | DBG("mode=%d", mode); | ||
273 | |||
274 | if (enabled == mdp4_lcdc_encoder->enabled) | ||
275 | return; | ||
276 | |||
277 | if (enabled) { | ||
278 | unsigned long pc = mdp4_lcdc_encoder->pixclock; | ||
279 | int ret; | ||
280 | |||
281 | bs_set(mdp4_lcdc_encoder, 1); | ||
282 | |||
283 | for (i = 0; i < ARRAY_SIZE(mdp4_lcdc_encoder->regs); i++) { | ||
284 | ret = regulator_enable(mdp4_lcdc_encoder->regs[i]); | ||
285 | if (ret) | ||
286 | dev_err(dev->dev, "failed to enable regulator: %d\n", ret); | ||
287 | } | ||
288 | |||
289 | DBG("setting lcdc_clk=%lu", pc); | ||
290 | ret = clk_set_rate(mdp4_lcdc_encoder->lcdc_clk, pc); | ||
291 | if (ret) | ||
292 | dev_err(dev->dev, "failed to configure lcdc_clk: %d\n", ret); | ||
293 | ret = clk_prepare_enable(mdp4_lcdc_encoder->lcdc_clk); | ||
294 | if (ret) | ||
295 | dev_err(dev->dev, "failed to enable lcdc_clk: %d\n", ret); | ||
296 | |||
297 | if (panel) | ||
298 | drm_panel_enable(panel); | ||
299 | |||
300 | setup_phy(encoder); | ||
301 | |||
302 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_ENABLE, 1); | ||
303 | } else { | ||
304 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_ENABLE, 0); | ||
305 | |||
306 | if (panel) | ||
307 | drm_panel_disable(panel); | ||
308 | |||
309 | /* | ||
310 | * Wait for a vsync so we know the ENABLE=0 latched before | ||
311 | * the (connector) source of the vsync's gets disabled, | ||
312 | * otherwise we end up in a funny state if we re-enable | ||
313 | * before the disable latches, which results that some of | ||
314 | * the settings changes for the new modeset (like new | ||
315 | * scanout buffer) don't latch properly.. | ||
316 | */ | ||
317 | mdp_irq_wait(&mdp4_kms->base, MDP4_IRQ_PRIMARY_VSYNC); | ||
318 | |||
319 | clk_disable_unprepare(mdp4_lcdc_encoder->lcdc_clk); | ||
320 | |||
321 | for (i = 0; i < ARRAY_SIZE(mdp4_lcdc_encoder->regs); i++) { | ||
322 | ret = regulator_disable(mdp4_lcdc_encoder->regs[i]); | ||
323 | if (ret) | ||
324 | dev_err(dev->dev, "failed to disable regulator: %d\n", ret); | ||
325 | } | ||
326 | |||
327 | bs_set(mdp4_lcdc_encoder, 0); | ||
328 | } | ||
329 | |||
330 | mdp4_lcdc_encoder->enabled = enabled; | ||
331 | } | ||
332 | |||
333 | static bool mdp4_lcdc_encoder_mode_fixup(struct drm_encoder *encoder, | ||
334 | const struct drm_display_mode *mode, | ||
335 | struct drm_display_mode *adjusted_mode) | ||
336 | { | ||
337 | return true; | ||
338 | } | ||
339 | |||
340 | static void mdp4_lcdc_encoder_mode_set(struct drm_encoder *encoder, | ||
341 | struct drm_display_mode *mode, | ||
342 | struct drm_display_mode *adjusted_mode) | ||
343 | { | ||
344 | struct mdp4_lcdc_encoder *mdp4_lcdc_encoder = | ||
345 | to_mdp4_lcdc_encoder(encoder); | ||
346 | struct mdp4_kms *mdp4_kms = get_kms(encoder); | ||
347 | uint32_t lcdc_hsync_skew, vsync_period, vsync_len, ctrl_pol; | ||
348 | uint32_t display_v_start, display_v_end; | ||
349 | uint32_t hsync_start_x, hsync_end_x; | ||
350 | |||
351 | mode = adjusted_mode; | ||
352 | |||
353 | DBG("set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x", | ||
354 | mode->base.id, mode->name, | ||
355 | mode->vrefresh, mode->clock, | ||
356 | mode->hdisplay, mode->hsync_start, | ||
357 | mode->hsync_end, mode->htotal, | ||
358 | mode->vdisplay, mode->vsync_start, | ||
359 | mode->vsync_end, mode->vtotal, | ||
360 | mode->type, mode->flags); | ||
361 | |||
362 | mdp4_lcdc_encoder->pixclock = mode->clock * 1000; | ||
363 | |||
364 | DBG("pixclock=%lu", mdp4_lcdc_encoder->pixclock); | ||
365 | |||
366 | ctrl_pol = 0; | ||
367 | if (mode->flags & DRM_MODE_FLAG_NHSYNC) | ||
368 | ctrl_pol |= MDP4_LCDC_CTRL_POLARITY_HSYNC_LOW; | ||
369 | if (mode->flags & DRM_MODE_FLAG_NVSYNC) | ||
370 | ctrl_pol |= MDP4_LCDC_CTRL_POLARITY_VSYNC_LOW; | ||
371 | /* probably need to get DATA_EN polarity from panel.. */ | ||
372 | |||
373 | lcdc_hsync_skew = 0; /* get this from panel? */ | ||
374 | |||
375 | hsync_start_x = (mode->htotal - mode->hsync_start); | ||
376 | hsync_end_x = mode->htotal - (mode->hsync_start - mode->hdisplay) - 1; | ||
377 | |||
378 | vsync_period = mode->vtotal * mode->htotal; | ||
379 | vsync_len = (mode->vsync_end - mode->vsync_start) * mode->htotal; | ||
380 | display_v_start = (mode->vtotal - mode->vsync_start) * mode->htotal + lcdc_hsync_skew; | ||
381 | display_v_end = vsync_period - ((mode->vsync_start - mode->vdisplay) * mode->htotal) + lcdc_hsync_skew - 1; | ||
382 | |||
383 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_HSYNC_CTRL, | ||
384 | MDP4_LCDC_HSYNC_CTRL_PULSEW(mode->hsync_end - mode->hsync_start) | | ||
385 | MDP4_LCDC_HSYNC_CTRL_PERIOD(mode->htotal)); | ||
386 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_VSYNC_PERIOD, vsync_period); | ||
387 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_VSYNC_LEN, vsync_len); | ||
388 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_DISPLAY_HCTRL, | ||
389 | MDP4_LCDC_DISPLAY_HCTRL_START(hsync_start_x) | | ||
390 | MDP4_LCDC_DISPLAY_HCTRL_END(hsync_end_x)); | ||
391 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_DISPLAY_VSTART, display_v_start); | ||
392 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_DISPLAY_VEND, display_v_end); | ||
393 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_BORDER_CLR, 0); | ||
394 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_UNDERFLOW_CLR, | ||
395 | MDP4_LCDC_UNDERFLOW_CLR_ENABLE_RECOVERY | | ||
396 | MDP4_LCDC_UNDERFLOW_CLR_COLOR(0xff)); | ||
397 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_HSYNC_SKEW, lcdc_hsync_skew); | ||
398 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_CTRL_POLARITY, ctrl_pol); | ||
399 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_ACTIVE_HCTL, | ||
400 | MDP4_LCDC_ACTIVE_HCTL_START(0) | | ||
401 | MDP4_LCDC_ACTIVE_HCTL_END(0)); | ||
402 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_ACTIVE_VSTART, 0); | ||
403 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_ACTIVE_VEND, 0); | ||
404 | } | ||
405 | |||
406 | static void mdp4_lcdc_encoder_prepare(struct drm_encoder *encoder) | ||
407 | { | ||
408 | mdp4_lcdc_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); | ||
409 | } | ||
410 | |||
411 | static void mdp4_lcdc_encoder_commit(struct drm_encoder *encoder) | ||
412 | { | ||
413 | /* TODO: hard-coded for 18bpp: */ | ||
414 | mdp4_crtc_set_config(encoder->crtc, | ||
415 | MDP4_DMA_CONFIG_R_BPC(BPC6) | | ||
416 | MDP4_DMA_CONFIG_G_BPC(BPC6) | | ||
417 | MDP4_DMA_CONFIG_B_BPC(BPC6) | | ||
418 | MDP4_DMA_CONFIG_PACK_ALIGN_MSB | | ||
419 | MDP4_DMA_CONFIG_PACK(0x21) | | ||
420 | MDP4_DMA_CONFIG_DEFLKR_EN | | ||
421 | MDP4_DMA_CONFIG_DITHER_EN); | ||
422 | mdp4_crtc_set_intf(encoder->crtc, INTF_LCDC_DTV, 0); | ||
423 | mdp4_lcdc_encoder_dpms(encoder, DRM_MODE_DPMS_ON); | ||
424 | } | ||
425 | |||
426 | static const struct drm_encoder_helper_funcs mdp4_lcdc_encoder_helper_funcs = { | ||
427 | .dpms = mdp4_lcdc_encoder_dpms, | ||
428 | .mode_fixup = mdp4_lcdc_encoder_mode_fixup, | ||
429 | .mode_set = mdp4_lcdc_encoder_mode_set, | ||
430 | .prepare = mdp4_lcdc_encoder_prepare, | ||
431 | .commit = mdp4_lcdc_encoder_commit, | ||
432 | }; | ||
433 | |||
434 | long mdp4_lcdc_round_pixclk(struct drm_encoder *encoder, unsigned long rate) | ||
435 | { | ||
436 | struct mdp4_lcdc_encoder *mdp4_lcdc_encoder = | ||
437 | to_mdp4_lcdc_encoder(encoder); | ||
438 | return clk_round_rate(mdp4_lcdc_encoder->lcdc_clk, rate); | ||
439 | } | ||
440 | |||
441 | /* initialize encoder */ | ||
442 | struct drm_encoder *mdp4_lcdc_encoder_init(struct drm_device *dev, | ||
443 | struct drm_panel *panel) | ||
444 | { | ||
445 | struct drm_encoder *encoder = NULL; | ||
446 | struct mdp4_lcdc_encoder *mdp4_lcdc_encoder; | ||
447 | struct regulator *reg; | ||
448 | int ret; | ||
449 | |||
450 | mdp4_lcdc_encoder = kzalloc(sizeof(*mdp4_lcdc_encoder), GFP_KERNEL); | ||
451 | if (!mdp4_lcdc_encoder) { | ||
452 | ret = -ENOMEM; | ||
453 | goto fail; | ||
454 | } | ||
455 | |||
456 | mdp4_lcdc_encoder->panel = panel; | ||
457 | |||
458 | encoder = &mdp4_lcdc_encoder->base; | ||
459 | |||
460 | drm_encoder_init(dev, encoder, &mdp4_lcdc_encoder_funcs, | ||
461 | DRM_MODE_ENCODER_LVDS); | ||
462 | drm_encoder_helper_add(encoder, &mdp4_lcdc_encoder_helper_funcs); | ||
463 | |||
464 | /* TODO: do we need different pll in other cases? */ | ||
465 | mdp4_lcdc_encoder->lcdc_clk = mpd4_lvds_pll_init(dev); | ||
466 | if (IS_ERR(mdp4_lcdc_encoder->lcdc_clk)) { | ||
467 | dev_err(dev->dev, "failed to get lvds_clk\n"); | ||
468 | ret = PTR_ERR(mdp4_lcdc_encoder->lcdc_clk); | ||
469 | goto fail; | ||
470 | } | ||
471 | |||
472 | /* TODO: different regulators in other cases? */ | ||
473 | reg = devm_regulator_get(dev->dev, "lvds-vccs-3p3v"); | ||
474 | if (IS_ERR(reg)) { | ||
475 | ret = PTR_ERR(reg); | ||
476 | dev_err(dev->dev, "failed to get lvds-vccs-3p3v: %d\n", ret); | ||
477 | goto fail; | ||
478 | } | ||
479 | mdp4_lcdc_encoder->regs[0] = reg; | ||
480 | |||
481 | reg = devm_regulator_get(dev->dev, "lvds-pll-vdda"); | ||
482 | if (IS_ERR(reg)) { | ||
483 | ret = PTR_ERR(reg); | ||
484 | dev_err(dev->dev, "failed to get lvds-pll-vdda: %d\n", ret); | ||
485 | goto fail; | ||
486 | } | ||
487 | mdp4_lcdc_encoder->regs[1] = reg; | ||
488 | |||
489 | reg = devm_regulator_get(dev->dev, "lvds-vdda"); | ||
490 | if (IS_ERR(reg)) { | ||
491 | ret = PTR_ERR(reg); | ||
492 | dev_err(dev->dev, "failed to get lvds-vdda: %d\n", ret); | ||
493 | goto fail; | ||
494 | } | ||
495 | mdp4_lcdc_encoder->regs[2] = reg; | ||
496 | |||
497 | bs_init(mdp4_lcdc_encoder); | ||
498 | |||
499 | return encoder; | ||
500 | |||
501 | fail: | ||
502 | if (encoder) | ||
503 | mdp4_lcdc_encoder_destroy(encoder); | ||
504 | |||
505 | return ERR_PTR(ret); | ||
506 | } | ||
diff --git a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_connector.c b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_connector.c new file mode 100644 index 000000000000..310034688c15 --- /dev/null +++ b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_connector.c | |||
@@ -0,0 +1,151 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * Author: Vinay Simha <vinaysimha@inforcecomputing.com> | ||
5 | * | ||
6 | * This program is free software; you can redistribute it and/or modify it | ||
7 | * under the terms of the GNU General Public License version 2 as published by | ||
8 | * the Free Software Foundation. | ||
9 | * | ||
10 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
13 | * more details. | ||
14 | * | ||
15 | * You should have received a copy of the GNU General Public License along with | ||
16 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
17 | */ | ||
18 | |||
19 | #include <linux/gpio.h> | ||
20 | |||
21 | #include "mdp4_kms.h" | ||
22 | |||
23 | struct mdp4_lvds_connector { | ||
24 | struct drm_connector base; | ||
25 | struct drm_encoder *encoder; | ||
26 | struct drm_panel *panel; | ||
27 | }; | ||
28 | #define to_mdp4_lvds_connector(x) container_of(x, struct mdp4_lvds_connector, base) | ||
29 | |||
30 | static enum drm_connector_status mdp4_lvds_connector_detect( | ||
31 | struct drm_connector *connector, bool force) | ||
32 | { | ||
33 | struct mdp4_lvds_connector *mdp4_lvds_connector = | ||
34 | to_mdp4_lvds_connector(connector); | ||
35 | |||
36 | return mdp4_lvds_connector->panel ? | ||
37 | connector_status_connected : | ||
38 | connector_status_disconnected; | ||
39 | } | ||
40 | |||
41 | static void mdp4_lvds_connector_destroy(struct drm_connector *connector) | ||
42 | { | ||
43 | struct mdp4_lvds_connector *mdp4_lvds_connector = | ||
44 | to_mdp4_lvds_connector(connector); | ||
45 | struct drm_panel *panel = mdp4_lvds_connector->panel; | ||
46 | |||
47 | if (panel) | ||
48 | drm_panel_detach(panel); | ||
49 | |||
50 | drm_connector_unregister(connector); | ||
51 | drm_connector_cleanup(connector); | ||
52 | |||
53 | kfree(mdp4_lvds_connector); | ||
54 | } | ||
55 | |||
56 | static int mdp4_lvds_connector_get_modes(struct drm_connector *connector) | ||
57 | { | ||
58 | struct mdp4_lvds_connector *mdp4_lvds_connector = | ||
59 | to_mdp4_lvds_connector(connector); | ||
60 | struct drm_panel *panel = mdp4_lvds_connector->panel; | ||
61 | int ret = 0; | ||
62 | |||
63 | if (panel) | ||
64 | ret = panel->funcs->get_modes(panel); | ||
65 | |||
66 | return ret; | ||
67 | } | ||
68 | |||
69 | static int mdp4_lvds_connector_mode_valid(struct drm_connector *connector, | ||
70 | struct drm_display_mode *mode) | ||
71 | { | ||
72 | struct mdp4_lvds_connector *mdp4_lvds_connector = | ||
73 | to_mdp4_lvds_connector(connector); | ||
74 | struct drm_encoder *encoder = mdp4_lvds_connector->encoder; | ||
75 | long actual, requested; | ||
76 | |||
77 | requested = 1000 * mode->clock; | ||
78 | actual = mdp4_lcdc_round_pixclk(encoder, requested); | ||
79 | |||
80 | DBG("requested=%ld, actual=%ld", requested, actual); | ||
81 | |||
82 | if (actual != requested) | ||
83 | return MODE_CLOCK_RANGE; | ||
84 | |||
85 | return MODE_OK; | ||
86 | } | ||
87 | |||
88 | static struct drm_encoder * | ||
89 | mdp4_lvds_connector_best_encoder(struct drm_connector *connector) | ||
90 | { | ||
91 | struct mdp4_lvds_connector *mdp4_lvds_connector = | ||
92 | to_mdp4_lvds_connector(connector); | ||
93 | return mdp4_lvds_connector->encoder; | ||
94 | } | ||
95 | |||
96 | static const struct drm_connector_funcs mdp4_lvds_connector_funcs = { | ||
97 | .dpms = drm_helper_connector_dpms, | ||
98 | .detect = mdp4_lvds_connector_detect, | ||
99 | .fill_modes = drm_helper_probe_single_connector_modes, | ||
100 | .destroy = mdp4_lvds_connector_destroy, | ||
101 | }; | ||
102 | |||
103 | static const struct drm_connector_helper_funcs mdp4_lvds_connector_helper_funcs = { | ||
104 | .get_modes = mdp4_lvds_connector_get_modes, | ||
105 | .mode_valid = mdp4_lvds_connector_mode_valid, | ||
106 | .best_encoder = mdp4_lvds_connector_best_encoder, | ||
107 | }; | ||
108 | |||
109 | /* initialize connector */ | ||
110 | struct drm_connector *mdp4_lvds_connector_init(struct drm_device *dev, | ||
111 | struct drm_panel *panel, struct drm_encoder *encoder) | ||
112 | { | ||
113 | struct drm_connector *connector = NULL; | ||
114 | struct mdp4_lvds_connector *mdp4_lvds_connector; | ||
115 | int ret; | ||
116 | |||
117 | mdp4_lvds_connector = kzalloc(sizeof(*mdp4_lvds_connector), GFP_KERNEL); | ||
118 | if (!mdp4_lvds_connector) { | ||
119 | ret = -ENOMEM; | ||
120 | goto fail; | ||
121 | } | ||
122 | |||
123 | mdp4_lvds_connector->encoder = encoder; | ||
124 | mdp4_lvds_connector->panel = panel; | ||
125 | |||
126 | connector = &mdp4_lvds_connector->base; | ||
127 | |||
128 | drm_connector_init(dev, connector, &mdp4_lvds_connector_funcs, | ||
129 | DRM_MODE_CONNECTOR_LVDS); | ||
130 | drm_connector_helper_add(connector, &mdp4_lvds_connector_helper_funcs); | ||
131 | |||
132 | connector->polled = 0; | ||
133 | |||
134 | connector->interlace_allowed = 0; | ||
135 | connector->doublescan_allowed = 0; | ||
136 | |||
137 | drm_connector_register(connector); | ||
138 | |||
139 | drm_mode_connector_attach_encoder(connector, encoder); | ||
140 | |||
141 | if (panel) | ||
142 | drm_panel_attach(panel, connector); | ||
143 | |||
144 | return connector; | ||
145 | |||
146 | fail: | ||
147 | if (connector) | ||
148 | mdp4_lvds_connector_destroy(connector); | ||
149 | |||
150 | return ERR_PTR(ret); | ||
151 | } | ||
diff --git a/drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_pll.c b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_pll.c new file mode 100644 index 000000000000..ce4245971673 --- /dev/null +++ b/drivers/gpu/drm/msm/mdp/mdp4/mdp4_lvds_pll.c | |||
@@ -0,0 +1,172 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2014 Red Hat | ||
3 | * Author: Rob Clark <robdclark@gmail.com> | ||
4 | * | ||
5 | * This program is free software; you can redistribute it and/or modify it | ||
6 | * under the terms of the GNU General Public License version 2 as published by | ||
7 | * the Free Software Foundation. | ||
8 | * | ||
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | ||
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
12 | * more details. | ||
13 | * | ||
14 | * You should have received a copy of the GNU General Public License along with | ||
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | ||
16 | */ | ||
17 | |||
18 | #include <linux/clk.h> | ||
19 | #include <linux/clk-provider.h> | ||
20 | |||
21 | #include "mdp4_kms.h" | ||
22 | |||
23 | struct mdp4_lvds_pll { | ||
24 | struct clk_hw pll_hw; | ||
25 | struct drm_device *dev; | ||
26 | unsigned long pixclk; | ||
27 | }; | ||
28 | #define to_mdp4_lvds_pll(x) container_of(x, struct mdp4_lvds_pll, pll_hw) | ||
29 | |||
30 | static struct mdp4_kms *get_kms(struct mdp4_lvds_pll *lvds_pll) | ||
31 | { | ||
32 | struct msm_drm_private *priv = lvds_pll->dev->dev_private; | ||
33 | return to_mdp4_kms(to_mdp_kms(priv->kms)); | ||
34 | } | ||
35 | |||
36 | struct pll_rate { | ||
37 | unsigned long rate; | ||
38 | struct { | ||
39 | uint32_t val; | ||
40 | uint32_t reg; | ||
41 | } conf[32]; | ||
42 | }; | ||
43 | |||
44 | /* NOTE: keep sorted highest freq to lowest: */ | ||
45 | static const struct pll_rate freqtbl[] = { | ||
46 | { 72000000, { | ||
47 | { 0x8f, REG_MDP4_LVDS_PHY_PLL_CTRL_1 }, | ||
48 | { 0x30, REG_MDP4_LVDS_PHY_PLL_CTRL_2 }, | ||
49 | { 0xc6, REG_MDP4_LVDS_PHY_PLL_CTRL_3 }, | ||
50 | { 0x10, REG_MDP4_LVDS_PHY_PLL_CTRL_5 }, | ||
51 | { 0x07, REG_MDP4_LVDS_PHY_PLL_CTRL_6 }, | ||
52 | { 0x62, REG_MDP4_LVDS_PHY_PLL_CTRL_7 }, | ||
53 | { 0x41, REG_MDP4_LVDS_PHY_PLL_CTRL_8 }, | ||
54 | { 0x0d, REG_MDP4_LVDS_PHY_PLL_CTRL_9 }, | ||
55 | { 0, 0 } } | ||
56 | }, | ||
57 | }; | ||
58 | |||
59 | static const struct pll_rate *find_rate(unsigned long rate) | ||
60 | { | ||
61 | int i; | ||
62 | for (i = 1; i < ARRAY_SIZE(freqtbl); i++) | ||
63 | if (rate > freqtbl[i].rate) | ||
64 | return &freqtbl[i-1]; | ||
65 | return &freqtbl[i-1]; | ||
66 | } | ||
67 | |||
68 | static int mpd4_lvds_pll_enable(struct clk_hw *hw) | ||
69 | { | ||
70 | struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw); | ||
71 | struct mdp4_kms *mdp4_kms = get_kms(lvds_pll); | ||
72 | const struct pll_rate *pll_rate = find_rate(lvds_pll->pixclk); | ||
73 | int i; | ||
74 | |||
75 | DBG("pixclk=%lu (%lu)", lvds_pll->pixclk, pll_rate->rate); | ||
76 | |||
77 | if (WARN_ON(!pll_rate)) | ||
78 | return -EINVAL; | ||
79 | |||
80 | mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_PHY_RESET, 0x33); | ||
81 | |||
82 | for (i = 0; pll_rate->conf[i].reg; i++) | ||
83 | mdp4_write(mdp4_kms, pll_rate->conf[i].reg, pll_rate->conf[i].val); | ||
84 | |||
85 | mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_PLL_CTRL_0, 0x01); | ||
86 | |||
87 | /* Wait until LVDS PLL is locked and ready */ | ||
88 | while (!mdp4_read(mdp4_kms, REG_MDP4_LVDS_PHY_PLL_LOCKED)) | ||
89 | cpu_relax(); | ||
90 | |||
91 | return 0; | ||
92 | } | ||
93 | |||
94 | static void mpd4_lvds_pll_disable(struct clk_hw *hw) | ||
95 | { | ||
96 | struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw); | ||
97 | struct mdp4_kms *mdp4_kms = get_kms(lvds_pll); | ||
98 | |||
99 | DBG(""); | ||
100 | |||
101 | mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG0, 0x0); | ||
102 | mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_PLL_CTRL_0, 0x0); | ||
103 | } | ||
104 | |||
105 | static unsigned long mpd4_lvds_pll_recalc_rate(struct clk_hw *hw, | ||
106 | unsigned long parent_rate) | ||
107 | { | ||
108 | struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw); | ||
109 | return lvds_pll->pixclk; | ||
110 | } | ||
111 | |||
112 | static long mpd4_lvds_pll_round_rate(struct clk_hw *hw, unsigned long rate, | ||
113 | unsigned long *parent_rate) | ||
114 | { | ||
115 | const struct pll_rate *pll_rate = find_rate(rate); | ||
116 | return pll_rate->rate; | ||
117 | } | ||
118 | |||
119 | static int mpd4_lvds_pll_set_rate(struct clk_hw *hw, unsigned long rate, | ||
120 | unsigned long parent_rate) | ||
121 | { | ||
122 | struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw); | ||
123 | lvds_pll->pixclk = rate; | ||
124 | return 0; | ||
125 | } | ||
126 | |||
127 | |||
128 | static const struct clk_ops mpd4_lvds_pll_ops = { | ||
129 | .enable = mpd4_lvds_pll_enable, | ||
130 | .disable = mpd4_lvds_pll_disable, | ||
131 | .recalc_rate = mpd4_lvds_pll_recalc_rate, | ||
132 | .round_rate = mpd4_lvds_pll_round_rate, | ||
133 | .set_rate = mpd4_lvds_pll_set_rate, | ||
134 | }; | ||
135 | |||
136 | static const char *mpd4_lvds_pll_parents[] = { | ||
137 | "pxo", | ||
138 | }; | ||
139 | |||
140 | static struct clk_init_data pll_init = { | ||
141 | .name = "mpd4_lvds_pll", | ||
142 | .ops = &mpd4_lvds_pll_ops, | ||
143 | .parent_names = mpd4_lvds_pll_parents, | ||
144 | .num_parents = ARRAY_SIZE(mpd4_lvds_pll_parents), | ||
145 | }; | ||
146 | |||
147 | struct clk *mpd4_lvds_pll_init(struct drm_device *dev) | ||
148 | { | ||
149 | struct mdp4_lvds_pll *lvds_pll; | ||
150 | struct clk *clk; | ||
151 | int ret; | ||
152 | |||
153 | lvds_pll = devm_kzalloc(dev->dev, sizeof(*lvds_pll), GFP_KERNEL); | ||
154 | if (!lvds_pll) { | ||
155 | ret = -ENOMEM; | ||
156 | goto fail; | ||
157 | } | ||
158 | |||
159 | lvds_pll->dev = dev; | ||
160 | |||
161 | lvds_pll->pll_hw.init = &pll_init; | ||
162 | clk = devm_clk_register(dev->dev, &lvds_pll->pll_hw); | ||
163 | if (IS_ERR(clk)) { | ||
164 | ret = PTR_ERR(clk); | ||
165 | goto fail; | ||
166 | } | ||
167 | |||
168 | return clk; | ||
169 | |||
170 | fail: | ||
171 | return ERR_PTR(ret); | ||
172 | } | ||