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 | } | ||
