mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-10-29 23:28:52 +01:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
14
drivers/gpu/drm/tilcdc/Kconfig
Normal file
14
drivers/gpu/drm/tilcdc/Kconfig
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
config DRM_TILCDC
|
||||
tristate "DRM Support for TI LCDC Display Controller"
|
||||
depends on DRM && OF && ARM
|
||||
select DRM_KMS_HELPER
|
||||
select DRM_KMS_FB_HELPER
|
||||
select DRM_KMS_CMA_HELPER
|
||||
select DRM_GEM_CMA_HELPER
|
||||
select VIDEOMODE_HELPERS
|
||||
select BACKLIGHT_CLASS_DEVICE
|
||||
select BACKLIGHT_LCD_SUPPORT
|
||||
help
|
||||
Choose this option if you have an TI SoC with LCDC display
|
||||
controller, for example AM33xx in beagle-bone, DA8xx, or
|
||||
OMAP-L1xx. This driver replaces the FB_DA8XX fbdev driver.
|
||||
13
drivers/gpu/drm/tilcdc/Makefile
Normal file
13
drivers/gpu/drm/tilcdc/Makefile
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
ccflags-y := -Iinclude/drm
|
||||
ifeq (, $(findstring -W,$(EXTRA_CFLAGS)))
|
||||
ccflags-y += -Werror
|
||||
endif
|
||||
|
||||
tilcdc-y := \
|
||||
tilcdc_crtc.o \
|
||||
tilcdc_tfp410.o \
|
||||
tilcdc_slave.o \
|
||||
tilcdc_panel.o \
|
||||
tilcdc_drv.o
|
||||
|
||||
obj-$(CONFIG_DRM_TILCDC) += tilcdc.o
|
||||
685
drivers/gpu/drm/tilcdc/tilcdc_crtc.c
Normal file
685
drivers/gpu/drm/tilcdc/tilcdc_crtc.c
Normal file
|
|
@ -0,0 +1,685 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Texas Instruments
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include "drm_flip_work.h"
|
||||
|
||||
#include "tilcdc_drv.h"
|
||||
#include "tilcdc_regs.h"
|
||||
|
||||
struct tilcdc_crtc {
|
||||
struct drm_crtc base;
|
||||
|
||||
const struct tilcdc_panel_info *info;
|
||||
uint32_t dirty;
|
||||
dma_addr_t start, end;
|
||||
struct drm_pending_vblank_event *event;
|
||||
int dpms;
|
||||
wait_queue_head_t frame_done_wq;
|
||||
bool frame_done;
|
||||
|
||||
/* fb currently set to scanout 0/1: */
|
||||
struct drm_framebuffer *scanout[2];
|
||||
|
||||
/* for deferred fb unref's: */
|
||||
struct drm_flip_work unref_work;
|
||||
};
|
||||
#define to_tilcdc_crtc(x) container_of(x, struct tilcdc_crtc, base)
|
||||
|
||||
static void unref_worker(struct drm_flip_work *work, void *val)
|
||||
{
|
||||
struct tilcdc_crtc *tilcdc_crtc =
|
||||
container_of(work, struct tilcdc_crtc, unref_work);
|
||||
struct drm_device *dev = tilcdc_crtc->base.dev;
|
||||
|
||||
mutex_lock(&dev->mode_config.mutex);
|
||||
drm_framebuffer_unreference(val);
|
||||
mutex_unlock(&dev->mode_config.mutex);
|
||||
}
|
||||
|
||||
static void set_scanout(struct drm_crtc *crtc, int n)
|
||||
{
|
||||
static const uint32_t base_reg[] = {
|
||||
LCDC_DMA_FB_BASE_ADDR_0_REG,
|
||||
LCDC_DMA_FB_BASE_ADDR_1_REG,
|
||||
};
|
||||
static const uint32_t ceil_reg[] = {
|
||||
LCDC_DMA_FB_CEILING_ADDR_0_REG,
|
||||
LCDC_DMA_FB_CEILING_ADDR_1_REG,
|
||||
};
|
||||
static const uint32_t stat[] = {
|
||||
LCDC_END_OF_FRAME0, LCDC_END_OF_FRAME1,
|
||||
};
|
||||
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
|
||||
pm_runtime_get_sync(dev->dev);
|
||||
tilcdc_write(dev, base_reg[n], tilcdc_crtc->start);
|
||||
tilcdc_write(dev, ceil_reg[n], tilcdc_crtc->end);
|
||||
if (tilcdc_crtc->scanout[n]) {
|
||||
drm_flip_work_queue(&tilcdc_crtc->unref_work, tilcdc_crtc->scanout[n]);
|
||||
drm_flip_work_commit(&tilcdc_crtc->unref_work, priv->wq);
|
||||
}
|
||||
tilcdc_crtc->scanout[n] = crtc->primary->fb;
|
||||
drm_framebuffer_reference(tilcdc_crtc->scanout[n]);
|
||||
tilcdc_crtc->dirty &= ~stat[n];
|
||||
pm_runtime_put_sync(dev->dev);
|
||||
}
|
||||
|
||||
static void update_scanout(struct drm_crtc *crtc)
|
||||
{
|
||||
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct drm_framebuffer *fb = crtc->primary->fb;
|
||||
struct drm_gem_cma_object *gem;
|
||||
unsigned int depth, bpp;
|
||||
|
||||
drm_fb_get_bpp_depth(fb->pixel_format, &depth, &bpp);
|
||||
gem = drm_fb_cma_get_gem_obj(fb, 0);
|
||||
|
||||
tilcdc_crtc->start = gem->paddr + fb->offsets[0] +
|
||||
(crtc->y * fb->pitches[0]) + (crtc->x * bpp/8);
|
||||
|
||||
tilcdc_crtc->end = tilcdc_crtc->start +
|
||||
(crtc->mode.vdisplay * fb->pitches[0]);
|
||||
|
||||
if (tilcdc_crtc->dpms == DRM_MODE_DPMS_ON) {
|
||||
/* already enabled, so just mark the frames that need
|
||||
* updating and they will be updated on vblank:
|
||||
*/
|
||||
tilcdc_crtc->dirty |= LCDC_END_OF_FRAME0 | LCDC_END_OF_FRAME1;
|
||||
drm_vblank_get(dev, 0);
|
||||
} else {
|
||||
/* not enabled yet, so update registers immediately: */
|
||||
set_scanout(crtc, 0);
|
||||
set_scanout(crtc, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void start(struct drm_crtc *crtc)
|
||||
{
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
|
||||
if (priv->rev == 2) {
|
||||
tilcdc_set(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
|
||||
msleep(1);
|
||||
tilcdc_clear(dev, LCDC_CLK_RESET_REG, LCDC_CLK_MAIN_RESET);
|
||||
msleep(1);
|
||||
}
|
||||
|
||||
tilcdc_set(dev, LCDC_DMA_CTRL_REG, LCDC_DUAL_FRAME_BUFFER_ENABLE);
|
||||
tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_PALETTE_LOAD_MODE(DATA_ONLY));
|
||||
tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
|
||||
}
|
||||
|
||||
static void stop(struct drm_crtc *crtc)
|
||||
{
|
||||
struct drm_device *dev = crtc->dev;
|
||||
|
||||
tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ENABLE);
|
||||
}
|
||||
|
||||
static void tilcdc_crtc_destroy(struct drm_crtc *crtc)
|
||||
{
|
||||
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
|
||||
|
||||
WARN_ON(tilcdc_crtc->dpms == DRM_MODE_DPMS_ON);
|
||||
|
||||
drm_crtc_cleanup(crtc);
|
||||
drm_flip_work_cleanup(&tilcdc_crtc->unref_work);
|
||||
|
||||
kfree(tilcdc_crtc);
|
||||
}
|
||||
|
||||
static int tilcdc_crtc_page_flip(struct drm_crtc *crtc,
|
||||
struct drm_framebuffer *fb,
|
||||
struct drm_pending_vblank_event *event,
|
||||
uint32_t page_flip_flags)
|
||||
{
|
||||
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
|
||||
struct drm_device *dev = crtc->dev;
|
||||
|
||||
if (tilcdc_crtc->event) {
|
||||
dev_err(dev->dev, "already pending page flip!\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
crtc->primary->fb = fb;
|
||||
tilcdc_crtc->event = event;
|
||||
update_scanout(crtc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tilcdc_crtc_dpms(struct drm_crtc *crtc, int mode)
|
||||
{
|
||||
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
|
||||
/* we really only care about on or off: */
|
||||
if (mode != DRM_MODE_DPMS_ON)
|
||||
mode = DRM_MODE_DPMS_OFF;
|
||||
|
||||
if (tilcdc_crtc->dpms == mode)
|
||||
return;
|
||||
|
||||
tilcdc_crtc->dpms = mode;
|
||||
|
||||
pm_runtime_get_sync(dev->dev);
|
||||
|
||||
if (mode == DRM_MODE_DPMS_ON) {
|
||||
pm_runtime_forbid(dev->dev);
|
||||
start(crtc);
|
||||
} else {
|
||||
tilcdc_crtc->frame_done = false;
|
||||
stop(crtc);
|
||||
|
||||
/*
|
||||
* if necessary wait for framedone irq which will still come
|
||||
* before putting things to sleep..
|
||||
*/
|
||||
if (priv->rev == 2) {
|
||||
int ret = wait_event_timeout(
|
||||
tilcdc_crtc->frame_done_wq,
|
||||
tilcdc_crtc->frame_done,
|
||||
msecs_to_jiffies(50));
|
||||
if (ret == 0)
|
||||
dev_err(dev->dev, "timeout waiting for framedone\n");
|
||||
}
|
||||
pm_runtime_allow(dev->dev);
|
||||
}
|
||||
|
||||
pm_runtime_put_sync(dev->dev);
|
||||
}
|
||||
|
||||
static bool tilcdc_crtc_mode_fixup(struct drm_crtc *crtc,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void tilcdc_crtc_prepare(struct drm_crtc *crtc)
|
||||
{
|
||||
tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
|
||||
}
|
||||
|
||||
static void tilcdc_crtc_commit(struct drm_crtc *crtc)
|
||||
{
|
||||
tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
|
||||
}
|
||||
|
||||
static int tilcdc_crtc_mode_set(struct drm_crtc *crtc,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode,
|
||||
int x, int y,
|
||||
struct drm_framebuffer *old_fb)
|
||||
{
|
||||
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
const struct tilcdc_panel_info *info = tilcdc_crtc->info;
|
||||
uint32_t reg, hbp, hfp, hsw, vbp, vfp, vsw;
|
||||
int ret;
|
||||
|
||||
ret = tilcdc_crtc_mode_valid(crtc, mode);
|
||||
if (WARN_ON(ret))
|
||||
return ret;
|
||||
|
||||
if (WARN_ON(!info))
|
||||
return -EINVAL;
|
||||
|
||||
pm_runtime_get_sync(dev->dev);
|
||||
|
||||
/* Configure the Burst Size and fifo threshold of DMA: */
|
||||
reg = tilcdc_read(dev, LCDC_DMA_CTRL_REG) & ~0x00000770;
|
||||
switch (info->dma_burst_sz) {
|
||||
case 1:
|
||||
reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_1);
|
||||
break;
|
||||
case 2:
|
||||
reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_2);
|
||||
break;
|
||||
case 4:
|
||||
reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_4);
|
||||
break;
|
||||
case 8:
|
||||
reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_8);
|
||||
break;
|
||||
case 16:
|
||||
reg |= LCDC_DMA_BURST_SIZE(LCDC_DMA_BURST_16);
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
reg |= (info->fifo_th << 8);
|
||||
tilcdc_write(dev, LCDC_DMA_CTRL_REG, reg);
|
||||
|
||||
/* Configure timings: */
|
||||
hbp = mode->htotal - mode->hsync_end;
|
||||
hfp = mode->hsync_start - mode->hdisplay;
|
||||
hsw = mode->hsync_end - mode->hsync_start;
|
||||
vbp = mode->vtotal - mode->vsync_end;
|
||||
vfp = mode->vsync_start - mode->vdisplay;
|
||||
vsw = mode->vsync_end - mode->vsync_start;
|
||||
|
||||
DBG("%dx%d, hbp=%u, hfp=%u, hsw=%u, vbp=%u, vfp=%u, vsw=%u",
|
||||
mode->hdisplay, mode->vdisplay, hbp, hfp, hsw, vbp, vfp, vsw);
|
||||
|
||||
/* Configure the AC Bias Period and Number of Transitions per Interrupt: */
|
||||
reg = tilcdc_read(dev, LCDC_RASTER_TIMING_2_REG) & ~0x000fff00;
|
||||
reg |= LCDC_AC_BIAS_FREQUENCY(info->ac_bias) |
|
||||
LCDC_AC_BIAS_TRANSITIONS_PER_INT(info->ac_bias_intrpt);
|
||||
|
||||
/*
|
||||
* subtract one from hfp, hbp, hsw because the hardware uses
|
||||
* a value of 0 as 1
|
||||
*/
|
||||
if (priv->rev == 2) {
|
||||
/* clear bits we're going to set */
|
||||
reg &= ~0x78000033;
|
||||
reg |= ((hfp-1) & 0x300) >> 8;
|
||||
reg |= ((hbp-1) & 0x300) >> 4;
|
||||
reg |= ((hsw-1) & 0x3c0) << 21;
|
||||
}
|
||||
tilcdc_write(dev, LCDC_RASTER_TIMING_2_REG, reg);
|
||||
|
||||
reg = (((mode->hdisplay >> 4) - 1) << 4) |
|
||||
(((hbp-1) & 0xff) << 24) |
|
||||
(((hfp-1) & 0xff) << 16) |
|
||||
(((hsw-1) & 0x3f) << 10);
|
||||
if (priv->rev == 2)
|
||||
reg |= (((mode->hdisplay >> 4) - 1) & 0x40) >> 3;
|
||||
tilcdc_write(dev, LCDC_RASTER_TIMING_0_REG, reg);
|
||||
|
||||
reg = ((mode->vdisplay - 1) & 0x3ff) |
|
||||
((vbp & 0xff) << 24) |
|
||||
((vfp & 0xff) << 16) |
|
||||
(((vsw-1) & 0x3f) << 10);
|
||||
tilcdc_write(dev, LCDC_RASTER_TIMING_1_REG, reg);
|
||||
|
||||
/*
|
||||
* be sure to set Bit 10 for the V2 LCDC controller,
|
||||
* otherwise limited to 1024 pixels width, stopping
|
||||
* 1920x1080 being suppoted.
|
||||
*/
|
||||
if (priv->rev == 2) {
|
||||
if ((mode->vdisplay - 1) & 0x400) {
|
||||
tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG,
|
||||
LCDC_LPP_B10);
|
||||
} else {
|
||||
tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG,
|
||||
LCDC_LPP_B10);
|
||||
}
|
||||
}
|
||||
|
||||
/* Configure display type: */
|
||||
reg = tilcdc_read(dev, LCDC_RASTER_CTRL_REG) &
|
||||
~(LCDC_TFT_MODE | LCDC_MONO_8BIT_MODE | LCDC_MONOCHROME_MODE |
|
||||
LCDC_V2_TFT_24BPP_MODE | LCDC_V2_TFT_24BPP_UNPACK | 0x000ff000);
|
||||
reg |= LCDC_TFT_MODE; /* no monochrome/passive support */
|
||||
if (info->tft_alt_mode)
|
||||
reg |= LCDC_TFT_ALT_ENABLE;
|
||||
if (priv->rev == 2) {
|
||||
unsigned int depth, bpp;
|
||||
|
||||
drm_fb_get_bpp_depth(crtc->primary->fb->pixel_format, &depth, &bpp);
|
||||
switch (bpp) {
|
||||
case 16:
|
||||
break;
|
||||
case 32:
|
||||
reg |= LCDC_V2_TFT_24BPP_UNPACK;
|
||||
/* fallthrough */
|
||||
case 24:
|
||||
reg |= LCDC_V2_TFT_24BPP_MODE;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev->dev, "invalid pixel format\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
reg |= info->fdd < 12;
|
||||
tilcdc_write(dev, LCDC_RASTER_CTRL_REG, reg);
|
||||
|
||||
if (info->invert_pxl_clk)
|
||||
tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK);
|
||||
else
|
||||
tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_PIXEL_CLOCK);
|
||||
|
||||
if (info->sync_ctrl)
|
||||
tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL);
|
||||
else
|
||||
tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_CTRL);
|
||||
|
||||
if (info->sync_edge)
|
||||
tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE);
|
||||
else
|
||||
tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_SYNC_EDGE);
|
||||
|
||||
/*
|
||||
* use value from adjusted_mode here as this might have been
|
||||
* changed as part of the fixup for slave encoders to solve the
|
||||
* issue where tilcdc timings are not VESA compliant
|
||||
*/
|
||||
if (adjusted_mode->flags & DRM_MODE_FLAG_NHSYNC)
|
||||
tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC);
|
||||
else
|
||||
tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_HSYNC);
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
|
||||
tilcdc_set(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC);
|
||||
else
|
||||
tilcdc_clear(dev, LCDC_RASTER_TIMING_2_REG, LCDC_INVERT_VSYNC);
|
||||
|
||||
if (info->raster_order)
|
||||
tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
|
||||
else
|
||||
tilcdc_clear(dev, LCDC_RASTER_CTRL_REG, LCDC_RASTER_ORDER);
|
||||
|
||||
|
||||
update_scanout(crtc);
|
||||
tilcdc_crtc_update_clk(crtc);
|
||||
|
||||
pm_runtime_put_sync(dev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tilcdc_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y,
|
||||
struct drm_framebuffer *old_fb)
|
||||
{
|
||||
update_scanout(crtc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct drm_crtc_funcs tilcdc_crtc_funcs = {
|
||||
.destroy = tilcdc_crtc_destroy,
|
||||
.set_config = drm_crtc_helper_set_config,
|
||||
.page_flip = tilcdc_crtc_page_flip,
|
||||
};
|
||||
|
||||
static const struct drm_crtc_helper_funcs tilcdc_crtc_helper_funcs = {
|
||||
.dpms = tilcdc_crtc_dpms,
|
||||
.mode_fixup = tilcdc_crtc_mode_fixup,
|
||||
.prepare = tilcdc_crtc_prepare,
|
||||
.commit = tilcdc_crtc_commit,
|
||||
.mode_set = tilcdc_crtc_mode_set,
|
||||
.mode_set_base = tilcdc_crtc_mode_set_base,
|
||||
};
|
||||
|
||||
int tilcdc_crtc_max_width(struct drm_crtc *crtc)
|
||||
{
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
int max_width = 0;
|
||||
|
||||
if (priv->rev == 1)
|
||||
max_width = 1024;
|
||||
else if (priv->rev == 2)
|
||||
max_width = 2048;
|
||||
|
||||
return max_width;
|
||||
}
|
||||
|
||||
int tilcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = crtc->dev->dev_private;
|
||||
unsigned int bandwidth;
|
||||
uint32_t hbp, hfp, hsw, vbp, vfp, vsw;
|
||||
|
||||
/*
|
||||
* check to see if the width is within the range that
|
||||
* the LCD Controller physically supports
|
||||
*/
|
||||
if (mode->hdisplay > tilcdc_crtc_max_width(crtc))
|
||||
return MODE_VIRTUAL_X;
|
||||
|
||||
/* width must be multiple of 16 */
|
||||
if (mode->hdisplay & 0xf)
|
||||
return MODE_VIRTUAL_X;
|
||||
|
||||
if (mode->vdisplay > 2048)
|
||||
return MODE_VIRTUAL_Y;
|
||||
|
||||
DBG("Processing mode %dx%d@%d with pixel clock %d",
|
||||
mode->hdisplay, mode->vdisplay,
|
||||
drm_mode_vrefresh(mode), mode->clock);
|
||||
|
||||
hbp = mode->htotal - mode->hsync_end;
|
||||
hfp = mode->hsync_start - mode->hdisplay;
|
||||
hsw = mode->hsync_end - mode->hsync_start;
|
||||
vbp = mode->vtotal - mode->vsync_end;
|
||||
vfp = mode->vsync_start - mode->vdisplay;
|
||||
vsw = mode->vsync_end - mode->vsync_start;
|
||||
|
||||
if ((hbp-1) & ~0x3ff) {
|
||||
DBG("Pruning mode: Horizontal Back Porch out of range");
|
||||
return MODE_HBLANK_WIDE;
|
||||
}
|
||||
|
||||
if ((hfp-1) & ~0x3ff) {
|
||||
DBG("Pruning mode: Horizontal Front Porch out of range");
|
||||
return MODE_HBLANK_WIDE;
|
||||
}
|
||||
|
||||
if ((hsw-1) & ~0x3ff) {
|
||||
DBG("Pruning mode: Horizontal Sync Width out of range");
|
||||
return MODE_HSYNC_WIDE;
|
||||
}
|
||||
|
||||
if (vbp & ~0xff) {
|
||||
DBG("Pruning mode: Vertical Back Porch out of range");
|
||||
return MODE_VBLANK_WIDE;
|
||||
}
|
||||
|
||||
if (vfp & ~0xff) {
|
||||
DBG("Pruning mode: Vertical Front Porch out of range");
|
||||
return MODE_VBLANK_WIDE;
|
||||
}
|
||||
|
||||
if ((vsw-1) & ~0x3f) {
|
||||
DBG("Pruning mode: Vertical Sync Width out of range");
|
||||
return MODE_VSYNC_WIDE;
|
||||
}
|
||||
|
||||
/*
|
||||
* some devices have a maximum allowed pixel clock
|
||||
* configured from the DT
|
||||
*/
|
||||
if (mode->clock > priv->max_pixelclock) {
|
||||
DBG("Pruning mode: pixel clock too high");
|
||||
return MODE_CLOCK_HIGH;
|
||||
}
|
||||
|
||||
/*
|
||||
* some devices further limit the max horizontal resolution
|
||||
* configured from the DT
|
||||
*/
|
||||
if (mode->hdisplay > priv->max_width)
|
||||
return MODE_BAD_WIDTH;
|
||||
|
||||
/* filter out modes that would require too much memory bandwidth: */
|
||||
bandwidth = mode->hdisplay * mode->vdisplay *
|
||||
drm_mode_vrefresh(mode);
|
||||
if (bandwidth > priv->max_bandwidth) {
|
||||
DBG("Pruning mode: exceeds defined bandwidth limit");
|
||||
return MODE_BAD;
|
||||
}
|
||||
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc,
|
||||
const struct tilcdc_panel_info *info)
|
||||
{
|
||||
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
|
||||
tilcdc_crtc->info = info;
|
||||
}
|
||||
|
||||
void tilcdc_crtc_update_clk(struct drm_crtc *crtc)
|
||||
{
|
||||
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
int dpms = tilcdc_crtc->dpms;
|
||||
unsigned int lcd_clk, div;
|
||||
int ret;
|
||||
|
||||
pm_runtime_get_sync(dev->dev);
|
||||
|
||||
if (dpms == DRM_MODE_DPMS_ON)
|
||||
tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_OFF);
|
||||
|
||||
/* in raster mode, minimum divisor is 2: */
|
||||
ret = clk_set_rate(priv->disp_clk, crtc->mode.clock * 1000 * 2);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to set display clock rate to: %d\n",
|
||||
crtc->mode.clock);
|
||||
goto out;
|
||||
}
|
||||
|
||||
lcd_clk = clk_get_rate(priv->clk);
|
||||
div = lcd_clk / (crtc->mode.clock * 1000);
|
||||
|
||||
DBG("lcd_clk=%u, mode clock=%d, div=%u", lcd_clk, crtc->mode.clock, div);
|
||||
DBG("fck=%lu, dpll_disp_ck=%lu", clk_get_rate(priv->clk), clk_get_rate(priv->disp_clk));
|
||||
|
||||
/* Configure the LCD clock divisor. */
|
||||
tilcdc_write(dev, LCDC_CTRL_REG, LCDC_CLK_DIVISOR(div) |
|
||||
LCDC_RASTER_MODE);
|
||||
|
||||
if (priv->rev == 2)
|
||||
tilcdc_set(dev, LCDC_CLK_ENABLE_REG,
|
||||
LCDC_V2_DMA_CLK_EN | LCDC_V2_LIDD_CLK_EN |
|
||||
LCDC_V2_CORE_CLK_EN);
|
||||
|
||||
if (dpms == DRM_MODE_DPMS_ON)
|
||||
tilcdc_crtc_dpms(crtc, DRM_MODE_DPMS_ON);
|
||||
|
||||
out:
|
||||
pm_runtime_put_sync(dev->dev);
|
||||
}
|
||||
|
||||
irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc)
|
||||
{
|
||||
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
|
||||
struct drm_device *dev = crtc->dev;
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
uint32_t stat = tilcdc_read_irqstatus(dev);
|
||||
|
||||
if ((stat & LCDC_SYNC_LOST) && (stat & LCDC_FIFO_UNDERFLOW)) {
|
||||
stop(crtc);
|
||||
dev_err(dev->dev, "error: %08x\n", stat);
|
||||
tilcdc_clear_irqstatus(dev, stat);
|
||||
start(crtc);
|
||||
} else if (stat & LCDC_PL_LOAD_DONE) {
|
||||
tilcdc_clear_irqstatus(dev, stat);
|
||||
} else {
|
||||
struct drm_pending_vblank_event *event;
|
||||
unsigned long flags;
|
||||
uint32_t dirty = tilcdc_crtc->dirty & stat;
|
||||
|
||||
tilcdc_clear_irqstatus(dev, stat);
|
||||
|
||||
if (dirty & LCDC_END_OF_FRAME0)
|
||||
set_scanout(crtc, 0);
|
||||
|
||||
if (dirty & LCDC_END_OF_FRAME1)
|
||||
set_scanout(crtc, 1);
|
||||
|
||||
drm_handle_vblank(dev, 0);
|
||||
|
||||
spin_lock_irqsave(&dev->event_lock, flags);
|
||||
event = tilcdc_crtc->event;
|
||||
tilcdc_crtc->event = NULL;
|
||||
if (event)
|
||||
drm_send_vblank_event(dev, 0, event);
|
||||
spin_unlock_irqrestore(&dev->event_lock, flags);
|
||||
|
||||
if (dirty && !tilcdc_crtc->dirty)
|
||||
drm_vblank_put(dev, 0);
|
||||
}
|
||||
|
||||
if (priv->rev == 2) {
|
||||
if (stat & LCDC_FRAME_DONE) {
|
||||
tilcdc_crtc->frame_done = true;
|
||||
wake_up(&tilcdc_crtc->frame_done_wq);
|
||||
}
|
||||
tilcdc_write(dev, LCDC_END_OF_INT_IND_REG, 0);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
void tilcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file)
|
||||
{
|
||||
struct tilcdc_crtc *tilcdc_crtc = to_tilcdc_crtc(crtc);
|
||||
struct drm_pending_vblank_event *event;
|
||||
struct drm_device *dev = crtc->dev;
|
||||
unsigned long flags;
|
||||
|
||||
/* Destroy the pending vertical blanking event associated with the
|
||||
* pending page flip, if any, and disable vertical blanking interrupts.
|
||||
*/
|
||||
spin_lock_irqsave(&dev->event_lock, flags);
|
||||
event = tilcdc_crtc->event;
|
||||
if (event && event->base.file_priv == file) {
|
||||
tilcdc_crtc->event = NULL;
|
||||
event->base.destroy(&event->base);
|
||||
drm_vblank_put(dev, 0);
|
||||
}
|
||||
spin_unlock_irqrestore(&dev->event_lock, flags);
|
||||
}
|
||||
|
||||
struct drm_crtc *tilcdc_crtc_create(struct drm_device *dev)
|
||||
{
|
||||
struct tilcdc_crtc *tilcdc_crtc;
|
||||
struct drm_crtc *crtc;
|
||||
int ret;
|
||||
|
||||
tilcdc_crtc = kzalloc(sizeof(*tilcdc_crtc), GFP_KERNEL);
|
||||
if (!tilcdc_crtc) {
|
||||
dev_err(dev->dev, "allocation failed\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
crtc = &tilcdc_crtc->base;
|
||||
|
||||
tilcdc_crtc->dpms = DRM_MODE_DPMS_OFF;
|
||||
init_waitqueue_head(&tilcdc_crtc->frame_done_wq);
|
||||
|
||||
ret = drm_flip_work_init(&tilcdc_crtc->unref_work, 16,
|
||||
"unref", unref_worker);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "could not allocate unref FIFO\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = drm_crtc_init(dev, crtc, &tilcdc_crtc_funcs);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
drm_crtc_helper_add(crtc, &tilcdc_crtc_helper_funcs);
|
||||
|
||||
return crtc;
|
||||
|
||||
fail:
|
||||
tilcdc_crtc_destroy(crtc);
|
||||
return NULL;
|
||||
}
|
||||
678
drivers/gpu/drm/tilcdc/tilcdc_drv.c
Normal file
678
drivers/gpu/drm/tilcdc/tilcdc_drv.c
Normal file
|
|
@ -0,0 +1,678 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Texas Instruments
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/* LCDC DRM driver, based on da8xx-fb */
|
||||
|
||||
#include "tilcdc_drv.h"
|
||||
#include "tilcdc_regs.h"
|
||||
#include "tilcdc_tfp410.h"
|
||||
#include "tilcdc_slave.h"
|
||||
#include "tilcdc_panel.h"
|
||||
|
||||
#include "drm_fb_helper.h"
|
||||
|
||||
static LIST_HEAD(module_list);
|
||||
static bool slave_probing;
|
||||
|
||||
void tilcdc_module_init(struct tilcdc_module *mod, const char *name,
|
||||
const struct tilcdc_module_ops *funcs)
|
||||
{
|
||||
mod->name = name;
|
||||
mod->funcs = funcs;
|
||||
INIT_LIST_HEAD(&mod->list);
|
||||
list_add(&mod->list, &module_list);
|
||||
}
|
||||
|
||||
void tilcdc_module_cleanup(struct tilcdc_module *mod)
|
||||
{
|
||||
list_del(&mod->list);
|
||||
}
|
||||
|
||||
void tilcdc_slave_probedefer(bool defered)
|
||||
{
|
||||
slave_probing = defered;
|
||||
}
|
||||
|
||||
static struct of_device_id tilcdc_of_match[];
|
||||
|
||||
static struct drm_framebuffer *tilcdc_fb_create(struct drm_device *dev,
|
||||
struct drm_file *file_priv, struct drm_mode_fb_cmd2 *mode_cmd)
|
||||
{
|
||||
return drm_fb_cma_create(dev, file_priv, mode_cmd);
|
||||
}
|
||||
|
||||
static void tilcdc_fb_output_poll_changed(struct drm_device *dev)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
if (priv->fbdev)
|
||||
drm_fbdev_cma_hotplug_event(priv->fbdev);
|
||||
}
|
||||
|
||||
static const struct drm_mode_config_funcs mode_config_funcs = {
|
||||
.fb_create = tilcdc_fb_create,
|
||||
.output_poll_changed = tilcdc_fb_output_poll_changed,
|
||||
};
|
||||
|
||||
static int modeset_init(struct drm_device *dev)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
struct tilcdc_module *mod;
|
||||
|
||||
drm_mode_config_init(dev);
|
||||
|
||||
priv->crtc = tilcdc_crtc_create(dev);
|
||||
|
||||
list_for_each_entry(mod, &module_list, list) {
|
||||
DBG("loading module: %s", mod->name);
|
||||
mod->funcs->modeset_init(mod, dev);
|
||||
}
|
||||
|
||||
if ((priv->num_encoders == 0) || (priv->num_connectors == 0)) {
|
||||
/* oh nos! */
|
||||
dev_err(dev->dev, "no encoders/connectors found\n");
|
||||
drm_mode_config_cleanup(dev);
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
dev->mode_config.min_width = 0;
|
||||
dev->mode_config.min_height = 0;
|
||||
dev->mode_config.max_width = tilcdc_crtc_max_width(priv->crtc);
|
||||
dev->mode_config.max_height = 2048;
|
||||
dev->mode_config.funcs = &mode_config_funcs;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_CPU_FREQ
|
||||
static int cpufreq_transition(struct notifier_block *nb,
|
||||
unsigned long val, void *data)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = container_of(nb,
|
||||
struct tilcdc_drm_private, freq_transition);
|
||||
if (val == CPUFREQ_POSTCHANGE) {
|
||||
if (priv->lcd_fck_rate != clk_get_rate(priv->clk)) {
|
||||
priv->lcd_fck_rate = clk_get_rate(priv->clk);
|
||||
tilcdc_crtc_update_clk(priv->crtc);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* DRM operations:
|
||||
*/
|
||||
|
||||
static int tilcdc_unload(struct drm_device *dev)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
|
||||
drm_fbdev_cma_fini(priv->fbdev);
|
||||
drm_kms_helper_poll_fini(dev);
|
||||
drm_mode_config_cleanup(dev);
|
||||
drm_vblank_cleanup(dev);
|
||||
|
||||
pm_runtime_get_sync(dev->dev);
|
||||
drm_irq_uninstall(dev);
|
||||
pm_runtime_put_sync(dev->dev);
|
||||
|
||||
#ifdef CONFIG_CPU_FREQ
|
||||
cpufreq_unregister_notifier(&priv->freq_transition,
|
||||
CPUFREQ_TRANSITION_NOTIFIER);
|
||||
#endif
|
||||
|
||||
if (priv->clk)
|
||||
clk_put(priv->clk);
|
||||
|
||||
if (priv->mmio)
|
||||
iounmap(priv->mmio);
|
||||
|
||||
flush_workqueue(priv->wq);
|
||||
destroy_workqueue(priv->wq);
|
||||
|
||||
dev->dev_private = NULL;
|
||||
|
||||
pm_runtime_disable(dev->dev);
|
||||
|
||||
kfree(priv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tilcdc_load(struct drm_device *dev, unsigned long flags)
|
||||
{
|
||||
struct platform_device *pdev = dev->platformdev;
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct tilcdc_drm_private *priv;
|
||||
struct tilcdc_module *mod;
|
||||
struct resource *res;
|
||||
u32 bpp = 0;
|
||||
int ret;
|
||||
|
||||
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv) {
|
||||
dev_err(dev->dev, "failed to allocate private data\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
dev->dev_private = priv;
|
||||
|
||||
priv->wq = alloc_ordered_workqueue("tilcdc", 0);
|
||||
if (!priv->wq) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_free_priv;
|
||||
}
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res) {
|
||||
dev_err(dev->dev, "failed to get memory resource\n");
|
||||
ret = -EINVAL;
|
||||
goto fail_free_wq;
|
||||
}
|
||||
|
||||
priv->mmio = ioremap_nocache(res->start, resource_size(res));
|
||||
if (!priv->mmio) {
|
||||
dev_err(dev->dev, "failed to ioremap\n");
|
||||
ret = -ENOMEM;
|
||||
goto fail_free_wq;
|
||||
}
|
||||
|
||||
priv->clk = clk_get(dev->dev, "fck");
|
||||
if (IS_ERR(priv->clk)) {
|
||||
dev_err(dev->dev, "failed to get functional clock\n");
|
||||
ret = -ENODEV;
|
||||
goto fail_iounmap;
|
||||
}
|
||||
|
||||
priv->disp_clk = clk_get(dev->dev, "dpll_disp_ck");
|
||||
if (IS_ERR(priv->clk)) {
|
||||
dev_err(dev->dev, "failed to get display clock\n");
|
||||
ret = -ENODEV;
|
||||
goto fail_put_clk;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_CPU_FREQ
|
||||
priv->lcd_fck_rate = clk_get_rate(priv->clk);
|
||||
priv->freq_transition.notifier_call = cpufreq_transition;
|
||||
ret = cpufreq_register_notifier(&priv->freq_transition,
|
||||
CPUFREQ_TRANSITION_NOTIFIER);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to register cpufreq notifier\n");
|
||||
goto fail_put_disp_clk;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (of_property_read_u32(node, "max-bandwidth", &priv->max_bandwidth))
|
||||
priv->max_bandwidth = TILCDC_DEFAULT_MAX_BANDWIDTH;
|
||||
|
||||
DBG("Maximum Bandwidth Value %d", priv->max_bandwidth);
|
||||
|
||||
if (of_property_read_u32(node, "ti,max-width", &priv->max_width))
|
||||
priv->max_width = TILCDC_DEFAULT_MAX_WIDTH;
|
||||
|
||||
DBG("Maximum Horizontal Pixel Width Value %dpixels", priv->max_width);
|
||||
|
||||
if (of_property_read_u32(node, "ti,max-pixelclock",
|
||||
&priv->max_pixelclock))
|
||||
priv->max_pixelclock = TILCDC_DEFAULT_MAX_PIXELCLOCK;
|
||||
|
||||
DBG("Maximum Pixel Clock Value %dKHz", priv->max_pixelclock);
|
||||
|
||||
pm_runtime_enable(dev->dev);
|
||||
|
||||
/* Determine LCD IP Version */
|
||||
pm_runtime_get_sync(dev->dev);
|
||||
switch (tilcdc_read(dev, LCDC_PID_REG)) {
|
||||
case 0x4c100102:
|
||||
priv->rev = 1;
|
||||
break;
|
||||
case 0x4f200800:
|
||||
case 0x4f201000:
|
||||
priv->rev = 2;
|
||||
break;
|
||||
default:
|
||||
dev_warn(dev->dev, "Unknown PID Reg value 0x%08x, "
|
||||
"defaulting to LCD revision 1\n",
|
||||
tilcdc_read(dev, LCDC_PID_REG));
|
||||
priv->rev = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
pm_runtime_put_sync(dev->dev);
|
||||
|
||||
ret = modeset_init(dev);
|
||||
if (ret < 0) {
|
||||
dev_err(dev->dev, "failed to initialize mode setting\n");
|
||||
goto fail_cpufreq_unregister;
|
||||
}
|
||||
|
||||
ret = drm_vblank_init(dev, 1);
|
||||
if (ret < 0) {
|
||||
dev_err(dev->dev, "failed to initialize vblank\n");
|
||||
goto fail_mode_config_cleanup;
|
||||
}
|
||||
|
||||
pm_runtime_get_sync(dev->dev);
|
||||
ret = drm_irq_install(dev, platform_get_irq(dev->platformdev, 0));
|
||||
pm_runtime_put_sync(dev->dev);
|
||||
if (ret < 0) {
|
||||
dev_err(dev->dev, "failed to install IRQ handler\n");
|
||||
goto fail_vblank_cleanup;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, dev);
|
||||
|
||||
|
||||
list_for_each_entry(mod, &module_list, list) {
|
||||
DBG("%s: preferred_bpp: %d", mod->name, mod->preferred_bpp);
|
||||
bpp = mod->preferred_bpp;
|
||||
if (bpp > 0)
|
||||
break;
|
||||
}
|
||||
|
||||
priv->fbdev = drm_fbdev_cma_init(dev, bpp,
|
||||
dev->mode_config.num_crtc,
|
||||
dev->mode_config.num_connector);
|
||||
if (IS_ERR(priv->fbdev)) {
|
||||
ret = PTR_ERR(priv->fbdev);
|
||||
goto fail_irq_uninstall;
|
||||
}
|
||||
|
||||
drm_kms_helper_poll_init(dev);
|
||||
|
||||
return 0;
|
||||
|
||||
fail_irq_uninstall:
|
||||
pm_runtime_get_sync(dev->dev);
|
||||
drm_irq_uninstall(dev);
|
||||
pm_runtime_put_sync(dev->dev);
|
||||
|
||||
fail_vblank_cleanup:
|
||||
drm_vblank_cleanup(dev);
|
||||
|
||||
fail_mode_config_cleanup:
|
||||
drm_mode_config_cleanup(dev);
|
||||
|
||||
fail_cpufreq_unregister:
|
||||
pm_runtime_disable(dev->dev);
|
||||
#ifdef CONFIG_CPU_FREQ
|
||||
cpufreq_unregister_notifier(&priv->freq_transition,
|
||||
CPUFREQ_TRANSITION_NOTIFIER);
|
||||
fail_put_disp_clk:
|
||||
clk_put(priv->disp_clk);
|
||||
#endif
|
||||
|
||||
fail_put_clk:
|
||||
clk_put(priv->clk);
|
||||
|
||||
fail_iounmap:
|
||||
iounmap(priv->mmio);
|
||||
|
||||
fail_free_wq:
|
||||
flush_workqueue(priv->wq);
|
||||
destroy_workqueue(priv->wq);
|
||||
|
||||
fail_free_priv:
|
||||
dev->dev_private = NULL;
|
||||
kfree(priv);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void tilcdc_preclose(struct drm_device *dev, struct drm_file *file)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
|
||||
tilcdc_crtc_cancel_page_flip(priv->crtc, file);
|
||||
}
|
||||
|
||||
static void tilcdc_lastclose(struct drm_device *dev)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
drm_fbdev_cma_restore_mode(priv->fbdev);
|
||||
}
|
||||
|
||||
static irqreturn_t tilcdc_irq(int irq, void *arg)
|
||||
{
|
||||
struct drm_device *dev = arg;
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
return tilcdc_crtc_irq(priv->crtc);
|
||||
}
|
||||
|
||||
static void tilcdc_irq_preinstall(struct drm_device *dev)
|
||||
{
|
||||
tilcdc_clear_irqstatus(dev, 0xffffffff);
|
||||
}
|
||||
|
||||
static int tilcdc_irq_postinstall(struct drm_device *dev)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
|
||||
/* enable FIFO underflow irq: */
|
||||
if (priv->rev == 1)
|
||||
tilcdc_set(dev, LCDC_RASTER_CTRL_REG, LCDC_V1_UNDERFLOW_INT_ENA);
|
||||
else
|
||||
tilcdc_set(dev, LCDC_INT_ENABLE_SET_REG, LCDC_V2_UNDERFLOW_INT_ENA);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tilcdc_irq_uninstall(struct drm_device *dev)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
|
||||
/* disable irqs that we might have enabled: */
|
||||
if (priv->rev == 1) {
|
||||
tilcdc_clear(dev, LCDC_RASTER_CTRL_REG,
|
||||
LCDC_V1_UNDERFLOW_INT_ENA | LCDC_V1_PL_INT_ENA);
|
||||
tilcdc_clear(dev, LCDC_DMA_CTRL_REG, LCDC_V1_END_OF_FRAME_INT_ENA);
|
||||
} else {
|
||||
tilcdc_clear(dev, LCDC_INT_ENABLE_SET_REG,
|
||||
LCDC_V2_UNDERFLOW_INT_ENA | LCDC_V2_PL_INT_ENA |
|
||||
LCDC_V2_END_OF_FRAME0_INT_ENA | LCDC_V2_END_OF_FRAME1_INT_ENA |
|
||||
LCDC_FRAME_DONE);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void enable_vblank(struct drm_device *dev, bool enable)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
u32 reg, mask;
|
||||
|
||||
if (priv->rev == 1) {
|
||||
reg = LCDC_DMA_CTRL_REG;
|
||||
mask = LCDC_V1_END_OF_FRAME_INT_ENA;
|
||||
} else {
|
||||
reg = LCDC_INT_ENABLE_SET_REG;
|
||||
mask = LCDC_V2_END_OF_FRAME0_INT_ENA |
|
||||
LCDC_V2_END_OF_FRAME1_INT_ENA | LCDC_FRAME_DONE;
|
||||
}
|
||||
|
||||
if (enable)
|
||||
tilcdc_set(dev, reg, mask);
|
||||
else
|
||||
tilcdc_clear(dev, reg, mask);
|
||||
}
|
||||
|
||||
static int tilcdc_enable_vblank(struct drm_device *dev, int crtc)
|
||||
{
|
||||
enable_vblank(dev, true);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void tilcdc_disable_vblank(struct drm_device *dev, int crtc)
|
||||
{
|
||||
enable_vblank(dev, false);
|
||||
}
|
||||
|
||||
#if defined(CONFIG_DEBUG_FS) || defined(CONFIG_PM_SLEEP)
|
||||
static const struct {
|
||||
const char *name;
|
||||
uint8_t rev;
|
||||
uint8_t save;
|
||||
uint32_t reg;
|
||||
} registers[] = {
|
||||
#define REG(rev, save, reg) { #reg, rev, save, reg }
|
||||
/* exists in revision 1: */
|
||||
REG(1, false, LCDC_PID_REG),
|
||||
REG(1, true, LCDC_CTRL_REG),
|
||||
REG(1, false, LCDC_STAT_REG),
|
||||
REG(1, true, LCDC_RASTER_CTRL_REG),
|
||||
REG(1, true, LCDC_RASTER_TIMING_0_REG),
|
||||
REG(1, true, LCDC_RASTER_TIMING_1_REG),
|
||||
REG(1, true, LCDC_RASTER_TIMING_2_REG),
|
||||
REG(1, true, LCDC_DMA_CTRL_REG),
|
||||
REG(1, true, LCDC_DMA_FB_BASE_ADDR_0_REG),
|
||||
REG(1, true, LCDC_DMA_FB_CEILING_ADDR_0_REG),
|
||||
REG(1, true, LCDC_DMA_FB_BASE_ADDR_1_REG),
|
||||
REG(1, true, LCDC_DMA_FB_CEILING_ADDR_1_REG),
|
||||
/* new in revision 2: */
|
||||
REG(2, false, LCDC_RAW_STAT_REG),
|
||||
REG(2, false, LCDC_MASKED_STAT_REG),
|
||||
REG(2, false, LCDC_INT_ENABLE_SET_REG),
|
||||
REG(2, false, LCDC_INT_ENABLE_CLR_REG),
|
||||
REG(2, false, LCDC_END_OF_INT_IND_REG),
|
||||
REG(2, true, LCDC_CLK_ENABLE_REG),
|
||||
REG(2, true, LCDC_INT_ENABLE_SET_REG),
|
||||
#undef REG
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static int tilcdc_regs_show(struct seq_file *m, void *arg)
|
||||
{
|
||||
struct drm_info_node *node = (struct drm_info_node *) m->private;
|
||||
struct drm_device *dev = node->minor->dev;
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
unsigned i;
|
||||
|
||||
pm_runtime_get_sync(dev->dev);
|
||||
|
||||
seq_printf(m, "revision: %d\n", priv->rev);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(registers); i++)
|
||||
if (priv->rev >= registers[i].rev)
|
||||
seq_printf(m, "%s:\t %08x\n", registers[i].name,
|
||||
tilcdc_read(dev, registers[i].reg));
|
||||
|
||||
pm_runtime_put_sync(dev->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tilcdc_mm_show(struct seq_file *m, void *arg)
|
||||
{
|
||||
struct drm_info_node *node = (struct drm_info_node *) m->private;
|
||||
struct drm_device *dev = node->minor->dev;
|
||||
return drm_mm_dump_table(m, &dev->vma_offset_manager->vm_addr_space_mm);
|
||||
}
|
||||
|
||||
static struct drm_info_list tilcdc_debugfs_list[] = {
|
||||
{ "regs", tilcdc_regs_show, 0 },
|
||||
{ "mm", tilcdc_mm_show, 0 },
|
||||
{ "fb", drm_fb_cma_debugfs_show, 0 },
|
||||
};
|
||||
|
||||
static int tilcdc_debugfs_init(struct drm_minor *minor)
|
||||
{
|
||||
struct drm_device *dev = minor->dev;
|
||||
struct tilcdc_module *mod;
|
||||
int ret;
|
||||
|
||||
ret = drm_debugfs_create_files(tilcdc_debugfs_list,
|
||||
ARRAY_SIZE(tilcdc_debugfs_list),
|
||||
minor->debugfs_root, minor);
|
||||
|
||||
list_for_each_entry(mod, &module_list, list)
|
||||
if (mod->funcs->debugfs_init)
|
||||
mod->funcs->debugfs_init(mod, minor);
|
||||
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "could not install tilcdc_debugfs_list\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void tilcdc_debugfs_cleanup(struct drm_minor *minor)
|
||||
{
|
||||
struct tilcdc_module *mod;
|
||||
drm_debugfs_remove_files(tilcdc_debugfs_list,
|
||||
ARRAY_SIZE(tilcdc_debugfs_list), minor);
|
||||
|
||||
list_for_each_entry(mod, &module_list, list)
|
||||
if (mod->funcs->debugfs_cleanup)
|
||||
mod->funcs->debugfs_cleanup(mod, minor);
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct file_operations fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.open = drm_open,
|
||||
.release = drm_release,
|
||||
.unlocked_ioctl = drm_ioctl,
|
||||
#ifdef CONFIG_COMPAT
|
||||
.compat_ioctl = drm_compat_ioctl,
|
||||
#endif
|
||||
.poll = drm_poll,
|
||||
.read = drm_read,
|
||||
.llseek = no_llseek,
|
||||
.mmap = drm_gem_cma_mmap,
|
||||
};
|
||||
|
||||
static struct drm_driver tilcdc_driver = {
|
||||
.driver_features = DRIVER_HAVE_IRQ | DRIVER_GEM | DRIVER_MODESET,
|
||||
.load = tilcdc_load,
|
||||
.unload = tilcdc_unload,
|
||||
.preclose = tilcdc_preclose,
|
||||
.lastclose = tilcdc_lastclose,
|
||||
.set_busid = drm_platform_set_busid,
|
||||
.irq_handler = tilcdc_irq,
|
||||
.irq_preinstall = tilcdc_irq_preinstall,
|
||||
.irq_postinstall = tilcdc_irq_postinstall,
|
||||
.irq_uninstall = tilcdc_irq_uninstall,
|
||||
.get_vblank_counter = drm_vblank_count,
|
||||
.enable_vblank = tilcdc_enable_vblank,
|
||||
.disable_vblank = tilcdc_disable_vblank,
|
||||
.gem_free_object = drm_gem_cma_free_object,
|
||||
.gem_vm_ops = &drm_gem_cma_vm_ops,
|
||||
.dumb_create = drm_gem_cma_dumb_create,
|
||||
.dumb_map_offset = drm_gem_cma_dumb_map_offset,
|
||||
.dumb_destroy = drm_gem_dumb_destroy,
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
.debugfs_init = tilcdc_debugfs_init,
|
||||
.debugfs_cleanup = tilcdc_debugfs_cleanup,
|
||||
#endif
|
||||
.fops = &fops,
|
||||
.name = "tilcdc",
|
||||
.desc = "TI LCD Controller DRM",
|
||||
.date = "20121205",
|
||||
.major = 1,
|
||||
.minor = 0,
|
||||
};
|
||||
|
||||
/*
|
||||
* Power management:
|
||||
*/
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int tilcdc_pm_suspend(struct device *dev)
|
||||
{
|
||||
struct drm_device *ddev = dev_get_drvdata(dev);
|
||||
struct tilcdc_drm_private *priv = ddev->dev_private;
|
||||
unsigned i, n = 0;
|
||||
|
||||
drm_kms_helper_poll_disable(ddev);
|
||||
|
||||
/* Save register state: */
|
||||
for (i = 0; i < ARRAY_SIZE(registers); i++)
|
||||
if (registers[i].save && (priv->rev >= registers[i].rev))
|
||||
priv->saved_register[n++] = tilcdc_read(ddev, registers[i].reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tilcdc_pm_resume(struct device *dev)
|
||||
{
|
||||
struct drm_device *ddev = dev_get_drvdata(dev);
|
||||
struct tilcdc_drm_private *priv = ddev->dev_private;
|
||||
unsigned i, n = 0;
|
||||
|
||||
/* Restore register state: */
|
||||
for (i = 0; i < ARRAY_SIZE(registers); i++)
|
||||
if (registers[i].save && (priv->rev >= registers[i].rev))
|
||||
tilcdc_write(ddev, registers[i].reg, priv->saved_register[n++]);
|
||||
|
||||
drm_kms_helper_poll_enable(ddev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct dev_pm_ops tilcdc_pm_ops = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(tilcdc_pm_suspend, tilcdc_pm_resume)
|
||||
};
|
||||
|
||||
/*
|
||||
* Platform driver:
|
||||
*/
|
||||
|
||||
static int tilcdc_pdev_probe(struct platform_device *pdev)
|
||||
{
|
||||
/* bail out early if no DT data: */
|
||||
if (!pdev->dev.of_node) {
|
||||
dev_err(&pdev->dev, "device-tree data is missing\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
/* defer probing if slave is in deferred probing */
|
||||
if (slave_probing == true)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
return drm_platform_init(&tilcdc_driver, pdev);
|
||||
}
|
||||
|
||||
static int tilcdc_pdev_remove(struct platform_device *pdev)
|
||||
{
|
||||
drm_put_dev(platform_get_drvdata(pdev));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct of_device_id tilcdc_of_match[] = {
|
||||
{ .compatible = "ti,am33xx-tilcdc", },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, tilcdc_of_match);
|
||||
|
||||
static struct platform_driver tilcdc_platform_driver = {
|
||||
.probe = tilcdc_pdev_probe,
|
||||
.remove = tilcdc_pdev_remove,
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "tilcdc",
|
||||
.pm = &tilcdc_pm_ops,
|
||||
.of_match_table = tilcdc_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init tilcdc_drm_init(void)
|
||||
{
|
||||
DBG("init");
|
||||
tilcdc_tfp410_init();
|
||||
tilcdc_slave_init();
|
||||
tilcdc_panel_init();
|
||||
return platform_driver_register(&tilcdc_platform_driver);
|
||||
}
|
||||
|
||||
static void __exit tilcdc_drm_fini(void)
|
||||
{
|
||||
DBG("fini");
|
||||
platform_driver_unregister(&tilcdc_platform_driver);
|
||||
tilcdc_panel_fini();
|
||||
tilcdc_slave_fini();
|
||||
tilcdc_tfp410_fini();
|
||||
}
|
||||
|
||||
module_init(tilcdc_drm_init);
|
||||
module_exit(tilcdc_drm_fini);
|
||||
|
||||
MODULE_AUTHOR("Rob Clark <robdclark@gmail.com");
|
||||
MODULE_DESCRIPTION("TI LCD Controller DRM Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
172
drivers/gpu/drm/tilcdc/tilcdc_drv.h
Normal file
172
drivers/gpu/drm/tilcdc/tilcdc_drv.h
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Texas Instruments
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef __TILCDC_DRV_H__
|
||||
#define __TILCDC_DRV_H__
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/cpufreq.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_gem_cma_helper.h>
|
||||
#include <drm/drm_fb_cma_helper.h>
|
||||
|
||||
/* Defaulting to pixel clock defined on AM335x */
|
||||
#define TILCDC_DEFAULT_MAX_PIXELCLOCK 126000
|
||||
/* Defaulting to max width as defined on AM335x */
|
||||
#define TILCDC_DEFAULT_MAX_WIDTH 2048
|
||||
/*
|
||||
* This may need some tweaking, but want to allow at least 1280x1024@60
|
||||
* with optimized DDR & EMIF settings tweaked 1920x1080@24 appears to
|
||||
* be supportable
|
||||
*/
|
||||
#define TILCDC_DEFAULT_MAX_BANDWIDTH (1280*1024*60)
|
||||
|
||||
|
||||
struct tilcdc_drm_private {
|
||||
void __iomem *mmio;
|
||||
|
||||
struct clk *disp_clk; /* display dpll */
|
||||
struct clk *clk; /* functional clock */
|
||||
int rev; /* IP revision */
|
||||
|
||||
/* don't attempt resolutions w/ higher W * H * Hz: */
|
||||
uint32_t max_bandwidth;
|
||||
/*
|
||||
* Pixel Clock will be restricted to some value as
|
||||
* defined in the device datasheet measured in KHz
|
||||
*/
|
||||
uint32_t max_pixelclock;
|
||||
/*
|
||||
* Max allowable width is limited on a per device basis
|
||||
* measured in pixels
|
||||
*/
|
||||
uint32_t max_width;
|
||||
|
||||
/* register contents saved across suspend/resume: */
|
||||
u32 saved_register[12];
|
||||
|
||||
#ifdef CONFIG_CPU_FREQ
|
||||
struct notifier_block freq_transition;
|
||||
unsigned int lcd_fck_rate;
|
||||
#endif
|
||||
|
||||
struct workqueue_struct *wq;
|
||||
|
||||
struct drm_fbdev_cma *fbdev;
|
||||
|
||||
struct drm_crtc *crtc;
|
||||
|
||||
unsigned int num_encoders;
|
||||
struct drm_encoder *encoders[8];
|
||||
|
||||
unsigned int num_connectors;
|
||||
struct drm_connector *connectors[8];
|
||||
};
|
||||
|
||||
/* Sub-module for display. Since we don't know at compile time what panels
|
||||
* or display adapter(s) might be present (for ex, off chip dvi/tfp410,
|
||||
* hdmi encoder, various lcd panels), the connector/encoder(s) are split into
|
||||
* separate drivers. If they are probed and found to be present, they
|
||||
* register themselves with tilcdc_register_module().
|
||||
*/
|
||||
struct tilcdc_module;
|
||||
|
||||
struct tilcdc_module_ops {
|
||||
/* create appropriate encoders/connectors: */
|
||||
int (*modeset_init)(struct tilcdc_module *mod, struct drm_device *dev);
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
/* create debugfs nodes (can be NULL): */
|
||||
int (*debugfs_init)(struct tilcdc_module *mod, struct drm_minor *minor);
|
||||
/* cleanup debugfs nodes (can be NULL): */
|
||||
void (*debugfs_cleanup)(struct tilcdc_module *mod, struct drm_minor *minor);
|
||||
#endif
|
||||
};
|
||||
|
||||
struct tilcdc_module {
|
||||
const char *name;
|
||||
struct list_head list;
|
||||
const struct tilcdc_module_ops *funcs;
|
||||
unsigned int preferred_bpp;
|
||||
};
|
||||
|
||||
void tilcdc_module_init(struct tilcdc_module *mod, const char *name,
|
||||
const struct tilcdc_module_ops *funcs);
|
||||
void tilcdc_module_cleanup(struct tilcdc_module *mod);
|
||||
void tilcdc_slave_probedefer(bool defered);
|
||||
|
||||
/* Panel config that needs to be set in the crtc, but is not coming from
|
||||
* the mode timings. The display module is expected to call
|
||||
* tilcdc_crtc_set_panel_info() to set this during modeset.
|
||||
*/
|
||||
struct tilcdc_panel_info {
|
||||
|
||||
/* AC Bias Pin Frequency */
|
||||
uint32_t ac_bias;
|
||||
|
||||
/* AC Bias Pin Transitions per Interrupt */
|
||||
uint32_t ac_bias_intrpt;
|
||||
|
||||
/* DMA burst size */
|
||||
uint32_t dma_burst_sz;
|
||||
|
||||
/* Bits per pixel */
|
||||
uint32_t bpp;
|
||||
|
||||
/* FIFO DMA Request Delay */
|
||||
uint32_t fdd;
|
||||
|
||||
/* TFT Alternative Signal Mapping (Only for active) */
|
||||
bool tft_alt_mode;
|
||||
|
||||
/* Invert pixel clock */
|
||||
bool invert_pxl_clk;
|
||||
|
||||
/* Horizontal and Vertical Sync Edge: 0=rising 1=falling */
|
||||
uint32_t sync_edge;
|
||||
|
||||
/* Horizontal and Vertical Sync: Control: 0=ignore */
|
||||
uint32_t sync_ctrl;
|
||||
|
||||
/* Raster Data Order Select: 1=Most-to-least 0=Least-to-most */
|
||||
uint32_t raster_order;
|
||||
|
||||
/* DMA FIFO threshold */
|
||||
uint32_t fifo_th;
|
||||
};
|
||||
|
||||
#define DBG(fmt, ...) DRM_DEBUG(fmt"\n", ##__VA_ARGS__)
|
||||
|
||||
struct drm_crtc *tilcdc_crtc_create(struct drm_device *dev);
|
||||
void tilcdc_crtc_cancel_page_flip(struct drm_crtc *crtc, struct drm_file *file);
|
||||
irqreturn_t tilcdc_crtc_irq(struct drm_crtc *crtc);
|
||||
void tilcdc_crtc_update_clk(struct drm_crtc *crtc);
|
||||
void tilcdc_crtc_set_panel_info(struct drm_crtc *crtc,
|
||||
const struct tilcdc_panel_info *info);
|
||||
int tilcdc_crtc_mode_valid(struct drm_crtc *crtc, struct drm_display_mode *mode);
|
||||
int tilcdc_crtc_max_width(struct drm_crtc *crtc);
|
||||
|
||||
#endif /* __TILCDC_DRV_H__ */
|
||||
476
drivers/gpu/drm/tilcdc/tilcdc_panel.c
Normal file
476
drivers/gpu/drm/tilcdc/tilcdc_panel.c
Normal file
|
|
@ -0,0 +1,476 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Texas Instruments
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include <linux/pinctrl/pinmux.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/backlight.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <video/display_timing.h>
|
||||
#include <video/of_display_timing.h>
|
||||
#include <video/videomode.h>
|
||||
|
||||
#include "tilcdc_drv.h"
|
||||
|
||||
struct panel_module {
|
||||
struct tilcdc_module base;
|
||||
struct tilcdc_panel_info *info;
|
||||
struct display_timings *timings;
|
||||
struct backlight_device *backlight;
|
||||
struct gpio_desc *enable_gpio;
|
||||
};
|
||||
#define to_panel_module(x) container_of(x, struct panel_module, base)
|
||||
|
||||
|
||||
/*
|
||||
* Encoder:
|
||||
*/
|
||||
|
||||
struct panel_encoder {
|
||||
struct drm_encoder base;
|
||||
struct panel_module *mod;
|
||||
};
|
||||
#define to_panel_encoder(x) container_of(x, struct panel_encoder, base)
|
||||
|
||||
|
||||
static void panel_encoder_destroy(struct drm_encoder *encoder)
|
||||
{
|
||||
struct panel_encoder *panel_encoder = to_panel_encoder(encoder);
|
||||
drm_encoder_cleanup(encoder);
|
||||
kfree(panel_encoder);
|
||||
}
|
||||
|
||||
static void panel_encoder_dpms(struct drm_encoder *encoder, int mode)
|
||||
{
|
||||
struct panel_encoder *panel_encoder = to_panel_encoder(encoder);
|
||||
struct backlight_device *backlight = panel_encoder->mod->backlight;
|
||||
struct gpio_desc *gpio = panel_encoder->mod->enable_gpio;
|
||||
|
||||
if (backlight) {
|
||||
backlight->props.power = mode == DRM_MODE_DPMS_ON ?
|
||||
FB_BLANK_UNBLANK : FB_BLANK_POWERDOWN;
|
||||
backlight_update_status(backlight);
|
||||
}
|
||||
|
||||
if (gpio)
|
||||
gpiod_set_value_cansleep(gpio,
|
||||
mode == DRM_MODE_DPMS_ON ? 1 : 0);
|
||||
}
|
||||
|
||||
static bool panel_encoder_mode_fixup(struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
/* nothing needed */
|
||||
return true;
|
||||
}
|
||||
|
||||
static void panel_encoder_prepare(struct drm_encoder *encoder)
|
||||
{
|
||||
struct panel_encoder *panel_encoder = to_panel_encoder(encoder);
|
||||
panel_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
|
||||
tilcdc_crtc_set_panel_info(encoder->crtc, panel_encoder->mod->info);
|
||||
}
|
||||
|
||||
static void panel_encoder_commit(struct drm_encoder *encoder)
|
||||
{
|
||||
panel_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
|
||||
}
|
||||
|
||||
static void panel_encoder_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
/* nothing needed */
|
||||
}
|
||||
|
||||
static const struct drm_encoder_funcs panel_encoder_funcs = {
|
||||
.destroy = panel_encoder_destroy,
|
||||
};
|
||||
|
||||
static const struct drm_encoder_helper_funcs panel_encoder_helper_funcs = {
|
||||
.dpms = panel_encoder_dpms,
|
||||
.mode_fixup = panel_encoder_mode_fixup,
|
||||
.prepare = panel_encoder_prepare,
|
||||
.commit = panel_encoder_commit,
|
||||
.mode_set = panel_encoder_mode_set,
|
||||
};
|
||||
|
||||
static struct drm_encoder *panel_encoder_create(struct drm_device *dev,
|
||||
struct panel_module *mod)
|
||||
{
|
||||
struct panel_encoder *panel_encoder;
|
||||
struct drm_encoder *encoder;
|
||||
int ret;
|
||||
|
||||
panel_encoder = kzalloc(sizeof(*panel_encoder), GFP_KERNEL);
|
||||
if (!panel_encoder) {
|
||||
dev_err(dev->dev, "allocation failed\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
panel_encoder->mod = mod;
|
||||
|
||||
encoder = &panel_encoder->base;
|
||||
encoder->possible_crtcs = 1;
|
||||
|
||||
ret = drm_encoder_init(dev, encoder, &panel_encoder_funcs,
|
||||
DRM_MODE_ENCODER_LVDS);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
drm_encoder_helper_add(encoder, &panel_encoder_helper_funcs);
|
||||
|
||||
return encoder;
|
||||
|
||||
fail:
|
||||
panel_encoder_destroy(encoder);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Connector:
|
||||
*/
|
||||
|
||||
struct panel_connector {
|
||||
struct drm_connector base;
|
||||
|
||||
struct drm_encoder *encoder; /* our connected encoder */
|
||||
struct panel_module *mod;
|
||||
};
|
||||
#define to_panel_connector(x) container_of(x, struct panel_connector, base)
|
||||
|
||||
|
||||
static void panel_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
struct panel_connector *panel_connector = to_panel_connector(connector);
|
||||
drm_connector_unregister(connector);
|
||||
drm_connector_cleanup(connector);
|
||||
kfree(panel_connector);
|
||||
}
|
||||
|
||||
static enum drm_connector_status panel_connector_detect(
|
||||
struct drm_connector *connector,
|
||||
bool force)
|
||||
{
|
||||
return connector_status_connected;
|
||||
}
|
||||
|
||||
static int panel_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct drm_device *dev = connector->dev;
|
||||
struct panel_connector *panel_connector = to_panel_connector(connector);
|
||||
struct display_timings *timings = panel_connector->mod->timings;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < timings->num_timings; i++) {
|
||||
struct drm_display_mode *mode = drm_mode_create(dev);
|
||||
struct videomode vm;
|
||||
|
||||
if (videomode_from_timings(timings, &vm, i))
|
||||
break;
|
||||
|
||||
drm_display_mode_from_videomode(&vm, mode);
|
||||
|
||||
mode->type = DRM_MODE_TYPE_DRIVER;
|
||||
|
||||
if (timings->native_mode == i)
|
||||
mode->type |= DRM_MODE_TYPE_PREFERRED;
|
||||
|
||||
drm_mode_set_name(mode);
|
||||
drm_mode_probed_add(connector, mode);
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static int panel_connector_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = connector->dev->dev_private;
|
||||
/* our only constraints are what the crtc can generate: */
|
||||
return tilcdc_crtc_mode_valid(priv->crtc, mode);
|
||||
}
|
||||
|
||||
static struct drm_encoder *panel_connector_best_encoder(
|
||||
struct drm_connector *connector)
|
||||
{
|
||||
struct panel_connector *panel_connector = to_panel_connector(connector);
|
||||
return panel_connector->encoder;
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs panel_connector_funcs = {
|
||||
.destroy = panel_connector_destroy,
|
||||
.dpms = drm_helper_connector_dpms,
|
||||
.detect = panel_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
};
|
||||
|
||||
static const struct drm_connector_helper_funcs panel_connector_helper_funcs = {
|
||||
.get_modes = panel_connector_get_modes,
|
||||
.mode_valid = panel_connector_mode_valid,
|
||||
.best_encoder = panel_connector_best_encoder,
|
||||
};
|
||||
|
||||
static struct drm_connector *panel_connector_create(struct drm_device *dev,
|
||||
struct panel_module *mod, struct drm_encoder *encoder)
|
||||
{
|
||||
struct panel_connector *panel_connector;
|
||||
struct drm_connector *connector;
|
||||
int ret;
|
||||
|
||||
panel_connector = kzalloc(sizeof(*panel_connector), GFP_KERNEL);
|
||||
if (!panel_connector) {
|
||||
dev_err(dev->dev, "allocation failed\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
panel_connector->encoder = encoder;
|
||||
panel_connector->mod = mod;
|
||||
|
||||
connector = &panel_connector->base;
|
||||
|
||||
drm_connector_init(dev, connector, &panel_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_LVDS);
|
||||
drm_connector_helper_add(connector, &panel_connector_helper_funcs);
|
||||
|
||||
connector->interlace_allowed = 0;
|
||||
connector->doublescan_allowed = 0;
|
||||
|
||||
ret = drm_mode_connector_attach_encoder(connector, encoder);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
drm_connector_register(connector);
|
||||
|
||||
return connector;
|
||||
|
||||
fail:
|
||||
panel_connector_destroy(connector);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Module:
|
||||
*/
|
||||
|
||||
static int panel_modeset_init(struct tilcdc_module *mod, struct drm_device *dev)
|
||||
{
|
||||
struct panel_module *panel_mod = to_panel_module(mod);
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
struct drm_encoder *encoder;
|
||||
struct drm_connector *connector;
|
||||
|
||||
encoder = panel_encoder_create(dev, panel_mod);
|
||||
if (!encoder)
|
||||
return -ENOMEM;
|
||||
|
||||
connector = panel_connector_create(dev, panel_mod, encoder);
|
||||
if (!connector)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->encoders[priv->num_encoders++] = encoder;
|
||||
priv->connectors[priv->num_connectors++] = connector;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct tilcdc_module_ops panel_module_ops = {
|
||||
.modeset_init = panel_modeset_init,
|
||||
};
|
||||
|
||||
/*
|
||||
* Device:
|
||||
*/
|
||||
|
||||
/* maybe move this somewhere common if it is needed by other outputs? */
|
||||
static struct tilcdc_panel_info *of_get_panel_info(struct device_node *np)
|
||||
{
|
||||
struct device_node *info_np;
|
||||
struct tilcdc_panel_info *info;
|
||||
int ret = 0;
|
||||
|
||||
if (!np) {
|
||||
pr_err("%s: no devicenode given\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
info_np = of_get_child_by_name(np, "panel-info");
|
||||
if (!info_np) {
|
||||
pr_err("%s: could not find panel-info node\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
info = kzalloc(sizeof(*info), GFP_KERNEL);
|
||||
if (!info) {
|
||||
pr_err("%s: allocation failed\n", __func__);
|
||||
of_node_put(info_np);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret |= of_property_read_u32(info_np, "ac-bias", &info->ac_bias);
|
||||
ret |= of_property_read_u32(info_np, "ac-bias-intrpt", &info->ac_bias_intrpt);
|
||||
ret |= of_property_read_u32(info_np, "dma-burst-sz", &info->dma_burst_sz);
|
||||
ret |= of_property_read_u32(info_np, "bpp", &info->bpp);
|
||||
ret |= of_property_read_u32(info_np, "fdd", &info->fdd);
|
||||
ret |= of_property_read_u32(info_np, "sync-edge", &info->sync_edge);
|
||||
ret |= of_property_read_u32(info_np, "sync-ctrl", &info->sync_ctrl);
|
||||
ret |= of_property_read_u32(info_np, "raster-order", &info->raster_order);
|
||||
ret |= of_property_read_u32(info_np, "fifo-th", &info->fifo_th);
|
||||
|
||||
/* optional: */
|
||||
info->tft_alt_mode = of_property_read_bool(info_np, "tft-alt-mode");
|
||||
info->invert_pxl_clk = of_property_read_bool(info_np, "invert-pxl-clk");
|
||||
|
||||
if (ret) {
|
||||
pr_err("%s: error reading panel-info properties\n", __func__);
|
||||
kfree(info);
|
||||
of_node_put(info_np);
|
||||
return NULL;
|
||||
}
|
||||
of_node_put(info_np);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
static int panel_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *bl_node, *node = pdev->dev.of_node;
|
||||
struct panel_module *panel_mod;
|
||||
struct tilcdc_module *mod;
|
||||
struct pinctrl *pinctrl;
|
||||
int ret;
|
||||
|
||||
/* bail out early if no DT data: */
|
||||
if (!node) {
|
||||
dev_err(&pdev->dev, "device-tree data is missing\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
panel_mod = devm_kzalloc(&pdev->dev, sizeof(*panel_mod), GFP_KERNEL);
|
||||
if (!panel_mod)
|
||||
return -ENOMEM;
|
||||
|
||||
bl_node = of_parse_phandle(node, "backlight", 0);
|
||||
if (bl_node) {
|
||||
panel_mod->backlight = of_find_backlight_by_node(bl_node);
|
||||
of_node_put(bl_node);
|
||||
|
||||
if (!panel_mod->backlight)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
dev_info(&pdev->dev, "found backlight\n");
|
||||
}
|
||||
|
||||
panel_mod->enable_gpio = devm_gpiod_get(&pdev->dev, "enable");
|
||||
if (IS_ERR(panel_mod->enable_gpio)) {
|
||||
ret = PTR_ERR(panel_mod->enable_gpio);
|
||||
if (ret != -ENOENT) {
|
||||
dev_err(&pdev->dev, "failed to request enable GPIO\n");
|
||||
goto fail_backlight;
|
||||
}
|
||||
|
||||
/* Optional GPIO is not here, continue silently. */
|
||||
panel_mod->enable_gpio = NULL;
|
||||
} else {
|
||||
ret = gpiod_direction_output(panel_mod->enable_gpio, 0);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "failed to setup GPIO\n");
|
||||
goto fail_backlight;
|
||||
}
|
||||
dev_info(&pdev->dev, "found enable GPIO\n");
|
||||
}
|
||||
|
||||
mod = &panel_mod->base;
|
||||
pdev->dev.platform_data = mod;
|
||||
|
||||
tilcdc_module_init(mod, "panel", &panel_module_ops);
|
||||
|
||||
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
|
||||
if (IS_ERR(pinctrl))
|
||||
dev_warn(&pdev->dev, "pins are not configured\n");
|
||||
|
||||
panel_mod->timings = of_get_display_timings(node);
|
||||
if (!panel_mod->timings) {
|
||||
dev_err(&pdev->dev, "could not get panel timings\n");
|
||||
ret = -EINVAL;
|
||||
goto fail_free;
|
||||
}
|
||||
|
||||
panel_mod->info = of_get_panel_info(node);
|
||||
if (!panel_mod->info) {
|
||||
dev_err(&pdev->dev, "could not get panel info\n");
|
||||
ret = -EINVAL;
|
||||
goto fail_timings;
|
||||
}
|
||||
|
||||
mod->preferred_bpp = panel_mod->info->bpp;
|
||||
|
||||
return 0;
|
||||
|
||||
fail_timings:
|
||||
display_timings_release(panel_mod->timings);
|
||||
|
||||
fail_free:
|
||||
tilcdc_module_cleanup(mod);
|
||||
|
||||
fail_backlight:
|
||||
if (panel_mod->backlight)
|
||||
put_device(&panel_mod->backlight->dev);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int panel_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct tilcdc_module *mod = dev_get_platdata(&pdev->dev);
|
||||
struct panel_module *panel_mod = to_panel_module(mod);
|
||||
struct backlight_device *backlight = panel_mod->backlight;
|
||||
|
||||
if (backlight)
|
||||
put_device(&backlight->dev);
|
||||
|
||||
display_timings_release(panel_mod->timings);
|
||||
|
||||
tilcdc_module_cleanup(mod);
|
||||
kfree(panel_mod->info);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct of_device_id panel_of_match[] = {
|
||||
{ .compatible = "ti,tilcdc,panel", },
|
||||
{ },
|
||||
};
|
||||
|
||||
struct platform_driver panel_driver = {
|
||||
.probe = panel_probe,
|
||||
.remove = panel_remove,
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "panel",
|
||||
.of_match_table = panel_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
int __init tilcdc_panel_init(void)
|
||||
{
|
||||
return platform_driver_register(&panel_driver);
|
||||
}
|
||||
|
||||
void __exit tilcdc_panel_fini(void)
|
||||
{
|
||||
platform_driver_unregister(&panel_driver);
|
||||
}
|
||||
26
drivers/gpu/drm/tilcdc/tilcdc_panel.h
Normal file
26
drivers/gpu/drm/tilcdc/tilcdc_panel.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Texas Instruments
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef __TILCDC_PANEL_H__
|
||||
#define __TILCDC_PANEL_H__
|
||||
|
||||
/* sub-module for generic lcd panel output */
|
||||
|
||||
int tilcdc_panel_init(void);
|
||||
void tilcdc_panel_fini(void);
|
||||
|
||||
#endif /* __TILCDC_PANEL_H__ */
|
||||
155
drivers/gpu/drm/tilcdc/tilcdc_regs.h
Normal file
155
drivers/gpu/drm/tilcdc/tilcdc_regs.h
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Texas Instruments
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef __TILCDC_REGS_H__
|
||||
#define __TILCDC_REGS_H__
|
||||
|
||||
/* LCDC register definitions, based on da8xx-fb */
|
||||
|
||||
#include <linux/bitops.h>
|
||||
|
||||
#include "tilcdc_drv.h"
|
||||
|
||||
/* LCDC Status Register */
|
||||
#define LCDC_END_OF_FRAME1 BIT(9)
|
||||
#define LCDC_END_OF_FRAME0 BIT(8)
|
||||
#define LCDC_PL_LOAD_DONE BIT(6)
|
||||
#define LCDC_FIFO_UNDERFLOW BIT(5)
|
||||
#define LCDC_SYNC_LOST BIT(2)
|
||||
#define LCDC_FRAME_DONE BIT(0)
|
||||
|
||||
/* LCDC DMA Control Register */
|
||||
#define LCDC_DMA_BURST_SIZE(x) ((x) << 4)
|
||||
#define LCDC_DMA_BURST_1 0x0
|
||||
#define LCDC_DMA_BURST_2 0x1
|
||||
#define LCDC_DMA_BURST_4 0x2
|
||||
#define LCDC_DMA_BURST_8 0x3
|
||||
#define LCDC_DMA_BURST_16 0x4
|
||||
#define LCDC_V1_END_OF_FRAME_INT_ENA BIT(2)
|
||||
#define LCDC_V2_END_OF_FRAME0_INT_ENA BIT(8)
|
||||
#define LCDC_V2_END_OF_FRAME1_INT_ENA BIT(9)
|
||||
#define LCDC_DUAL_FRAME_BUFFER_ENABLE BIT(0)
|
||||
|
||||
/* LCDC Control Register */
|
||||
#define LCDC_CLK_DIVISOR(x) ((x) << 8)
|
||||
#define LCDC_RASTER_MODE 0x01
|
||||
|
||||
/* LCDC Raster Control Register */
|
||||
#define LCDC_PALETTE_LOAD_MODE(x) ((x) << 20)
|
||||
#define PALETTE_AND_DATA 0x00
|
||||
#define PALETTE_ONLY 0x01
|
||||
#define DATA_ONLY 0x02
|
||||
|
||||
#define LCDC_MONO_8BIT_MODE BIT(9)
|
||||
#define LCDC_RASTER_ORDER BIT(8)
|
||||
#define LCDC_TFT_MODE BIT(7)
|
||||
#define LCDC_V1_UNDERFLOW_INT_ENA BIT(6)
|
||||
#define LCDC_V2_UNDERFLOW_INT_ENA BIT(5)
|
||||
#define LCDC_V1_PL_INT_ENA BIT(4)
|
||||
#define LCDC_V2_PL_INT_ENA BIT(6)
|
||||
#define LCDC_MONOCHROME_MODE BIT(1)
|
||||
#define LCDC_RASTER_ENABLE BIT(0)
|
||||
#define LCDC_TFT_ALT_ENABLE BIT(23)
|
||||
#define LCDC_STN_565_ENABLE BIT(24)
|
||||
#define LCDC_V2_DMA_CLK_EN BIT(2)
|
||||
#define LCDC_V2_LIDD_CLK_EN BIT(1)
|
||||
#define LCDC_V2_CORE_CLK_EN BIT(0)
|
||||
#define LCDC_V2_LPP_B10 26
|
||||
#define LCDC_V2_TFT_24BPP_MODE BIT(25)
|
||||
#define LCDC_V2_TFT_24BPP_UNPACK BIT(26)
|
||||
|
||||
/* LCDC Raster Timing 2 Register */
|
||||
#define LCDC_AC_BIAS_TRANSITIONS_PER_INT(x) ((x) << 16)
|
||||
#define LCDC_AC_BIAS_FREQUENCY(x) ((x) << 8)
|
||||
#define LCDC_SYNC_CTRL BIT(25)
|
||||
#define LCDC_SYNC_EDGE BIT(24)
|
||||
#define LCDC_INVERT_PIXEL_CLOCK BIT(22)
|
||||
#define LCDC_INVERT_HSYNC BIT(21)
|
||||
#define LCDC_INVERT_VSYNC BIT(20)
|
||||
#define LCDC_LPP_B10 BIT(26)
|
||||
|
||||
/* LCDC Block */
|
||||
#define LCDC_PID_REG 0x0
|
||||
#define LCDC_CTRL_REG 0x4
|
||||
#define LCDC_STAT_REG 0x8
|
||||
#define LCDC_RASTER_CTRL_REG 0x28
|
||||
#define LCDC_RASTER_TIMING_0_REG 0x2c
|
||||
#define LCDC_RASTER_TIMING_1_REG 0x30
|
||||
#define LCDC_RASTER_TIMING_2_REG 0x34
|
||||
#define LCDC_DMA_CTRL_REG 0x40
|
||||
#define LCDC_DMA_FB_BASE_ADDR_0_REG 0x44
|
||||
#define LCDC_DMA_FB_CEILING_ADDR_0_REG 0x48
|
||||
#define LCDC_DMA_FB_BASE_ADDR_1_REG 0x4c
|
||||
#define LCDC_DMA_FB_CEILING_ADDR_1_REG 0x50
|
||||
|
||||
/* Interrupt Registers available only in Version 2 */
|
||||
#define LCDC_RAW_STAT_REG 0x58
|
||||
#define LCDC_MASKED_STAT_REG 0x5c
|
||||
#define LCDC_INT_ENABLE_SET_REG 0x60
|
||||
#define LCDC_INT_ENABLE_CLR_REG 0x64
|
||||
#define LCDC_END_OF_INT_IND_REG 0x68
|
||||
|
||||
/* Clock registers available only on Version 2 */
|
||||
#define LCDC_CLK_ENABLE_REG 0x6c
|
||||
#define LCDC_CLK_RESET_REG 0x70
|
||||
#define LCDC_CLK_MAIN_RESET BIT(3)
|
||||
|
||||
|
||||
/*
|
||||
* Helpers:
|
||||
*/
|
||||
|
||||
static inline void tilcdc_write(struct drm_device *dev, u32 reg, u32 data)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
iowrite32(data, priv->mmio + reg);
|
||||
}
|
||||
|
||||
static inline u32 tilcdc_read(struct drm_device *dev, u32 reg)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
return ioread32(priv->mmio + reg);
|
||||
}
|
||||
|
||||
static inline void tilcdc_set(struct drm_device *dev, u32 reg, u32 mask)
|
||||
{
|
||||
tilcdc_write(dev, reg, tilcdc_read(dev, reg) | mask);
|
||||
}
|
||||
|
||||
static inline void tilcdc_clear(struct drm_device *dev, u32 reg, u32 mask)
|
||||
{
|
||||
tilcdc_write(dev, reg, tilcdc_read(dev, reg) & ~mask);
|
||||
}
|
||||
|
||||
/* the register to read/clear irqstatus differs between v1 and v2 of the IP */
|
||||
static inline u32 tilcdc_irqstatus_reg(struct drm_device *dev)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
return (priv->rev == 2) ? LCDC_MASKED_STAT_REG : LCDC_STAT_REG;
|
||||
}
|
||||
|
||||
static inline u32 tilcdc_read_irqstatus(struct drm_device *dev)
|
||||
{
|
||||
return tilcdc_read(dev, tilcdc_irqstatus_reg(dev));
|
||||
}
|
||||
|
||||
static inline void tilcdc_clear_irqstatus(struct drm_device *dev, u32 mask)
|
||||
{
|
||||
tilcdc_write(dev, tilcdc_irqstatus_reg(dev), mask);
|
||||
}
|
||||
|
||||
#endif /* __TILCDC_REGS_H__ */
|
||||
411
drivers/gpu/drm/tilcdc/tilcdc_slave.c
Normal file
411
drivers/gpu/drm/tilcdc/tilcdc_slave.c
Normal file
|
|
@ -0,0 +1,411 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Texas Instruments
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/pinctrl/pinmux.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <drm/drm_encoder_slave.h>
|
||||
|
||||
#include "tilcdc_drv.h"
|
||||
|
||||
struct slave_module {
|
||||
struct tilcdc_module base;
|
||||
struct i2c_adapter *i2c;
|
||||
};
|
||||
#define to_slave_module(x) container_of(x, struct slave_module, base)
|
||||
|
||||
static const struct tilcdc_panel_info slave_info = {
|
||||
.bpp = 16,
|
||||
.ac_bias = 255,
|
||||
.ac_bias_intrpt = 0,
|
||||
.dma_burst_sz = 16,
|
||||
.fdd = 0x80,
|
||||
.tft_alt_mode = 0,
|
||||
.sync_edge = 0,
|
||||
.sync_ctrl = 1,
|
||||
.raster_order = 0,
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Encoder:
|
||||
*/
|
||||
|
||||
struct slave_encoder {
|
||||
struct drm_encoder_slave base;
|
||||
struct slave_module *mod;
|
||||
};
|
||||
#define to_slave_encoder(x) container_of(to_encoder_slave(x), struct slave_encoder, base)
|
||||
|
||||
static inline struct drm_encoder_slave_funcs *
|
||||
get_slave_funcs(struct drm_encoder *enc)
|
||||
{
|
||||
return to_encoder_slave(enc)->slave_funcs;
|
||||
}
|
||||
|
||||
static void slave_encoder_destroy(struct drm_encoder *encoder)
|
||||
{
|
||||
struct slave_encoder *slave_encoder = to_slave_encoder(encoder);
|
||||
if (get_slave_funcs(encoder))
|
||||
get_slave_funcs(encoder)->destroy(encoder);
|
||||
drm_encoder_cleanup(encoder);
|
||||
kfree(slave_encoder);
|
||||
}
|
||||
|
||||
static void slave_encoder_prepare(struct drm_encoder *encoder)
|
||||
{
|
||||
drm_i2c_encoder_prepare(encoder);
|
||||
tilcdc_crtc_set_panel_info(encoder->crtc, &slave_info);
|
||||
}
|
||||
|
||||
static bool slave_encoder_fixup(struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
/*
|
||||
* tilcdc does not generate VESA-complient sync but aligns
|
||||
* VS on the second edge of HS instead of first edge.
|
||||
* We use adjusted_mode, to fixup sync by aligning both rising
|
||||
* edges and add HSKEW offset to let the slave encoder fix it up.
|
||||
*/
|
||||
adjusted_mode->hskew = mode->hsync_end - mode->hsync_start;
|
||||
adjusted_mode->flags |= DRM_MODE_FLAG_HSKEW;
|
||||
|
||||
if (mode->flags & DRM_MODE_FLAG_NHSYNC) {
|
||||
adjusted_mode->flags |= DRM_MODE_FLAG_PHSYNC;
|
||||
adjusted_mode->flags &= ~DRM_MODE_FLAG_NHSYNC;
|
||||
} else {
|
||||
adjusted_mode->flags |= DRM_MODE_FLAG_NHSYNC;
|
||||
adjusted_mode->flags &= ~DRM_MODE_FLAG_PHSYNC;
|
||||
}
|
||||
|
||||
return drm_i2c_encoder_mode_fixup(encoder, mode, adjusted_mode);
|
||||
}
|
||||
|
||||
|
||||
static const struct drm_encoder_funcs slave_encoder_funcs = {
|
||||
.destroy = slave_encoder_destroy,
|
||||
};
|
||||
|
||||
static const struct drm_encoder_helper_funcs slave_encoder_helper_funcs = {
|
||||
.dpms = drm_i2c_encoder_dpms,
|
||||
.mode_fixup = slave_encoder_fixup,
|
||||
.prepare = slave_encoder_prepare,
|
||||
.commit = drm_i2c_encoder_commit,
|
||||
.mode_set = drm_i2c_encoder_mode_set,
|
||||
.save = drm_i2c_encoder_save,
|
||||
.restore = drm_i2c_encoder_restore,
|
||||
};
|
||||
|
||||
static const struct i2c_board_info info = {
|
||||
I2C_BOARD_INFO("tda998x", 0x70)
|
||||
};
|
||||
|
||||
static struct drm_encoder *slave_encoder_create(struct drm_device *dev,
|
||||
struct slave_module *mod)
|
||||
{
|
||||
struct slave_encoder *slave_encoder;
|
||||
struct drm_encoder *encoder;
|
||||
int ret;
|
||||
|
||||
slave_encoder = kzalloc(sizeof(*slave_encoder), GFP_KERNEL);
|
||||
if (!slave_encoder) {
|
||||
dev_err(dev->dev, "allocation failed\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
slave_encoder->mod = mod;
|
||||
|
||||
encoder = &slave_encoder->base.base;
|
||||
encoder->possible_crtcs = 1;
|
||||
|
||||
ret = drm_encoder_init(dev, encoder, &slave_encoder_funcs,
|
||||
DRM_MODE_ENCODER_TMDS);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
drm_encoder_helper_add(encoder, &slave_encoder_helper_funcs);
|
||||
|
||||
ret = drm_i2c_encoder_init(dev, to_encoder_slave(encoder), mod->i2c, &info);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
return encoder;
|
||||
|
||||
fail:
|
||||
slave_encoder_destroy(encoder);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Connector:
|
||||
*/
|
||||
|
||||
struct slave_connector {
|
||||
struct drm_connector base;
|
||||
|
||||
struct drm_encoder *encoder; /* our connected encoder */
|
||||
struct slave_module *mod;
|
||||
};
|
||||
#define to_slave_connector(x) container_of(x, struct slave_connector, base)
|
||||
|
||||
static void slave_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
struct slave_connector *slave_connector = to_slave_connector(connector);
|
||||
drm_connector_unregister(connector);
|
||||
drm_connector_cleanup(connector);
|
||||
kfree(slave_connector);
|
||||
}
|
||||
|
||||
static enum drm_connector_status slave_connector_detect(
|
||||
struct drm_connector *connector,
|
||||
bool force)
|
||||
{
|
||||
struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
|
||||
return get_slave_funcs(encoder)->detect(encoder, connector);
|
||||
}
|
||||
|
||||
static int slave_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
|
||||
return get_slave_funcs(encoder)->get_modes(encoder, connector);
|
||||
}
|
||||
|
||||
static int slave_connector_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
|
||||
struct tilcdc_drm_private *priv = connector->dev->dev_private;
|
||||
int ret;
|
||||
|
||||
ret = tilcdc_crtc_mode_valid(priv->crtc, mode);
|
||||
if (ret != MODE_OK)
|
||||
return ret;
|
||||
|
||||
return get_slave_funcs(encoder)->mode_valid(encoder, mode);
|
||||
}
|
||||
|
||||
static struct drm_encoder *slave_connector_best_encoder(
|
||||
struct drm_connector *connector)
|
||||
{
|
||||
struct slave_connector *slave_connector = to_slave_connector(connector);
|
||||
return slave_connector->encoder;
|
||||
}
|
||||
|
||||
static int slave_connector_set_property(struct drm_connector *connector,
|
||||
struct drm_property *property, uint64_t value)
|
||||
{
|
||||
struct drm_encoder *encoder = to_slave_connector(connector)->encoder;
|
||||
return get_slave_funcs(encoder)->set_property(encoder,
|
||||
connector, property, value);
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs slave_connector_funcs = {
|
||||
.destroy = slave_connector_destroy,
|
||||
.dpms = drm_helper_connector_dpms,
|
||||
.detect = slave_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.set_property = slave_connector_set_property,
|
||||
};
|
||||
|
||||
static const struct drm_connector_helper_funcs slave_connector_helper_funcs = {
|
||||
.get_modes = slave_connector_get_modes,
|
||||
.mode_valid = slave_connector_mode_valid,
|
||||
.best_encoder = slave_connector_best_encoder,
|
||||
};
|
||||
|
||||
static struct drm_connector *slave_connector_create(struct drm_device *dev,
|
||||
struct slave_module *mod, struct drm_encoder *encoder)
|
||||
{
|
||||
struct slave_connector *slave_connector;
|
||||
struct drm_connector *connector;
|
||||
int ret;
|
||||
|
||||
slave_connector = kzalloc(sizeof(*slave_connector), GFP_KERNEL);
|
||||
if (!slave_connector) {
|
||||
dev_err(dev->dev, "allocation failed\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
slave_connector->encoder = encoder;
|
||||
slave_connector->mod = mod;
|
||||
|
||||
connector = &slave_connector->base;
|
||||
|
||||
drm_connector_init(dev, connector, &slave_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_HDMIA);
|
||||
drm_connector_helper_add(connector, &slave_connector_helper_funcs);
|
||||
|
||||
connector->polled = DRM_CONNECTOR_POLL_CONNECT |
|
||||
DRM_CONNECTOR_POLL_DISCONNECT;
|
||||
|
||||
connector->interlace_allowed = 0;
|
||||
connector->doublescan_allowed = 0;
|
||||
|
||||
get_slave_funcs(encoder)->create_resources(encoder, connector);
|
||||
|
||||
ret = drm_mode_connector_attach_encoder(connector, encoder);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
drm_connector_register(connector);
|
||||
|
||||
return connector;
|
||||
|
||||
fail:
|
||||
slave_connector_destroy(connector);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Module:
|
||||
*/
|
||||
|
||||
static int slave_modeset_init(struct tilcdc_module *mod, struct drm_device *dev)
|
||||
{
|
||||
struct slave_module *slave_mod = to_slave_module(mod);
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
struct drm_encoder *encoder;
|
||||
struct drm_connector *connector;
|
||||
|
||||
encoder = slave_encoder_create(dev, slave_mod);
|
||||
if (!encoder)
|
||||
return -ENOMEM;
|
||||
|
||||
connector = slave_connector_create(dev, slave_mod, encoder);
|
||||
if (!connector)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->encoders[priv->num_encoders++] = encoder;
|
||||
priv->connectors[priv->num_connectors++] = connector;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct tilcdc_module_ops slave_module_ops = {
|
||||
.modeset_init = slave_modeset_init,
|
||||
};
|
||||
|
||||
/*
|
||||
* Device:
|
||||
*/
|
||||
|
||||
static struct of_device_id slave_of_match[];
|
||||
|
||||
static int slave_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct device_node *i2c_node;
|
||||
struct slave_module *slave_mod;
|
||||
struct tilcdc_module *mod;
|
||||
struct pinctrl *pinctrl;
|
||||
uint32_t i2c_phandle;
|
||||
struct i2c_adapter *slavei2c;
|
||||
int ret = -EINVAL;
|
||||
|
||||
/* bail out early if no DT data: */
|
||||
if (!node) {
|
||||
dev_err(&pdev->dev, "device-tree data is missing\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
/* Bail out early if i2c not specified */
|
||||
if (of_property_read_u32(node, "i2c", &i2c_phandle)) {
|
||||
dev_err(&pdev->dev, "could not get i2c bus phandle\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
i2c_node = of_find_node_by_phandle(i2c_phandle);
|
||||
if (!i2c_node) {
|
||||
dev_err(&pdev->dev, "could not get i2c bus node\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* but defer the probe if it can't be initialized it might come later */
|
||||
slavei2c = of_find_i2c_adapter_by_node(i2c_node);
|
||||
of_node_put(i2c_node);
|
||||
|
||||
if (!slavei2c) {
|
||||
ret = -EPROBE_DEFER;
|
||||
tilcdc_slave_probedefer(true);
|
||||
dev_err(&pdev->dev, "could not get i2c\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
slave_mod = kzalloc(sizeof(*slave_mod), GFP_KERNEL);
|
||||
if (!slave_mod) {
|
||||
ret = -ENOMEM;
|
||||
goto fail_adapter;
|
||||
}
|
||||
|
||||
mod = &slave_mod->base;
|
||||
pdev->dev.platform_data = mod;
|
||||
|
||||
mod->preferred_bpp = slave_info.bpp;
|
||||
|
||||
slave_mod->i2c = slavei2c;
|
||||
|
||||
tilcdc_module_init(mod, "slave", &slave_module_ops);
|
||||
|
||||
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
|
||||
if (IS_ERR(pinctrl))
|
||||
dev_warn(&pdev->dev, "pins are not configured\n");
|
||||
|
||||
tilcdc_slave_probedefer(false);
|
||||
|
||||
return 0;
|
||||
|
||||
fail_adapter:
|
||||
i2c_put_adapter(slavei2c);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int slave_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct tilcdc_module *mod = dev_get_platdata(&pdev->dev);
|
||||
struct slave_module *slave_mod = to_slave_module(mod);
|
||||
|
||||
tilcdc_module_cleanup(mod);
|
||||
kfree(slave_mod);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct of_device_id slave_of_match[] = {
|
||||
{ .compatible = "ti,tilcdc,slave", },
|
||||
{ },
|
||||
};
|
||||
|
||||
struct platform_driver slave_driver = {
|
||||
.probe = slave_probe,
|
||||
.remove = slave_remove,
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "slave",
|
||||
.of_match_table = slave_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
int __init tilcdc_slave_init(void)
|
||||
{
|
||||
return platform_driver_register(&slave_driver);
|
||||
}
|
||||
|
||||
void __exit tilcdc_slave_fini(void)
|
||||
{
|
||||
platform_driver_unregister(&slave_driver);
|
||||
}
|
||||
26
drivers/gpu/drm/tilcdc/tilcdc_slave.h
Normal file
26
drivers/gpu/drm/tilcdc/tilcdc_slave.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Texas Instruments
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef __TILCDC_SLAVE_H__
|
||||
#define __TILCDC_SLAVE_H__
|
||||
|
||||
/* sub-module for i2c slave encoder output */
|
||||
|
||||
int tilcdc_slave_init(void);
|
||||
void tilcdc_slave_fini(void);
|
||||
|
||||
#endif /* __TILCDC_SLAVE_H__ */
|
||||
420
drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
Normal file
420
drivers/gpu/drm/tilcdc/tilcdc_tfp410.c
Normal file
|
|
@ -0,0 +1,420 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Texas Instruments
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/pinctrl/pinmux.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
|
||||
#include "tilcdc_drv.h"
|
||||
|
||||
struct tfp410_module {
|
||||
struct tilcdc_module base;
|
||||
struct i2c_adapter *i2c;
|
||||
int gpio;
|
||||
};
|
||||
#define to_tfp410_module(x) container_of(x, struct tfp410_module, base)
|
||||
|
||||
|
||||
static const struct tilcdc_panel_info dvi_info = {
|
||||
.ac_bias = 255,
|
||||
.ac_bias_intrpt = 0,
|
||||
.dma_burst_sz = 16,
|
||||
.bpp = 16,
|
||||
.fdd = 0x80,
|
||||
.tft_alt_mode = 0,
|
||||
.sync_edge = 0,
|
||||
.sync_ctrl = 1,
|
||||
.raster_order = 0,
|
||||
};
|
||||
|
||||
/*
|
||||
* Encoder:
|
||||
*/
|
||||
|
||||
struct tfp410_encoder {
|
||||
struct drm_encoder base;
|
||||
struct tfp410_module *mod;
|
||||
int dpms;
|
||||
};
|
||||
#define to_tfp410_encoder(x) container_of(x, struct tfp410_encoder, base)
|
||||
|
||||
|
||||
static void tfp410_encoder_destroy(struct drm_encoder *encoder)
|
||||
{
|
||||
struct tfp410_encoder *tfp410_encoder = to_tfp410_encoder(encoder);
|
||||
drm_encoder_cleanup(encoder);
|
||||
kfree(tfp410_encoder);
|
||||
}
|
||||
|
||||
static void tfp410_encoder_dpms(struct drm_encoder *encoder, int mode)
|
||||
{
|
||||
struct tfp410_encoder *tfp410_encoder = to_tfp410_encoder(encoder);
|
||||
|
||||
if (tfp410_encoder->dpms == mode)
|
||||
return;
|
||||
|
||||
if (mode == DRM_MODE_DPMS_ON) {
|
||||
DBG("Power on");
|
||||
gpio_direction_output(tfp410_encoder->mod->gpio, 1);
|
||||
} else {
|
||||
DBG("Power off");
|
||||
gpio_direction_output(tfp410_encoder->mod->gpio, 0);
|
||||
}
|
||||
|
||||
tfp410_encoder->dpms = mode;
|
||||
}
|
||||
|
||||
static bool tfp410_encoder_mode_fixup(struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
/* nothing needed */
|
||||
return true;
|
||||
}
|
||||
|
||||
static void tfp410_encoder_prepare(struct drm_encoder *encoder)
|
||||
{
|
||||
tfp410_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
|
||||
tilcdc_crtc_set_panel_info(encoder->crtc, &dvi_info);
|
||||
}
|
||||
|
||||
static void tfp410_encoder_commit(struct drm_encoder *encoder)
|
||||
{
|
||||
tfp410_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
|
||||
}
|
||||
|
||||
static void tfp410_encoder_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
/* nothing needed */
|
||||
}
|
||||
|
||||
static const struct drm_encoder_funcs tfp410_encoder_funcs = {
|
||||
.destroy = tfp410_encoder_destroy,
|
||||
};
|
||||
|
||||
static const struct drm_encoder_helper_funcs tfp410_encoder_helper_funcs = {
|
||||
.dpms = tfp410_encoder_dpms,
|
||||
.mode_fixup = tfp410_encoder_mode_fixup,
|
||||
.prepare = tfp410_encoder_prepare,
|
||||
.commit = tfp410_encoder_commit,
|
||||
.mode_set = tfp410_encoder_mode_set,
|
||||
};
|
||||
|
||||
static struct drm_encoder *tfp410_encoder_create(struct drm_device *dev,
|
||||
struct tfp410_module *mod)
|
||||
{
|
||||
struct tfp410_encoder *tfp410_encoder;
|
||||
struct drm_encoder *encoder;
|
||||
int ret;
|
||||
|
||||
tfp410_encoder = kzalloc(sizeof(*tfp410_encoder), GFP_KERNEL);
|
||||
if (!tfp410_encoder) {
|
||||
dev_err(dev->dev, "allocation failed\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tfp410_encoder->dpms = DRM_MODE_DPMS_OFF;
|
||||
tfp410_encoder->mod = mod;
|
||||
|
||||
encoder = &tfp410_encoder->base;
|
||||
encoder->possible_crtcs = 1;
|
||||
|
||||
ret = drm_encoder_init(dev, encoder, &tfp410_encoder_funcs,
|
||||
DRM_MODE_ENCODER_TMDS);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
drm_encoder_helper_add(encoder, &tfp410_encoder_helper_funcs);
|
||||
|
||||
return encoder;
|
||||
|
||||
fail:
|
||||
tfp410_encoder_destroy(encoder);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Connector:
|
||||
*/
|
||||
|
||||
struct tfp410_connector {
|
||||
struct drm_connector base;
|
||||
|
||||
struct drm_encoder *encoder; /* our connected encoder */
|
||||
struct tfp410_module *mod;
|
||||
};
|
||||
#define to_tfp410_connector(x) container_of(x, struct tfp410_connector, base)
|
||||
|
||||
|
||||
static void tfp410_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector);
|
||||
drm_connector_unregister(connector);
|
||||
drm_connector_cleanup(connector);
|
||||
kfree(tfp410_connector);
|
||||
}
|
||||
|
||||
static enum drm_connector_status tfp410_connector_detect(
|
||||
struct drm_connector *connector,
|
||||
bool force)
|
||||
{
|
||||
struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector);
|
||||
|
||||
if (drm_probe_ddc(tfp410_connector->mod->i2c))
|
||||
return connector_status_connected;
|
||||
|
||||
return connector_status_unknown;
|
||||
}
|
||||
|
||||
static int tfp410_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector);
|
||||
struct edid *edid;
|
||||
int ret = 0;
|
||||
|
||||
edid = drm_get_edid(connector, tfp410_connector->mod->i2c);
|
||||
|
||||
drm_mode_connector_update_edid_property(connector, edid);
|
||||
|
||||
if (edid) {
|
||||
ret = drm_add_edid_modes(connector, edid);
|
||||
kfree(edid);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tfp410_connector_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
struct tilcdc_drm_private *priv = connector->dev->dev_private;
|
||||
/* our only constraints are what the crtc can generate: */
|
||||
return tilcdc_crtc_mode_valid(priv->crtc, mode);
|
||||
}
|
||||
|
||||
static struct drm_encoder *tfp410_connector_best_encoder(
|
||||
struct drm_connector *connector)
|
||||
{
|
||||
struct tfp410_connector *tfp410_connector = to_tfp410_connector(connector);
|
||||
return tfp410_connector->encoder;
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs tfp410_connector_funcs = {
|
||||
.destroy = tfp410_connector_destroy,
|
||||
.dpms = drm_helper_connector_dpms,
|
||||
.detect = tfp410_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
};
|
||||
|
||||
static const struct drm_connector_helper_funcs tfp410_connector_helper_funcs = {
|
||||
.get_modes = tfp410_connector_get_modes,
|
||||
.mode_valid = tfp410_connector_mode_valid,
|
||||
.best_encoder = tfp410_connector_best_encoder,
|
||||
};
|
||||
|
||||
static struct drm_connector *tfp410_connector_create(struct drm_device *dev,
|
||||
struct tfp410_module *mod, struct drm_encoder *encoder)
|
||||
{
|
||||
struct tfp410_connector *tfp410_connector;
|
||||
struct drm_connector *connector;
|
||||
int ret;
|
||||
|
||||
tfp410_connector = kzalloc(sizeof(*tfp410_connector), GFP_KERNEL);
|
||||
if (!tfp410_connector) {
|
||||
dev_err(dev->dev, "allocation failed\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
tfp410_connector->encoder = encoder;
|
||||
tfp410_connector->mod = mod;
|
||||
|
||||
connector = &tfp410_connector->base;
|
||||
|
||||
drm_connector_init(dev, connector, &tfp410_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_DVID);
|
||||
drm_connector_helper_add(connector, &tfp410_connector_helper_funcs);
|
||||
|
||||
connector->polled = DRM_CONNECTOR_POLL_CONNECT |
|
||||
DRM_CONNECTOR_POLL_DISCONNECT;
|
||||
|
||||
connector->interlace_allowed = 0;
|
||||
connector->doublescan_allowed = 0;
|
||||
|
||||
ret = drm_mode_connector_attach_encoder(connector, encoder);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
drm_connector_register(connector);
|
||||
|
||||
return connector;
|
||||
|
||||
fail:
|
||||
tfp410_connector_destroy(connector);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* Module:
|
||||
*/
|
||||
|
||||
static int tfp410_modeset_init(struct tilcdc_module *mod, struct drm_device *dev)
|
||||
{
|
||||
struct tfp410_module *tfp410_mod = to_tfp410_module(mod);
|
||||
struct tilcdc_drm_private *priv = dev->dev_private;
|
||||
struct drm_encoder *encoder;
|
||||
struct drm_connector *connector;
|
||||
|
||||
encoder = tfp410_encoder_create(dev, tfp410_mod);
|
||||
if (!encoder)
|
||||
return -ENOMEM;
|
||||
|
||||
connector = tfp410_connector_create(dev, tfp410_mod, encoder);
|
||||
if (!connector)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->encoders[priv->num_encoders++] = encoder;
|
||||
priv->connectors[priv->num_connectors++] = connector;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct tilcdc_module_ops tfp410_module_ops = {
|
||||
.modeset_init = tfp410_modeset_init,
|
||||
};
|
||||
|
||||
/*
|
||||
* Device:
|
||||
*/
|
||||
|
||||
static struct of_device_id tfp410_of_match[];
|
||||
|
||||
static int tfp410_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *node = pdev->dev.of_node;
|
||||
struct device_node *i2c_node;
|
||||
struct tfp410_module *tfp410_mod;
|
||||
struct tilcdc_module *mod;
|
||||
struct pinctrl *pinctrl;
|
||||
uint32_t i2c_phandle;
|
||||
int ret = -EINVAL;
|
||||
|
||||
/* bail out early if no DT data: */
|
||||
if (!node) {
|
||||
dev_err(&pdev->dev, "device-tree data is missing\n");
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
tfp410_mod = kzalloc(sizeof(*tfp410_mod), GFP_KERNEL);
|
||||
if (!tfp410_mod)
|
||||
return -ENOMEM;
|
||||
|
||||
mod = &tfp410_mod->base;
|
||||
pdev->dev.platform_data = mod;
|
||||
|
||||
tilcdc_module_init(mod, "tfp410", &tfp410_module_ops);
|
||||
|
||||
pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
|
||||
if (IS_ERR(pinctrl))
|
||||
dev_warn(&pdev->dev, "pins are not configured\n");
|
||||
|
||||
if (of_property_read_u32(node, "i2c", &i2c_phandle)) {
|
||||
dev_err(&pdev->dev, "could not get i2c bus phandle\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
mod->preferred_bpp = dvi_info.bpp;
|
||||
|
||||
i2c_node = of_find_node_by_phandle(i2c_phandle);
|
||||
if (!i2c_node) {
|
||||
dev_err(&pdev->dev, "could not get i2c bus node\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
tfp410_mod->i2c = of_find_i2c_adapter_by_node(i2c_node);
|
||||
if (!tfp410_mod->i2c) {
|
||||
dev_err(&pdev->dev, "could not get i2c\n");
|
||||
of_node_put(i2c_node);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
of_node_put(i2c_node);
|
||||
|
||||
tfp410_mod->gpio = of_get_named_gpio_flags(node, "powerdn-gpio",
|
||||
0, NULL);
|
||||
if (IS_ERR_VALUE(tfp410_mod->gpio)) {
|
||||
dev_warn(&pdev->dev, "No power down GPIO\n");
|
||||
} else {
|
||||
ret = gpio_request(tfp410_mod->gpio, "DVI_PDn");
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "could not get DVI_PDn gpio\n");
|
||||
goto fail_adapter;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
fail_adapter:
|
||||
i2c_put_adapter(tfp410_mod->i2c);
|
||||
|
||||
fail:
|
||||
kfree(tfp410_mod);
|
||||
tilcdc_module_cleanup(mod);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tfp410_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct tilcdc_module *mod = dev_get_platdata(&pdev->dev);
|
||||
struct tfp410_module *tfp410_mod = to_tfp410_module(mod);
|
||||
|
||||
i2c_put_adapter(tfp410_mod->i2c);
|
||||
gpio_free(tfp410_mod->gpio);
|
||||
|
||||
tilcdc_module_cleanup(mod);
|
||||
kfree(tfp410_mod);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct of_device_id tfp410_of_match[] = {
|
||||
{ .compatible = "ti,tilcdc,tfp410", },
|
||||
{ },
|
||||
};
|
||||
|
||||
struct platform_driver tfp410_driver = {
|
||||
.probe = tfp410_probe,
|
||||
.remove = tfp410_remove,
|
||||
.driver = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "tfp410",
|
||||
.of_match_table = tfp410_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
int __init tilcdc_tfp410_init(void)
|
||||
{
|
||||
return platform_driver_register(&tfp410_driver);
|
||||
}
|
||||
|
||||
void __exit tilcdc_tfp410_fini(void)
|
||||
{
|
||||
platform_driver_unregister(&tfp410_driver);
|
||||
}
|
||||
26
drivers/gpu/drm/tilcdc/tilcdc_tfp410.h
Normal file
26
drivers/gpu/drm/tilcdc/tilcdc_tfp410.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (C) 2012 Texas Instruments
|
||||
* Author: Rob Clark <robdclark@gmail.com>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef __TILCDC_TFP410_H__
|
||||
#define __TILCDC_TFP410_H__
|
||||
|
||||
/* sub-module for tfp410 dvi adaptor */
|
||||
|
||||
int tilcdc_tfp410_init(void);
|
||||
void tilcdc_tfp410_fini(void);
|
||||
|
||||
#endif /* __TILCDC_TFP410_H__ */
|
||||
Loading…
Add table
Add a link
Reference in a new issue