Bryan Huntsman | 3f2bc4d | 2011-08-16 17:27:22 -0700 | [diff] [blame] | 1 | /* drivers/video/msm/mdp_lcdc.c |
| 2 | * |
| 3 | * Copyright (c) 2009 Google Inc. |
| 4 | * Copyright (c) 2009 Code Aurora Forum. All rights reserved. |
| 5 | * |
| 6 | * This software is licensed under the terms of the GNU General Public |
| 7 | * License version 2, as published by the Free Software Foundation, and |
| 8 | * may be copied, distributed, and modified under those terms. |
| 9 | * |
| 10 | * This program is distributed in the hope that it will be useful, |
| 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 13 | * GNU General Public License for more details. |
| 14 | * |
| 15 | * Author: Dima Zavin <dima@android.com> |
| 16 | */ |
| 17 | |
| 18 | #include <linux/clk.h> |
| 19 | #include <linux/err.h> |
| 20 | #include <linux/init.h> |
| 21 | #include <linux/kernel.h> |
| 22 | #include <linux/platform_device.h> |
| 23 | #include <linux/sched.h> |
| 24 | #include <linux/wait.h> |
| 25 | #include <linux/delay.h> |
| 26 | |
| 27 | #include <asm/io.h> |
| 28 | #include <asm/mach-types.h> |
| 29 | |
| 30 | #include <mach/msm_fb.h> |
| 31 | |
| 32 | #include "mdp_hw.h" |
| 33 | |
| 34 | struct mdp_lcdc_info { |
| 35 | struct mdp_info *mdp; |
| 36 | struct clk *mdp_clk; |
| 37 | struct clk *pclk; |
| 38 | struct clk *pad_pclk; |
| 39 | struct msm_panel_data fb_panel_data; |
| 40 | struct platform_device fb_pdev; |
| 41 | struct msm_lcdc_platform_data *pdata; |
| 42 | uint32_t fb_start; |
| 43 | |
| 44 | struct msmfb_callback frame_start_cb; |
| 45 | wait_queue_head_t vsync_waitq; |
| 46 | int got_vsync; |
| 47 | |
| 48 | struct { |
| 49 | uint32_t clk_rate; |
| 50 | uint32_t hsync_ctl; |
| 51 | uint32_t vsync_period; |
| 52 | uint32_t vsync_pulse_width; |
| 53 | uint32_t display_hctl; |
| 54 | uint32_t display_vstart; |
| 55 | uint32_t display_vend; |
| 56 | uint32_t hsync_skew; |
| 57 | uint32_t polarity; |
| 58 | } parms; |
| 59 | }; |
| 60 | |
| 61 | static struct mdp_device *mdp_dev; |
| 62 | |
| 63 | #define panel_to_lcdc(p) container_of((p), struct mdp_lcdc_info, fb_panel_data) |
| 64 | |
| 65 | static int lcdc_unblank(struct msm_panel_data *fb_panel) |
| 66 | { |
| 67 | struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel); |
| 68 | struct msm_lcdc_panel_ops *panel_ops = lcdc->pdata->panel_ops; |
| 69 | |
| 70 | pr_info("%s: ()\n", __func__); |
| 71 | panel_ops->unblank(panel_ops); |
| 72 | |
| 73 | return 0; |
| 74 | } |
| 75 | |
| 76 | static int lcdc_blank(struct msm_panel_data *fb_panel) |
| 77 | { |
| 78 | struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel); |
| 79 | struct msm_lcdc_panel_ops *panel_ops = lcdc->pdata->panel_ops; |
| 80 | |
| 81 | pr_info("%s: ()\n", __func__); |
| 82 | panel_ops->blank(panel_ops); |
| 83 | |
| 84 | return 0; |
| 85 | } |
| 86 | |
| 87 | static int lcdc_suspend(struct msm_panel_data *fb_panel) |
| 88 | { |
| 89 | struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel); |
| 90 | |
| 91 | pr_info("%s: suspending\n", __func__); |
| 92 | |
| 93 | mdp_writel(lcdc->mdp, 0, MDP_LCDC_EN); |
| 94 | clk_disable(lcdc->pad_pclk); |
| 95 | clk_disable(lcdc->pclk); |
| 96 | clk_disable(lcdc->mdp_clk); |
| 97 | |
| 98 | return 0; |
| 99 | } |
| 100 | |
| 101 | static int lcdc_resume(struct msm_panel_data *fb_panel) |
| 102 | { |
| 103 | struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel); |
| 104 | |
| 105 | pr_info("%s: resuming\n", __func__); |
| 106 | |
| 107 | clk_enable(lcdc->mdp_clk); |
| 108 | clk_enable(lcdc->pclk); |
| 109 | clk_enable(lcdc->pad_pclk); |
| 110 | mdp_writel(lcdc->mdp, 1, MDP_LCDC_EN); |
| 111 | |
| 112 | return 0; |
| 113 | } |
| 114 | |
| 115 | static int lcdc_hw_init(struct mdp_lcdc_info *lcdc) |
| 116 | { |
| 117 | struct msm_panel_data *fb_panel = &lcdc->fb_panel_data; |
| 118 | uint32_t dma_cfg; |
| 119 | |
| 120 | clk_enable(lcdc->mdp_clk); |
| 121 | clk_enable(lcdc->pclk); |
| 122 | clk_enable(lcdc->pad_pclk); |
| 123 | |
| 124 | clk_set_rate(lcdc->pclk, lcdc->parms.clk_rate); |
| 125 | clk_set_rate(lcdc->pad_pclk, lcdc->parms.clk_rate); |
| 126 | |
| 127 | /* write the lcdc params */ |
| 128 | mdp_writel(lcdc->mdp, lcdc->parms.hsync_ctl, MDP_LCDC_HSYNC_CTL); |
| 129 | mdp_writel(lcdc->mdp, lcdc->parms.vsync_period, MDP_LCDC_VSYNC_PERIOD); |
| 130 | mdp_writel(lcdc->mdp, lcdc->parms.vsync_pulse_width, |
| 131 | MDP_LCDC_VSYNC_PULSE_WIDTH); |
| 132 | mdp_writel(lcdc->mdp, lcdc->parms.display_hctl, MDP_LCDC_DISPLAY_HCTL); |
| 133 | mdp_writel(lcdc->mdp, lcdc->parms.display_vstart, |
| 134 | MDP_LCDC_DISPLAY_V_START); |
| 135 | mdp_writel(lcdc->mdp, lcdc->parms.display_vend, MDP_LCDC_DISPLAY_V_END); |
| 136 | mdp_writel(lcdc->mdp, lcdc->parms.hsync_skew, MDP_LCDC_HSYNC_SKEW); |
| 137 | |
| 138 | mdp_writel(lcdc->mdp, 0, MDP_LCDC_BORDER_CLR); |
| 139 | mdp_writel(lcdc->mdp, 0xff, MDP_LCDC_UNDERFLOW_CTL); |
| 140 | mdp_writel(lcdc->mdp, 0, MDP_LCDC_ACTIVE_HCTL); |
| 141 | mdp_writel(lcdc->mdp, 0, MDP_LCDC_ACTIVE_V_START); |
| 142 | mdp_writel(lcdc->mdp, 0, MDP_LCDC_ACTIVE_V_END); |
| 143 | mdp_writel(lcdc->mdp, lcdc->parms.polarity, MDP_LCDC_CTL_POLARITY); |
| 144 | |
| 145 | /* config the dma_p block that drives the lcdc data */ |
| 146 | mdp_writel(lcdc->mdp, lcdc->fb_start, MDP_DMA_P_IBUF_ADDR); |
| 147 | mdp_writel(lcdc->mdp, (((fb_panel->fb_data->yres & 0x7ff) << 16) | |
| 148 | (fb_panel->fb_data->xres & 0x7ff)), |
| 149 | MDP_DMA_P_SIZE); |
| 150 | |
| 151 | mdp_writel(lcdc->mdp, 0, MDP_DMA_P_OUT_XY); |
| 152 | |
| 153 | dma_cfg = mdp_readl(lcdc->mdp, MDP_DMA_P_CONFIG); |
| 154 | dma_cfg |= (DMA_PACK_ALIGN_LSB | |
| 155 | DMA_PACK_PATTERN_RGB | |
| 156 | DMA_DITHER_EN); |
| 157 | dma_cfg |= DMA_OUT_SEL_LCDC; |
| 158 | dma_cfg &= ~DMA_DST_BITS_MASK; |
| 159 | |
| 160 | if (fb_panel->fb_data->output_format == MSM_MDP_OUT_IF_FMT_RGB666) |
| 161 | dma_cfg |= DMA_DSTC0G_6BITS | DMA_DSTC1B_6BITS | DMA_DSTC2R_6BITS; |
| 162 | else |
| 163 | dma_cfg |= DMA_DSTC0G_6BITS | DMA_DSTC1B_5BITS | DMA_DSTC2R_5BITS; |
| 164 | |
| 165 | mdp_writel(lcdc->mdp, dma_cfg, MDP_DMA_P_CONFIG); |
| 166 | |
| 167 | /* enable the lcdc timing generation */ |
| 168 | mdp_writel(lcdc->mdp, 1, MDP_LCDC_EN); |
| 169 | |
| 170 | return 0; |
| 171 | } |
| 172 | |
| 173 | static void lcdc_wait_vsync(struct msm_panel_data *panel) |
| 174 | { |
| 175 | struct mdp_lcdc_info *lcdc = panel_to_lcdc(panel); |
| 176 | int ret; |
| 177 | |
| 178 | ret = wait_event_timeout(lcdc->vsync_waitq, lcdc->got_vsync, HZ / 2); |
| 179 | if (!ret && !lcdc->got_vsync) |
| 180 | pr_err("%s: timeout waiting for VSYNC\n", __func__); |
| 181 | lcdc->got_vsync = 0; |
| 182 | } |
| 183 | |
| 184 | static void lcdc_request_vsync(struct msm_panel_data *fb_panel, |
| 185 | struct msmfb_callback *vsync_cb) |
| 186 | { |
| 187 | struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel); |
| 188 | |
| 189 | /* the vsync callback will start the dma */ |
| 190 | vsync_cb->func(vsync_cb); |
| 191 | lcdc->got_vsync = 0; |
| 192 | mdp_out_if_req_irq(mdp_dev, MSM_LCDC_INTERFACE, MDP_LCDC_FRAME_START, |
| 193 | &lcdc->frame_start_cb); |
| 194 | lcdc_wait_vsync(fb_panel); |
| 195 | } |
| 196 | |
| 197 | static void lcdc_clear_vsync(struct msm_panel_data *fb_panel) |
| 198 | { |
| 199 | struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel); |
| 200 | lcdc->got_vsync = 0; |
| 201 | mdp_out_if_req_irq(mdp_dev, MSM_LCDC_INTERFACE, 0, NULL); |
| 202 | } |
| 203 | |
| 204 | /* called in irq context with mdp lock held, when mdp gets the |
| 205 | * MDP_LCDC_FRAME_START interrupt */ |
| 206 | static void lcdc_frame_start(struct msmfb_callback *cb) |
| 207 | { |
| 208 | struct mdp_lcdc_info *lcdc; |
| 209 | |
| 210 | lcdc = container_of(cb, struct mdp_lcdc_info, frame_start_cb); |
| 211 | |
| 212 | lcdc->got_vsync = 1; |
| 213 | wake_up(&lcdc->vsync_waitq); |
| 214 | } |
| 215 | |
| 216 | static void lcdc_dma_start(void *priv, uint32_t addr, uint32_t stride, |
| 217 | uint32_t width, uint32_t height, uint32_t x, |
| 218 | uint32_t y) |
| 219 | { |
| 220 | struct mdp_lcdc_info *lcdc = priv; |
| 221 | |
| 222 | struct mdp_info *mdp = container_of(mdp_dev, struct mdp_info, mdp_dev); |
| 223 | if (mdp->dma_config_dirty) |
| 224 | { |
| 225 | mdp_writel(lcdc->mdp, 0, MDP_LCDC_EN); |
| 226 | mdelay(20); |
| 227 | mdp_dev->configure_dma(mdp_dev); |
| 228 | mdp_writel(lcdc->mdp, 1, MDP_LCDC_EN); |
| 229 | } |
| 230 | mdp_writel(lcdc->mdp, stride, MDP_DMA_P_IBUF_Y_STRIDE); |
| 231 | mdp_writel(lcdc->mdp, addr, MDP_DMA_P_IBUF_ADDR); |
| 232 | } |
| 233 | |
| 234 | static void precompute_timing_parms(struct mdp_lcdc_info *lcdc) |
| 235 | { |
| 236 | struct msm_lcdc_timing *timing = lcdc->pdata->timing; |
| 237 | struct msm_fb_data *fb_data = lcdc->pdata->fb_data; |
| 238 | unsigned int hsync_period; |
| 239 | unsigned int hsync_start_x; |
| 240 | unsigned int hsync_end_x; |
| 241 | unsigned int vsync_period; |
| 242 | unsigned int display_vstart; |
| 243 | unsigned int display_vend; |
| 244 | |
| 245 | hsync_period = (timing->hsync_back_porch + |
| 246 | fb_data->xres + timing->hsync_front_porch); |
| 247 | hsync_start_x = timing->hsync_back_porch; |
| 248 | hsync_end_x = hsync_start_x + fb_data->xres - 1; |
| 249 | |
| 250 | vsync_period = (timing->vsync_back_porch + |
| 251 | fb_data->yres + timing->vsync_front_porch); |
| 252 | vsync_period *= hsync_period; |
| 253 | |
| 254 | display_vstart = timing->vsync_back_porch; |
| 255 | display_vstart *= hsync_period; |
| 256 | display_vstart += timing->hsync_skew; |
| 257 | |
| 258 | display_vend = (timing->vsync_back_porch + fb_data->yres) * |
| 259 | hsync_period; |
| 260 | display_vend += timing->hsync_skew - 1; |
| 261 | |
| 262 | /* register values we pre-compute at init time from the timing |
| 263 | * information in the panel info */ |
| 264 | lcdc->parms.hsync_ctl = (((hsync_period & 0xfff) << 16) | |
| 265 | (timing->hsync_pulse_width & 0xfff)); |
| 266 | lcdc->parms.vsync_period = vsync_period & 0xffffff; |
| 267 | lcdc->parms.vsync_pulse_width = (timing->vsync_pulse_width * |
| 268 | hsync_period) & 0xffffff; |
| 269 | |
| 270 | lcdc->parms.display_hctl = (((hsync_end_x & 0xfff) << 16) | |
| 271 | (hsync_start_x & 0xfff)); |
| 272 | lcdc->parms.display_vstart = display_vstart & 0xffffff; |
| 273 | lcdc->parms.display_vend = display_vend & 0xffffff; |
| 274 | lcdc->parms.hsync_skew = timing->hsync_skew & 0xfff; |
| 275 | lcdc->parms.polarity = ((timing->hsync_act_low << 0) | |
| 276 | (timing->vsync_act_low << 1) | |
| 277 | (timing->den_act_low << 2)); |
| 278 | lcdc->parms.clk_rate = timing->clk_rate; |
| 279 | } |
| 280 | |
| 281 | static int mdp_lcdc_probe(struct platform_device *pdev) |
| 282 | { |
| 283 | struct msm_lcdc_platform_data *pdata = pdev->dev.platform_data; |
| 284 | struct mdp_lcdc_info *lcdc; |
| 285 | int ret = 0; |
| 286 | |
| 287 | if (!pdata) { |
| 288 | pr_err("%s: no LCDC platform data found\n", __func__); |
| 289 | return -EINVAL; |
| 290 | } |
| 291 | |
| 292 | lcdc = kzalloc(sizeof(struct mdp_lcdc_info), GFP_KERNEL); |
| 293 | if (!lcdc) |
| 294 | return -ENOMEM; |
| 295 | |
| 296 | /* We don't actually own the clocks, the mdp does. */ |
| 297 | lcdc->mdp_clk = clk_get(mdp_dev->dev.parent, "mdp_clk"); |
| 298 | if (IS_ERR(lcdc->mdp_clk)) { |
| 299 | pr_err("%s: failed to get mdp_clk\n", __func__); |
| 300 | ret = PTR_ERR(lcdc->mdp_clk); |
| 301 | goto err_get_mdp_clk; |
| 302 | } |
| 303 | |
| 304 | lcdc->pclk = clk_get(mdp_dev->dev.parent, "lcdc_pclk_clk"); |
| 305 | if (IS_ERR(lcdc->pclk)) { |
| 306 | pr_err("%s: failed to get lcdc_pclk\n", __func__); |
| 307 | ret = PTR_ERR(lcdc->pclk); |
| 308 | goto err_get_pclk; |
| 309 | } |
| 310 | |
| 311 | lcdc->pad_pclk = clk_get(mdp_dev->dev.parent, "lcdc_pad_pclk_clk"); |
| 312 | if (IS_ERR(lcdc->pad_pclk)) { |
| 313 | pr_err("%s: failed to get lcdc_pad_pclk\n", __func__); |
| 314 | ret = PTR_ERR(lcdc->pad_pclk); |
| 315 | goto err_get_pad_pclk; |
| 316 | } |
| 317 | |
| 318 | init_waitqueue_head(&lcdc->vsync_waitq); |
| 319 | lcdc->pdata = pdata; |
| 320 | lcdc->frame_start_cb.func = lcdc_frame_start; |
| 321 | |
| 322 | platform_set_drvdata(pdev, lcdc); |
| 323 | |
| 324 | mdp_out_if_register(mdp_dev, MSM_LCDC_INTERFACE, lcdc, MDP_DMA_P_DONE, |
| 325 | lcdc_dma_start); |
| 326 | |
| 327 | precompute_timing_parms(lcdc); |
| 328 | |
| 329 | lcdc->fb_start = pdata->fb_resource->start; |
| 330 | lcdc->mdp = container_of(mdp_dev, struct mdp_info, mdp_dev); |
| 331 | |
| 332 | lcdc->fb_panel_data.suspend = lcdc_suspend; |
| 333 | lcdc->fb_panel_data.resume = lcdc_resume; |
| 334 | lcdc->fb_panel_data.wait_vsync = lcdc_wait_vsync; |
| 335 | lcdc->fb_panel_data.request_vsync = lcdc_request_vsync; |
| 336 | lcdc->fb_panel_data.clear_vsync = lcdc_clear_vsync; |
| 337 | lcdc->fb_panel_data.blank = lcdc_blank; |
| 338 | lcdc->fb_panel_data.unblank = lcdc_unblank; |
| 339 | lcdc->fb_panel_data.fb_data = pdata->fb_data; |
| 340 | lcdc->fb_panel_data.interface_type = MSM_LCDC_INTERFACE; |
| 341 | |
| 342 | ret = lcdc_hw_init(lcdc); |
| 343 | if (ret) { |
| 344 | pr_err("%s: Cannot initialize the mdp_lcdc\n", __func__); |
| 345 | goto err_hw_init; |
| 346 | } |
| 347 | |
| 348 | lcdc->fb_pdev.name = "msm_panel"; |
| 349 | lcdc->fb_pdev.id = pdata->fb_id; |
| 350 | lcdc->fb_pdev.resource = pdata->fb_resource; |
| 351 | lcdc->fb_pdev.num_resources = 1; |
| 352 | lcdc->fb_pdev.dev.platform_data = &lcdc->fb_panel_data; |
| 353 | |
| 354 | if (pdata->panel_ops->init) |
| 355 | pdata->panel_ops->init(pdata->panel_ops); |
| 356 | |
| 357 | ret = platform_device_register(&lcdc->fb_pdev); |
| 358 | if (ret) { |
| 359 | pr_err("%s: Cannot register msm_panel pdev\n", __func__); |
| 360 | goto err_plat_dev_reg; |
| 361 | } |
| 362 | |
| 363 | pr_info("%s: initialized\n", __func__); |
| 364 | |
| 365 | return 0; |
| 366 | |
| 367 | err_plat_dev_reg: |
| 368 | err_hw_init: |
| 369 | platform_set_drvdata(pdev, NULL); |
| 370 | clk_put(lcdc->pad_pclk); |
| 371 | err_get_pad_pclk: |
| 372 | clk_put(lcdc->pclk); |
| 373 | err_get_pclk: |
| 374 | clk_put(lcdc->mdp_clk); |
| 375 | err_get_mdp_clk: |
| 376 | kfree(lcdc); |
| 377 | return ret; |
| 378 | } |
| 379 | |
| 380 | static int mdp_lcdc_remove(struct platform_device *pdev) |
| 381 | { |
| 382 | struct mdp_lcdc_info *lcdc = platform_get_drvdata(pdev); |
| 383 | |
| 384 | platform_set_drvdata(pdev, NULL); |
| 385 | |
| 386 | clk_put(lcdc->pclk); |
| 387 | clk_put(lcdc->pad_pclk); |
| 388 | kfree(lcdc); |
| 389 | |
| 390 | return 0; |
| 391 | } |
| 392 | |
| 393 | static struct platform_driver mdp_lcdc_driver = { |
| 394 | .probe = mdp_lcdc_probe, |
| 395 | .remove = mdp_lcdc_remove, |
| 396 | .driver = { |
| 397 | .name = "msm_mdp_lcdc", |
| 398 | .owner = THIS_MODULE, |
| 399 | }, |
| 400 | }; |
| 401 | |
| 402 | static int mdp_lcdc_add_mdp_device(struct device *dev, |
| 403 | struct class_interface *class_intf) |
| 404 | { |
| 405 | /* might need locking if mulitple mdp devices */ |
| 406 | if (mdp_dev) |
| 407 | return 0; |
| 408 | mdp_dev = container_of(dev, struct mdp_device, dev); |
| 409 | return platform_driver_register(&mdp_lcdc_driver); |
| 410 | } |
| 411 | |
| 412 | static void mdp_lcdc_remove_mdp_device(struct device *dev, |
| 413 | struct class_interface *class_intf) |
| 414 | { |
| 415 | /* might need locking if mulitple mdp devices */ |
| 416 | if (dev != &mdp_dev->dev) |
| 417 | return; |
| 418 | platform_driver_unregister(&mdp_lcdc_driver); |
| 419 | mdp_dev = NULL; |
| 420 | } |
| 421 | |
| 422 | static struct class_interface mdp_lcdc_interface = { |
| 423 | .add_dev = &mdp_lcdc_add_mdp_device, |
| 424 | .remove_dev = &mdp_lcdc_remove_mdp_device, |
| 425 | }; |
| 426 | |
| 427 | static int __init mdp_lcdc_init(void) |
| 428 | { |
| 429 | return register_mdp_client(&mdp_lcdc_interface); |
| 430 | } |
| 431 | |
| 432 | module_init(mdp_lcdc_init); |