Skip to content

Commit

Permalink
drm/rcar-du: Add internal LVDS encoder support
Browse files Browse the repository at this point in the history
The R8A7790 includes two internal LVDS encoders. Support them in the DU
driver.

Signed-off-by: Laurent Pinchart <laurent.pinchart+renesas@ideasonboard.com>
  • Loading branch information
Laurent Pinchart committed Aug 9, 2013
1 parent 7cbc05c commit 90374b5
Show file tree
Hide file tree
Showing 12 changed files with 374 additions and 3 deletions.
7 changes: 7 additions & 0 deletions drivers/gpu/drm/rcar-du/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,10 @@ config DRM_RCAR_DU
help
Choose this option if you have an R-Car chipset.
If M is selected the module will be called rcar-du-drm.

config DRM_RCAR_LVDS
bool "R-Car DU LVDS Encoder Support"
depends on DRM_RCAR_DU
help
Enable support the R-Car Display Unit embedded LVDS encoders
(currently only on R8A7790).
4 changes: 3 additions & 1 deletion drivers/gpu/drm/rcar-du/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ rcar-du-drm-y := rcar_du_crtc.o \
rcar_du_plane.o \
rcar_du_vgacon.o

obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o
rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_lvdsenc.o

obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o
2 changes: 0 additions & 2 deletions drivers/gpu/drm/rcar-du/rcar_du_crtc.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@
#include "rcar_du_plane.h"
#include "rcar_du_regs.h"

#define to_rcar_crtc(c) container_of(c, struct rcar_du_crtc, crtc)

static u32 rcar_du_crtc_read(struct rcar_du_crtc *rcrtc, u32 reg)
{
struct rcar_du_device *rcdu = rcrtc->group->dev;
Expand Down
2 changes: 2 additions & 0 deletions drivers/gpu/drm/rcar-du/rcar_du_crtc.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ struct rcar_du_crtc {
struct rcar_du_plane *plane;
};

#define to_rcar_crtc(c) container_of(c, struct rcar_du_crtc, crtc)

int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int index);
void rcar_du_crtc_enable_vblank(struct rcar_du_crtc *rcrtc, bool enable);
void rcar_du_crtc_cancel_page_flip(struct rcar_du_crtc *rcrtc,
Expand Down
2 changes: 2 additions & 0 deletions drivers/gpu/drm/rcar-du/rcar_du_drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ static const struct rcar_du_device_info rcar_du_r8a7779_info = {
.encoder_type = DRM_MODE_ENCODER_NONE,
},
},
.num_lvds = 0,
};

static const struct rcar_du_device_info rcar_du_r8a7790_info = {
Expand All @@ -255,6 +256,7 @@ static const struct rcar_du_device_info rcar_du_r8a7790_info = {
.encoder_type = DRM_MODE_ENCODER_LVDS,
},
},
.num_lvds = 2,
};

static const struct platform_device_id rcar_du_id_table[] = {
Expand Down
4 changes: 4 additions & 0 deletions drivers/gpu/drm/rcar-du/rcar_du_drv.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ struct clk;
struct device;
struct drm_device;
struct rcar_du_device;
struct rcar_du_lvdsenc;

#define RCAR_DU_FEATURE_CRTC_IRQ_CLOCK (1 << 0) /* Per-CRTC IRQ and clock */
#define RCAR_DU_FEATURE_ALIGN_128B (1 << 1) /* Align pitches to 128 bytes */
Expand All @@ -48,11 +49,13 @@ struct rcar_du_output_routing {
* @features: device features (RCAR_DU_FEATURE_*)
* @num_crtcs: total number of CRTCs
* @routes: array of CRTC to output routes, indexed by output (RCAR_DU_OUTPUT_*)
* @num_lvds: number of internal LVDS encoders
*/
struct rcar_du_device_info {
unsigned int features;
unsigned int num_crtcs;
struct rcar_du_output_routing routes[RCAR_DU_OUTPUT_MAX];
unsigned int num_lvds;
};

struct rcar_du_device {
Expand All @@ -70,6 +73,7 @@ struct rcar_du_device {
struct rcar_du_group groups[2];

unsigned int dpad0_source;
struct rcar_du_lvdsenc *lvds[2];
};

static inline bool rcar_du_has(struct rcar_du_device *rcdu,
Expand Down
38 changes: 38 additions & 0 deletions drivers/gpu/drm/rcar-du/rcar_du_encoder.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
* (at your option) any later version.
*/

#include <linux/export.h>

#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
Expand All @@ -19,6 +21,7 @@
#include "rcar_du_encoder.h"
#include "rcar_du_kms.h"
#include "rcar_du_lvdscon.h"
#include "rcar_du_lvdsenc.h"
#include "rcar_du_vgacon.h"

/* -----------------------------------------------------------------------------
Expand All @@ -39,12 +42,17 @@ rcar_du_connector_best_encoder(struct drm_connector *connector)

static void rcar_du_encoder_dpms(struct drm_encoder *encoder, int mode)
{
struct rcar_du_encoder *renc = to_rcar_encoder(encoder);

if (renc->lvds)
rcar_du_lvdsenc_dpms(renc->lvds, encoder->crtc, mode);
}

static bool rcar_du_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
const struct drm_display_mode *panel_mode;
struct drm_device *dev = encoder->dev;
struct drm_connector *connector;
Expand Down Expand Up @@ -82,15 +90,32 @@ static bool rcar_du_encoder_mode_fixup(struct drm_encoder *encoder,
/* The flat panel mode is fixed, just copy it to the adjusted mode. */
drm_mode_copy(adjusted_mode, panel_mode);

/* The internal LVDS encoder has a clock frequency operating range of
* 30MHz to 150MHz. Clamp the clock accordingly.
*/
if (renc->lvds)
adjusted_mode->clock = clamp(adjusted_mode->clock,
30000, 150000);

return true;
}

static void rcar_du_encoder_mode_prepare(struct drm_encoder *encoder)
{
struct rcar_du_encoder *renc = to_rcar_encoder(encoder);

if (renc->lvds)
rcar_du_lvdsenc_dpms(renc->lvds, encoder->crtc,
DRM_MODE_DPMS_OFF);
}

static void rcar_du_encoder_mode_commit(struct drm_encoder *encoder)
{
struct rcar_du_encoder *renc = to_rcar_encoder(encoder);

if (renc->lvds)
rcar_du_lvdsenc_dpms(renc->lvds, encoder->crtc,
DRM_MODE_DPMS_ON);
}

static void rcar_du_encoder_mode_set(struct drm_encoder *encoder,
Expand Down Expand Up @@ -129,6 +154,19 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,

renc->output = output;

switch (output) {
case RCAR_DU_OUTPUT_LVDS0:
renc->lvds = rcdu->lvds[0];
break;

case RCAR_DU_OUTPUT_LVDS1:
renc->lvds = rcdu->lvds[1];
break;

default:
break;
}

switch (type) {
case RCAR_DU_ENCODER_VGA:
encoder_type = DRM_MODE_ENCODER_DAC;
Expand Down
2 changes: 2 additions & 0 deletions drivers/gpu/drm/rcar-du/rcar_du_encoder.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
#include <drm/drm_crtc.h>

struct rcar_du_device;
struct rcar_du_lvdsenc;

struct rcar_du_encoder {
struct drm_encoder encoder;
enum rcar_du_output output;
struct rcar_du_lvdsenc *lvds;
};

#define to_rcar_encoder(e) \
Expand Down
5 changes: 5 additions & 0 deletions drivers/gpu/drm/rcar-du/rcar_du_kms.c
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include "rcar_du_drv.h"
#include "rcar_du_encoder.h"
#include "rcar_du_kms.h"
#include "rcar_du_lvdsenc.h"
#include "rcar_du_regs.h"

/* -----------------------------------------------------------------------------
Expand Down Expand Up @@ -217,6 +218,10 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu)
}

/* Initialize the encoders. */
ret = rcar_du_lvdsenc_init(rcdu);
if (ret < 0)
return ret;

for (i = 0; i < rcdu->pdata->num_encoders; ++i) {
const struct rcar_du_encoder_data *pdata =
&rcdu->pdata->encoders[i];
Expand Down
196 changes: 196 additions & 0 deletions drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*
* rcar_du_lvdsenc.c -- R-Car Display Unit LVDS Encoder
*
* Copyright (C) 2013 Renesas Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/slab.h>

#include "rcar_du_drv.h"
#include "rcar_du_encoder.h"
#include "rcar_du_lvdsenc.h"
#include "rcar_lvds_regs.h"

struct rcar_du_lvdsenc {
struct rcar_du_device *dev;

unsigned int index;
void __iomem *mmio;
struct clk *clock;
int dpms;

enum rcar_lvds_input input;
};

static void rcar_lvds_write(struct rcar_du_lvdsenc *lvds, u32 reg, u32 data)
{
iowrite32(data, lvds->mmio + reg);
}

static int rcar_du_lvdsenc_start(struct rcar_du_lvdsenc *lvds,
struct rcar_du_crtc *rcrtc)
{
const struct drm_display_mode *mode = &rcrtc->crtc.mode;
unsigned int freq = mode->clock;
u32 lvdcr0;
u32 pllcr;
int ret;

if (lvds->dpms == DRM_MODE_DPMS_ON)
return 0;

ret = clk_prepare_enable(lvds->clock);
if (ret < 0)
return ret;

/* PLL clock configuration */
if (freq <= 38000)
pllcr = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_38M;
else if (freq <= 60000)
pllcr = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_60M;
else if (freq <= 121000)
pllcr = LVDPLLCR_CEEN | LVDPLLCR_COSEL | LVDPLLCR_PLLDLYCNT_121M;
else
pllcr = LVDPLLCR_PLLDLYCNT_150M;

rcar_lvds_write(lvds, LVDPLLCR, pllcr);

/* Hardcode the channels and control signals routing for now.
*
* HSYNC -> CTRL0
* VSYNC -> CTRL1
* DISP -> CTRL2
* 0 -> CTRL3
*
* Channels 1 and 3 are switched on ES1.
*/
rcar_lvds_write(lvds, LVDCTRCR, LVDCTRCR_CTR3SEL_ZERO |
LVDCTRCR_CTR2SEL_DISP | LVDCTRCR_CTR1SEL_VSYNC |
LVDCTRCR_CTR0SEL_HSYNC);
rcar_lvds_write(lvds, LVDCHCR,
LVDCHCR_CHSEL_CH(0, 0) | LVDCHCR_CHSEL_CH(1, 3) |
LVDCHCR_CHSEL_CH(2, 2) | LVDCHCR_CHSEL_CH(3, 1));

/* Select the input, hardcode mode 0, enable LVDS operation and turn
* bias circuitry on.
*/
lvdcr0 = LVDCR0_BEN | LVDCR0_LVEN;
if (rcrtc->index == 2)
lvdcr0 |= LVDCR0_DUSEL;
rcar_lvds_write(lvds, LVDCR0, lvdcr0);

/* Turn all the channels on. */
rcar_lvds_write(lvds, LVDCR1, LVDCR1_CHSTBY(3) | LVDCR1_CHSTBY(2) |
LVDCR1_CHSTBY(1) | LVDCR1_CHSTBY(0) | LVDCR1_CLKSTBY);

/* Turn the PLL on, wait for the startup delay, and turn the output
* on.
*/
lvdcr0 |= LVDCR0_PLLEN;
rcar_lvds_write(lvds, LVDCR0, lvdcr0);

usleep_range(100, 150);

lvdcr0 |= LVDCR0_LVRES;
rcar_lvds_write(lvds, LVDCR0, lvdcr0);

lvds->dpms = DRM_MODE_DPMS_ON;
return 0;
}

static void rcar_du_lvdsenc_stop(struct rcar_du_lvdsenc *lvds)
{
if (lvds->dpms == DRM_MODE_DPMS_OFF)
return;

rcar_lvds_write(lvds, LVDCR0, 0);
rcar_lvds_write(lvds, LVDCR1, 0);

clk_disable_unprepare(lvds->clock);

lvds->dpms = DRM_MODE_DPMS_OFF;
}

int rcar_du_lvdsenc_dpms(struct rcar_du_lvdsenc *lvds,
struct drm_crtc *crtc, int mode)
{
if (mode == DRM_MODE_DPMS_OFF) {
rcar_du_lvdsenc_stop(lvds);
return 0;
} else if (crtc) {
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
return rcar_du_lvdsenc_start(lvds, rcrtc);
} else
return -EINVAL;
}

static int rcar_du_lvdsenc_get_resources(struct rcar_du_lvdsenc *lvds,
struct platform_device *pdev)
{
struct resource *mem;
char name[7];

sprintf(name, "lvds.%u", lvds->index);

mem = platform_get_resource_byname(pdev, IORESOURCE_MEM, name);
if (mem == NULL) {
dev_err(&pdev->dev, "failed to get memory resource for %s\n",
name);
return -EINVAL;
}

lvds->mmio = devm_ioremap_resource(&pdev->dev, mem);
if (lvds->mmio == NULL) {
dev_err(&pdev->dev, "failed to remap memory resource for %s\n",
name);
return -ENOMEM;
}

lvds->clock = devm_clk_get(&pdev->dev, name);
if (IS_ERR(lvds->clock)) {
dev_err(&pdev->dev, "failed to get clock for %s\n", name);
return PTR_ERR(lvds->clock);
}

return 0;
}

int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu)
{
struct platform_device *pdev = to_platform_device(rcdu->dev);
struct rcar_du_lvdsenc *lvds;
unsigned int i;
int ret;

for (i = 0; i < rcdu->info->num_lvds; ++i) {
lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
if (lvds == NULL) {
dev_err(&pdev->dev, "failed to allocate private data\n");
return -ENOMEM;
}

lvds->dev = rcdu;
lvds->index = i;
lvds->input = i ? RCAR_LVDS_INPUT_DU1 : RCAR_LVDS_INPUT_DU0;
lvds->dpms = DRM_MODE_DPMS_OFF;

ret = rcar_du_lvdsenc_get_resources(lvds, pdev);
if (ret < 0)
return ret;

rcdu->lvds[i] = lvds;
}

return 0;
}
Loading

0 comments on commit 90374b5

Please sign in to comment.