/* drivers/video/msm/mdp_lcdc.c * * Copyright (c) 2009 Google Inc. * Copyright (c) 2009 The Linux Foundation. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * Author: Dima Zavin */ #include #include #include #include #include #include #include #include #include #include #include #include "mdp_hw.h" struct mdp_lcdc_info { struct mdp_info *mdp; struct clk *mdp_clk; struct clk *pclk; struct clk *pad_pclk; struct msm_panel_data fb_panel_data; struct platform_device fb_pdev; struct msm_lcdc_platform_data *pdata; uint32_t fb_start; struct msmfb_callback frame_start_cb; wait_queue_head_t vsync_waitq; int got_vsync; struct { uint32_t clk_rate; uint32_t hsync_ctl; uint32_t vsync_period; uint32_t vsync_pulse_width; uint32_t display_hctl; uint32_t display_vstart; uint32_t display_vend; uint32_t hsync_skew; uint32_t polarity; } parms; }; static struct mdp_device *mdp_dev; #define panel_to_lcdc(p) container_of((p), struct mdp_lcdc_info, fb_panel_data) static int lcdc_unblank(struct msm_panel_data *fb_panel) { struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel); struct msm_lcdc_panel_ops *panel_ops = lcdc->pdata->panel_ops; pr_info("%s: ()\n", __func__); panel_ops->unblank(panel_ops); return 0; } static int lcdc_blank(struct msm_panel_data *fb_panel) { struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel); struct msm_lcdc_panel_ops *panel_ops = lcdc->pdata->panel_ops; pr_info("%s: ()\n", __func__); panel_ops->blank(panel_ops); return 0; } static int lcdc_suspend(struct msm_panel_data *fb_panel) { struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel); pr_info("%s: suspending\n", __func__); mdp_writel(lcdc->mdp, 0, MDP_LCDC_EN); clk_disable_unprepare(lcdc->pad_pclk); clk_disable_unprepare(lcdc->pclk); clk_disable_unprepare(lcdc->mdp_clk); return 0; } static int lcdc_resume(struct msm_panel_data *fb_panel) { struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel); pr_info("%s: resuming\n", __func__); clk_prepare_enable(lcdc->mdp_clk); clk_prepare_enable(lcdc->pclk); clk_prepare_enable(lcdc->pad_pclk); mdp_writel(lcdc->mdp, 1, MDP_LCDC_EN); return 0; } static int lcdc_hw_init(struct mdp_lcdc_info *lcdc) { struct msm_panel_data *fb_panel = &lcdc->fb_panel_data; uint32_t dma_cfg; clk_prepare_enable(lcdc->mdp_clk); clk_prepare_enable(lcdc->pclk); clk_prepare_enable(lcdc->pad_pclk); clk_set_rate(lcdc->pclk, lcdc->parms.clk_rate); clk_set_rate(lcdc->pad_pclk, lcdc->parms.clk_rate); /* write the lcdc params */ mdp_writel(lcdc->mdp, lcdc->parms.hsync_ctl, MDP_LCDC_HSYNC_CTL); mdp_writel(lcdc->mdp, lcdc->parms.vsync_period, MDP_LCDC_VSYNC_PERIOD); mdp_writel(lcdc->mdp, lcdc->parms.vsync_pulse_width, MDP_LCDC_VSYNC_PULSE_WIDTH); mdp_writel(lcdc->mdp, lcdc->parms.display_hctl, MDP_LCDC_DISPLAY_HCTL); mdp_writel(lcdc->mdp, lcdc->parms.display_vstart, MDP_LCDC_DISPLAY_V_START); mdp_writel(lcdc->mdp, lcdc->parms.display_vend, MDP_LCDC_DISPLAY_V_END); mdp_writel(lcdc->mdp, lcdc->parms.hsync_skew, MDP_LCDC_HSYNC_SKEW); mdp_writel(lcdc->mdp, 0, MDP_LCDC_BORDER_CLR); mdp_writel(lcdc->mdp, 0xff, MDP_LCDC_UNDERFLOW_CTL); mdp_writel(lcdc->mdp, 0, MDP_LCDC_ACTIVE_HCTL); mdp_writel(lcdc->mdp, 0, MDP_LCDC_ACTIVE_V_START); mdp_writel(lcdc->mdp, 0, MDP_LCDC_ACTIVE_V_END); mdp_writel(lcdc->mdp, lcdc->parms.polarity, MDP_LCDC_CTL_POLARITY); /* config the dma_p block that drives the lcdc data */ mdp_writel(lcdc->mdp, lcdc->fb_start, MDP_DMA_P_IBUF_ADDR); mdp_writel(lcdc->mdp, (((fb_panel->fb_data->yres & 0x7ff) << 16) | (fb_panel->fb_data->xres & 0x7ff)), MDP_DMA_P_SIZE); mdp_writel(lcdc->mdp, 0, MDP_DMA_P_OUT_XY); dma_cfg = mdp_readl(lcdc->mdp, MDP_DMA_P_CONFIG); dma_cfg |= (DMA_PACK_ALIGN_LSB | DMA_PACK_PATTERN_RGB | DMA_DITHER_EN); dma_cfg |= DMA_OUT_SEL_LCDC; dma_cfg &= ~DMA_DST_BITS_MASK; if (fb_panel->fb_data->output_format == MSM_MDP_OUT_IF_FMT_RGB666) dma_cfg |= DMA_DSTC0G_6BITS | DMA_DSTC1B_6BITS | DMA_DSTC2R_6BITS; else dma_cfg |= DMA_DSTC0G_6BITS | DMA_DSTC1B_5BITS | DMA_DSTC2R_5BITS; mdp_writel(lcdc->mdp, dma_cfg, MDP_DMA_P_CONFIG); /* enable the lcdc timing generation */ mdp_writel(lcdc->mdp, 1, MDP_LCDC_EN); return 0; } static void lcdc_wait_vsync(struct msm_panel_data *panel) { struct mdp_lcdc_info *lcdc = panel_to_lcdc(panel); int ret; ret = wait_event_timeout(lcdc->vsync_waitq, lcdc->got_vsync, HZ / 2); if (!ret && !lcdc->got_vsync) pr_err("%s: timeout waiting for VSYNC\n", __func__); lcdc->got_vsync = 0; } static void lcdc_request_vsync(struct msm_panel_data *fb_panel, struct msmfb_callback *vsync_cb) { struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel); /* the vsync callback will start the dma */ vsync_cb->func(vsync_cb); lcdc->got_vsync = 0; mdp_out_if_req_irq(mdp_dev, MSM_LCDC_INTERFACE, MDP_LCDC_FRAME_START, &lcdc->frame_start_cb); lcdc_wait_vsync(fb_panel); } static void lcdc_clear_vsync(struct msm_panel_data *fb_panel) { struct mdp_lcdc_info *lcdc = panel_to_lcdc(fb_panel); lcdc->got_vsync = 0; mdp_out_if_req_irq(mdp_dev, MSM_LCDC_INTERFACE, 0, NULL); } /* called in irq context with mdp lock held, when mdp gets the * MDP_LCDC_FRAME_START interrupt */ static void lcdc_frame_start(struct msmfb_callback *cb) { struct mdp_lcdc_info *lcdc; lcdc = container_of(cb, struct mdp_lcdc_info, frame_start_cb); lcdc->got_vsync = 1; wake_up(&lcdc->vsync_waitq); } static void lcdc_dma_start(void *priv, uint32_t addr, uint32_t stride, uint32_t width, uint32_t height, uint32_t x, uint32_t y) { struct mdp_lcdc_info *lcdc = priv; struct mdp_info *mdp = container_of(mdp_dev, struct mdp_info, mdp_dev); if (mdp->dma_config_dirty) { mdp_writel(lcdc->mdp, 0, MDP_LCDC_EN); mdelay(20); mdp_dev->configure_dma(mdp_dev); mdp_writel(lcdc->mdp, 1, MDP_LCDC_EN); } mdp_writel(lcdc->mdp, stride, MDP_DMA_P_IBUF_Y_STRIDE); mdp_writel(lcdc->mdp, addr, MDP_DMA_P_IBUF_ADDR); } static void precompute_timing_parms(struct mdp_lcdc_info *lcdc) { struct msm_lcdc_timing *timing = lcdc->pdata->timing; struct msm_fb_data *fb_data = lcdc->pdata->fb_data; unsigned int hsync_period; unsigned int hsync_start_x; unsigned int hsync_end_x; unsigned int vsync_period; unsigned int display_vstart; unsigned int display_vend; hsync_period = (timing->hsync_back_porch + fb_data->xres + timing->hsync_front_porch); hsync_start_x = timing->hsync_back_porch; hsync_end_x = hsync_start_x + fb_data->xres - 1; vsync_period = (timing->vsync_back_porch + fb_data->yres + timing->vsync_front_porch); vsync_period *= hsync_period; display_vstart = timing->vsync_back_porch; display_vstart *= hsync_period; display_vstart += timing->hsync_skew; display_vend = (timing->vsync_back_porch + fb_data->yres) * hsync_period; display_vend += timing->hsync_skew - 1; /* register values we pre-compute at init time from the timing * information in the panel info */ lcdc->parms.hsync_ctl = (((hsync_period & 0xfff) << 16) | (timing->hsync_pulse_width & 0xfff)); lcdc->parms.vsync_period = vsync_period & 0xffffff; lcdc->parms.vsync_pulse_width = (timing->vsync_pulse_width * hsync_period) & 0xffffff; lcdc->parms.display_hctl = (((hsync_end_x & 0xfff) << 16) | (hsync_start_x & 0xfff)); lcdc->parms.display_vstart = display_vstart & 0xffffff; lcdc->parms.display_vend = display_vend & 0xffffff; lcdc->parms.hsync_skew = timing->hsync_skew & 0xfff; lcdc->parms.polarity = ((timing->hsync_act_low << 0) | (timing->vsync_act_low << 1) | (timing->den_act_low << 2)); lcdc->parms.clk_rate = timing->clk_rate; } static int mdp_lcdc_probe(struct platform_device *pdev) { struct msm_lcdc_platform_data *pdata = pdev->dev.platform_data; struct mdp_lcdc_info *lcdc; int ret = 0; if (!pdata) { pr_err("%s: no LCDC platform data found\n", __func__); return -EINVAL; } lcdc = kzalloc(sizeof(struct mdp_lcdc_info), GFP_KERNEL); if (!lcdc) return -ENOMEM; /* We don't actually own the clocks, the mdp does. */ lcdc->mdp_clk = clk_get(mdp_dev->dev.parent, "mdp_clk"); if (IS_ERR(lcdc->mdp_clk)) { pr_err("%s: failed to get mdp_clk\n", __func__); ret = PTR_ERR(lcdc->mdp_clk); goto err_get_mdp_clk; } lcdc->pclk = clk_get(mdp_dev->dev.parent, "lcdc_pclk_clk"); if (IS_ERR(lcdc->pclk)) { pr_err("%s: failed to get lcdc_pclk\n", __func__); ret = PTR_ERR(lcdc->pclk); goto err_get_pclk; } lcdc->pad_pclk = clk_get(mdp_dev->dev.parent, "lcdc_pad_pclk_clk"); if (IS_ERR(lcdc->pad_pclk)) { pr_err("%s: failed to get lcdc_pad_pclk\n", __func__); ret = PTR_ERR(lcdc->pad_pclk); goto err_get_pad_pclk; } init_waitqueue_head(&lcdc->vsync_waitq); lcdc->pdata = pdata; lcdc->frame_start_cb.func = lcdc_frame_start; platform_set_drvdata(pdev, lcdc); mdp_out_if_register(mdp_dev, MSM_LCDC_INTERFACE, lcdc, MDP_DMA_P_DONE, lcdc_dma_start); precompute_timing_parms(lcdc); lcdc->fb_start = pdata->fb_resource->start; lcdc->mdp = container_of(mdp_dev, struct mdp_info, mdp_dev); lcdc->fb_panel_data.suspend = lcdc_suspend; lcdc->fb_panel_data.resume = lcdc_resume; lcdc->fb_panel_data.wait_vsync = lcdc_wait_vsync; lcdc->fb_panel_data.request_vsync = lcdc_request_vsync; lcdc->fb_panel_data.clear_vsync = lcdc_clear_vsync; lcdc->fb_panel_data.blank = lcdc_blank; lcdc->fb_panel_data.unblank = lcdc_unblank; lcdc->fb_panel_data.fb_data = pdata->fb_data; lcdc->fb_panel_data.interface_type = MSM_LCDC_INTERFACE; ret = lcdc_hw_init(lcdc); if (ret) { pr_err("%s: Cannot initialize the mdp_lcdc\n", __func__); goto err_hw_init; } lcdc->fb_pdev.name = "msm_panel"; lcdc->fb_pdev.id = pdata->fb_id; lcdc->fb_pdev.resource = pdata->fb_resource; lcdc->fb_pdev.num_resources = 1; lcdc->fb_pdev.dev.platform_data = &lcdc->fb_panel_data; if (pdata->panel_ops->init) pdata->panel_ops->init(pdata->panel_ops); ret = platform_device_register(&lcdc->fb_pdev); if (ret) { pr_err("%s: Cannot register msm_panel pdev\n", __func__); goto err_plat_dev_reg; } pr_info("%s: initialized\n", __func__); return 0; err_plat_dev_reg: err_hw_init: platform_set_drvdata(pdev, NULL); clk_put(lcdc->pad_pclk); err_get_pad_pclk: clk_put(lcdc->pclk); err_get_pclk: clk_put(lcdc->mdp_clk); err_get_mdp_clk: kfree(lcdc); return ret; } static int mdp_lcdc_remove(struct platform_device *pdev) { struct mdp_lcdc_info *lcdc = platform_get_drvdata(pdev); platform_set_drvdata(pdev, NULL); clk_put(lcdc->pclk); clk_put(lcdc->pad_pclk); kfree(lcdc); return 0; } static struct platform_driver mdp_lcdc_driver = { .probe = mdp_lcdc_probe, .remove = mdp_lcdc_remove, .driver = { .name = "msm_mdp_lcdc", .owner = THIS_MODULE, }, }; static int mdp_lcdc_add_mdp_device(struct device *dev, struct class_interface *class_intf) { /* might need locking if mulitple mdp devices */ if (mdp_dev) return 0; mdp_dev = container_of(dev, struct mdp_device, dev); return platform_driver_register(&mdp_lcdc_driver); } static void mdp_lcdc_remove_mdp_device(struct device *dev, struct class_interface *class_intf) { /* might need locking if mulitple mdp devices */ if (dev != &mdp_dev->dev) return; platform_driver_unregister(&mdp_lcdc_driver); mdp_dev = NULL; } static struct class_interface mdp_lcdc_interface = { .add_dev = &mdp_lcdc_add_mdp_device, .remove_dev = &mdp_lcdc_remove_mdp_device, }; static int __init mdp_lcdc_init(void) { return register_mdp_client(&mdp_lcdc_interface); } module_init(mdp_lcdc_init);