forked from Minki/linux
8a2cfea8cc
It looks like on OMAP3 some DSS pins need VDDS_DSI to function properly. This has not been confirmed from TI, but looking at figure 15-1 "Display subsystem highlight" from the TRM, some data pins come near the DSI and SDI blocks. This is not very hard evidence, but the fact remains that with the power on, pixels are ok, and with the power off, pixels are not ok. It may also be that VDDS_SDI is needed to power some pins, but as normally both VDDS_SDI and VDDS_DSI come from the same power source, this hasn't been shown. It seems that a single driver can only get a regulator once. This patch solves it by getting all the required regulators in one place, and from which the submodules then get the regulators they need. Signed-off-by: Tomi Valkeinen <tomi.valkeinen@nokia.com>
437 lines
8.9 KiB
C
437 lines
8.9 KiB
C
/*
|
|
* linux/drivers/video/omap2/dss/dpi.c
|
|
*
|
|
* Copyright (C) 2009 Nokia Corporation
|
|
* Author: Tomi Valkeinen <tomi.valkeinen@nokia.com>
|
|
*
|
|
* Some code and ideas taken from drivers/video/omap/ driver
|
|
* by Imre Deak.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 as published by
|
|
* the Free Software Foundation.
|
|
*
|
|
* 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#define DSS_SUBSYS_NAME "DPI"
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/clk.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/err.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regulator/consumer.h>
|
|
|
|
#include <plat/display.h>
|
|
#include <plat/cpu.h>
|
|
|
|
#include "dss.h"
|
|
|
|
static struct {
|
|
int update_enabled;
|
|
struct regulator *vdds_dsi_reg;
|
|
} dpi;
|
|
|
|
#ifdef CONFIG_OMAP2_DSS_USE_DSI_PLL
|
|
static int dpi_set_dsi_clk(bool is_tft, unsigned long pck_req,
|
|
unsigned long *fck, int *lck_div, int *pck_div)
|
|
{
|
|
struct dsi_clock_info dsi_cinfo;
|
|
struct dispc_clock_info dispc_cinfo;
|
|
int r;
|
|
|
|
r = dsi_pll_calc_clock_div_pck(is_tft, pck_req, &dsi_cinfo,
|
|
&dispc_cinfo);
|
|
if (r)
|
|
return r;
|
|
|
|
r = dsi_pll_set_clock_div(&dsi_cinfo);
|
|
if (r)
|
|
return r;
|
|
|
|
dss_select_clk_source(0, 1);
|
|
|
|
r = dispc_set_clock_div(&dispc_cinfo);
|
|
if (r)
|
|
return r;
|
|
|
|
*fck = dsi_cinfo.dsi1_pll_fclk;
|
|
*lck_div = dispc_cinfo.lck_div;
|
|
*pck_div = dispc_cinfo.pck_div;
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
static int dpi_set_dispc_clk(bool is_tft, unsigned long pck_req,
|
|
unsigned long *fck, int *lck_div, int *pck_div)
|
|
{
|
|
struct dss_clock_info dss_cinfo;
|
|
struct dispc_clock_info dispc_cinfo;
|
|
int r;
|
|
|
|
r = dss_calc_clock_div(is_tft, pck_req, &dss_cinfo, &dispc_cinfo);
|
|
if (r)
|
|
return r;
|
|
|
|
r = dss_set_clock_div(&dss_cinfo);
|
|
if (r)
|
|
return r;
|
|
|
|
r = dispc_set_clock_div(&dispc_cinfo);
|
|
if (r)
|
|
return r;
|
|
|
|
*fck = dss_cinfo.fck;
|
|
*lck_div = dispc_cinfo.lck_div;
|
|
*pck_div = dispc_cinfo.pck_div;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int dpi_set_mode(struct omap_dss_device *dssdev)
|
|
{
|
|
struct omap_video_timings *t = &dssdev->panel.timings;
|
|
int lck_div, pck_div;
|
|
unsigned long fck;
|
|
unsigned long pck;
|
|
bool is_tft;
|
|
int r = 0;
|
|
|
|
dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK1);
|
|
|
|
dispc_set_pol_freq(dssdev->panel.config, dssdev->panel.acbi,
|
|
dssdev->panel.acb);
|
|
|
|
is_tft = (dssdev->panel.config & OMAP_DSS_LCD_TFT) != 0;
|
|
|
|
#ifdef CONFIG_OMAP2_DSS_USE_DSI_PLL
|
|
r = dpi_set_dsi_clk(is_tft, t->pixel_clock * 1000,
|
|
&fck, &lck_div, &pck_div);
|
|
#else
|
|
r = dpi_set_dispc_clk(is_tft, t->pixel_clock * 1000,
|
|
&fck, &lck_div, &pck_div);
|
|
#endif
|
|
if (r)
|
|
goto err0;
|
|
|
|
pck = fck / lck_div / pck_div / 1000;
|
|
|
|
if (pck != t->pixel_clock) {
|
|
DSSWARN("Could not find exact pixel clock. "
|
|
"Requested %d kHz, got %lu kHz\n",
|
|
t->pixel_clock, pck);
|
|
|
|
t->pixel_clock = pck;
|
|
}
|
|
|
|
dispc_set_lcd_timings(t);
|
|
|
|
err0:
|
|
dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK1);
|
|
return r;
|
|
}
|
|
|
|
static int dpi_basic_init(struct omap_dss_device *dssdev)
|
|
{
|
|
bool is_tft;
|
|
|
|
is_tft = (dssdev->panel.config & OMAP_DSS_LCD_TFT) != 0;
|
|
|
|
dispc_set_parallel_interface_mode(OMAP_DSS_PARALLELMODE_BYPASS);
|
|
dispc_set_lcd_display_type(is_tft ? OMAP_DSS_LCD_DISPLAY_TFT :
|
|
OMAP_DSS_LCD_DISPLAY_STN);
|
|
dispc_set_tft_data_lines(dssdev->phy.dpi.data_lines);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dpi_display_enable(struct omap_dss_device *dssdev)
|
|
{
|
|
int r;
|
|
|
|
r = omap_dss_start_device(dssdev);
|
|
if (r) {
|
|
DSSERR("failed to start device\n");
|
|
goto err0;
|
|
}
|
|
|
|
if (dssdev->state != OMAP_DSS_DISPLAY_DISABLED) {
|
|
DSSERR("display already enabled\n");
|
|
r = -EINVAL;
|
|
goto err1;
|
|
}
|
|
|
|
if (cpu_is_omap34xx()) {
|
|
r = regulator_enable(dpi.vdds_dsi_reg);
|
|
if (r)
|
|
goto err2;
|
|
}
|
|
|
|
dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK1);
|
|
|
|
r = dpi_basic_init(dssdev);
|
|
if (r)
|
|
goto err3;
|
|
|
|
#ifdef CONFIG_OMAP2_DSS_USE_DSI_PLL
|
|
dss_clk_enable(DSS_CLK_FCK2);
|
|
r = dsi_pll_init(dssdev, 0, 1);
|
|
if (r)
|
|
goto err4;
|
|
#endif
|
|
r = dpi_set_mode(dssdev);
|
|
if (r)
|
|
goto err5;
|
|
|
|
mdelay(2);
|
|
|
|
dispc_enable_lcd_out(1);
|
|
|
|
r = dssdev->driver->enable(dssdev);
|
|
if (r)
|
|
goto err6;
|
|
|
|
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
|
|
|
|
return 0;
|
|
|
|
err6:
|
|
dispc_enable_lcd_out(0);
|
|
err5:
|
|
#ifdef CONFIG_OMAP2_DSS_USE_DSI_PLL
|
|
dsi_pll_uninit();
|
|
err4:
|
|
dss_clk_disable(DSS_CLK_FCK2);
|
|
#endif
|
|
err3:
|
|
dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK1);
|
|
err2:
|
|
if (cpu_is_omap34xx())
|
|
regulator_disable(dpi.vdds_dsi_reg);
|
|
err1:
|
|
omap_dss_stop_device(dssdev);
|
|
err0:
|
|
return r;
|
|
}
|
|
|
|
static int dpi_display_resume(struct omap_dss_device *dssdev);
|
|
|
|
static void dpi_display_disable(struct omap_dss_device *dssdev)
|
|
{
|
|
if (dssdev->state == OMAP_DSS_DISPLAY_DISABLED)
|
|
return;
|
|
|
|
if (dssdev->state == OMAP_DSS_DISPLAY_SUSPENDED)
|
|
dpi_display_resume(dssdev);
|
|
|
|
dssdev->driver->disable(dssdev);
|
|
|
|
dispc_enable_lcd_out(0);
|
|
|
|
#ifdef CONFIG_OMAP2_DSS_USE_DSI_PLL
|
|
dss_select_clk_source(0, 0);
|
|
dsi_pll_uninit();
|
|
dss_clk_disable(DSS_CLK_FCK2);
|
|
#endif
|
|
|
|
dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK1);
|
|
|
|
if (cpu_is_omap34xx())
|
|
regulator_disable(dpi.vdds_dsi_reg);
|
|
|
|
dssdev->state = OMAP_DSS_DISPLAY_DISABLED;
|
|
|
|
omap_dss_stop_device(dssdev);
|
|
}
|
|
|
|
static int dpi_display_suspend(struct omap_dss_device *dssdev)
|
|
{
|
|
if (dssdev->state != OMAP_DSS_DISPLAY_ACTIVE)
|
|
return -EINVAL;
|
|
|
|
DSSDBG("dpi_display_suspend\n");
|
|
|
|
if (dssdev->driver->suspend)
|
|
dssdev->driver->suspend(dssdev);
|
|
|
|
dispc_enable_lcd_out(0);
|
|
|
|
dss_clk_disable(DSS_CLK_ICK | DSS_CLK_FCK1);
|
|
|
|
if (cpu_is_omap34xx())
|
|
regulator_disable(dpi.vdds_dsi_reg);
|
|
|
|
dssdev->state = OMAP_DSS_DISPLAY_SUSPENDED;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dpi_display_resume(struct omap_dss_device *dssdev)
|
|
{
|
|
int r;
|
|
|
|
if (dssdev->state != OMAP_DSS_DISPLAY_SUSPENDED)
|
|
return -EINVAL;
|
|
|
|
DSSDBG("dpi_display_resume\n");
|
|
|
|
if (cpu_is_omap34xx()) {
|
|
r = regulator_enable(dpi.vdds_dsi_reg);
|
|
if (r)
|
|
goto err0;
|
|
}
|
|
|
|
dss_clk_enable(DSS_CLK_ICK | DSS_CLK_FCK1);
|
|
|
|
dispc_enable_lcd_out(1);
|
|
|
|
if (dssdev->driver->resume)
|
|
dssdev->driver->resume(dssdev);
|
|
|
|
dssdev->state = OMAP_DSS_DISPLAY_ACTIVE;
|
|
|
|
return 0;
|
|
err0:
|
|
return r;
|
|
}
|
|
|
|
static void dpi_set_timings(struct omap_dss_device *dssdev,
|
|
struct omap_video_timings *timings)
|
|
{
|
|
DSSDBG("dpi_set_timings\n");
|
|
dssdev->panel.timings = *timings;
|
|
if (dssdev->state == OMAP_DSS_DISPLAY_ACTIVE) {
|
|
dpi_set_mode(dssdev);
|
|
dispc_go(OMAP_DSS_CHANNEL_LCD);
|
|
}
|
|
}
|
|
|
|
static int dpi_check_timings(struct omap_dss_device *dssdev,
|
|
struct omap_video_timings *timings)
|
|
{
|
|
bool is_tft;
|
|
int r;
|
|
int lck_div, pck_div;
|
|
unsigned long fck;
|
|
unsigned long pck;
|
|
|
|
if (!dispc_lcd_timings_ok(timings))
|
|
return -EINVAL;
|
|
|
|
if (timings->pixel_clock == 0)
|
|
return -EINVAL;
|
|
|
|
is_tft = (dssdev->panel.config & OMAP_DSS_LCD_TFT) != 0;
|
|
|
|
#ifdef CONFIG_OMAP2_DSS_USE_DSI_PLL
|
|
{
|
|
struct dsi_clock_info dsi_cinfo;
|
|
struct dispc_clock_info dispc_cinfo;
|
|
r = dsi_pll_calc_clock_div_pck(is_tft,
|
|
timings->pixel_clock * 1000,
|
|
&dsi_cinfo, &dispc_cinfo);
|
|
|
|
if (r)
|
|
return r;
|
|
|
|
fck = dsi_cinfo.dsi1_pll_fclk;
|
|
lck_div = dispc_cinfo.lck_div;
|
|
pck_div = dispc_cinfo.pck_div;
|
|
}
|
|
#else
|
|
{
|
|
struct dss_clock_info dss_cinfo;
|
|
struct dispc_clock_info dispc_cinfo;
|
|
r = dss_calc_clock_div(is_tft, timings->pixel_clock * 1000,
|
|
&dss_cinfo, &dispc_cinfo);
|
|
|
|
if (r)
|
|
return r;
|
|
|
|
fck = dss_cinfo.fck;
|
|
lck_div = dispc_cinfo.lck_div;
|
|
pck_div = dispc_cinfo.pck_div;
|
|
}
|
|
#endif
|
|
|
|
pck = fck / lck_div / pck_div / 1000;
|
|
|
|
timings->pixel_clock = pck;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dpi_get_timings(struct omap_dss_device *dssdev,
|
|
struct omap_video_timings *timings)
|
|
{
|
|
*timings = dssdev->panel.timings;
|
|
}
|
|
|
|
static int dpi_display_set_update_mode(struct omap_dss_device *dssdev,
|
|
enum omap_dss_update_mode mode)
|
|
{
|
|
if (mode == OMAP_DSS_UPDATE_MANUAL)
|
|
return -EINVAL;
|
|
|
|
if (mode == OMAP_DSS_UPDATE_DISABLED) {
|
|
dispc_enable_lcd_out(0);
|
|
dpi.update_enabled = 0;
|
|
} else {
|
|
dispc_enable_lcd_out(1);
|
|
dpi.update_enabled = 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static enum omap_dss_update_mode dpi_display_get_update_mode(
|
|
struct omap_dss_device *dssdev)
|
|
{
|
|
return dpi.update_enabled ? OMAP_DSS_UPDATE_AUTO :
|
|
OMAP_DSS_UPDATE_DISABLED;
|
|
}
|
|
|
|
int dpi_init_display(struct omap_dss_device *dssdev)
|
|
{
|
|
DSSDBG("init_display\n");
|
|
|
|
dssdev->enable = dpi_display_enable;
|
|
dssdev->disable = dpi_display_disable;
|
|
dssdev->suspend = dpi_display_suspend;
|
|
dssdev->resume = dpi_display_resume;
|
|
dssdev->set_timings = dpi_set_timings;
|
|
dssdev->check_timings = dpi_check_timings;
|
|
dssdev->get_timings = dpi_get_timings;
|
|
dssdev->set_update_mode = dpi_display_set_update_mode;
|
|
dssdev->get_update_mode = dpi_display_get_update_mode;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int dpi_init(struct platform_device *pdev)
|
|
{
|
|
if (cpu_is_omap34xx()) {
|
|
dpi.vdds_dsi_reg = dss_get_vdds_dsi();
|
|
if (IS_ERR(dpi.vdds_dsi_reg)) {
|
|
DSSERR("can't get VDDS_DSI regulator\n");
|
|
return PTR_ERR(dpi.vdds_dsi_reg);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void dpi_exit(void)
|
|
{
|
|
}
|
|
|