Commit 0cf09031 authored by Dave Airlie's avatar Dave Airlie

Merge branch 'drm_bridge_for_4.8' of https://github.com/boddob/linux into drm-next

This is an update to the previous drm bridge pull request. The ADV7511
driver's conversion from slave encoder to bridge meant that its users
(the rcar-du kms driver) should use the bridge interface too. This pull
request now also contains a commit that updates the rcar-du's hdmi encoder
interface from slave encoder to bridge.

The other updates are as before:

- Converts the ADV7511 i2c slave encoder driver to a bridge driver.
  Adds support for the ADV7533 bridge chip.
- Add bridge driver for TC358767 (DSI/DPI to eDP) encoder chips.

* 'drm_bridge_for_4.8' of https://github.com/boddob/linux:
  drm: rcar-du: Remove i2c slave encoder interface for hdmi encoder
  drm/bridge: tc358767: Add DPI to eDP bridge driver
  dt-bindings: tc358767: add DT documentation
  dt-bindings: drm/bridge: Update bindings for ADV7533
  drm/bridge: adv7533: Change number of DSI lanes dynamically
  drm/bridge: adv7533: Use internal timing generator
  drm/bridge: adv7533: Create a MIPI DSI device
  drm/bridge: adv7533: Initial support for ADV7533
  drm/bridge: adv7511: Fix mutex deadlock when interrupts are disabled
  drm/i2c: adv7511: Move to bridge folder
  drm/i2c: adv7511: Convert to drm_bridge
parents d315495d 1d926114
Analog Device ADV7511(W)/13 HDMI Encoders Analog Device ADV7511(W)/13/33 HDMI Encoders
----------------------------------------- -----------------------------------------
The ADV7511, ADV7511W and ADV7513 are HDMI audio and video transmitters The ADV7511, ADV7511W, ADV7513 and ADV7533 are HDMI audio and video transmitters
compatible with HDMI 1.4 and DVI 1.0. They support color space conversion, compatible with HDMI 1.4 and DVI 1.0. They support color space conversion,
S/PDIF, CEC and HDCP. S/PDIF, CEC and HDCP. ADV7533 supports the DSI interface for input pixels, while
the others support RGB interface.
Required properties: Required properties:
- compatible: Should be one of "adi,adv7511", "adi,adv7511w" or "adi,adv7513" - compatible: Should be one of:
"adi,adv7511"
"adi,adv7511w"
"adi,adv7513"
"adi,adv7533"
- reg: I2C slave address - reg: I2C slave address
The ADV7511 supports a large number of input data formats that differ by their The ADV7511 supports a large number of input data formats that differ by their
...@@ -32,6 +38,11 @@ The following input format properties are required except in "rgb 1x" and ...@@ -32,6 +38,11 @@ The following input format properties are required except in "rgb 1x" and
- adi,input-justification: The input bit justification ("left", "evenly", - adi,input-justification: The input bit justification ("left", "evenly",
"right"). "right").
The following properties are required for ADV7533:
- adi,dsi-lanes: Number of DSI data lanes connected to the DSI host. It should
be one of 1, 2, 3 or 4.
Optional properties: Optional properties:
- interrupts: Specifier for the ADV7511 interrupt - interrupts: Specifier for the ADV7511 interrupt
...@@ -42,13 +53,18 @@ Optional properties: ...@@ -42,13 +53,18 @@ Optional properties:
- adi,embedded-sync: The input uses synchronization signals embedded in the - adi,embedded-sync: The input uses synchronization signals embedded in the
data stream (similar to BT.656). Defaults to separate H/V synchronization data stream (similar to BT.656). Defaults to separate H/V synchronization
signals. signals.
- adi,disable-timing-generator: Only for ADV7533. Disables the internal timing
generator. The chip will rely on the sync signals in the DSI data lanes,
rather than generate its own timings for HDMI output.
Required nodes: Required nodes:
The ADV7511 has two video ports. Their connections are modelled using the OF The ADV7511 has two video ports. Their connections are modelled using the OF
graph bindings specified in Documentation/devicetree/bindings/graph.txt. graph bindings specified in Documentation/devicetree/bindings/graph.txt.
- Video port 0 for the RGB or YUV input - Video port 0 for the RGB, YUV or DSI input. In the case of ADV7533, the
remote endpoint phandle should be a reference to a valid mipi_dsi_host device
node.
- Video port 1 for the HDMI output - Video port 1 for the HDMI output
......
Toshiba TC358767 eDP bridge bindings
Required properties:
- compatible: "toshiba,tc358767"
- reg: i2c address of the bridge, 0x68 or 0x0f, depending on bootstrap pins
- clock-names: should be "ref"
- clocks: OF device-tree clock specification for refclk input. The reference
clock rate must be 13 MHz, 19.2 MHz, 26 MHz, or 38.4 MHz.
Optional properties:
- shutdown-gpios: OF device-tree gpio specification for SD pin
(active high shutdown input)
- reset-gpios: OF device-tree gpio specification for RSTX pin
(active low system reset)
- ports: the ports node can contain video interface port nodes to connect
to a DPI/DSI source and to an eDP/DP sink according to [1][2]:
- port@0: DSI input port
- port@1: DPI input port
- port@2: eDP/DP output port
[1]: Documentation/devicetree/bindings/graph.txt
[2]: Documentation/devicetree/bindings/media/video-interfaces.txt
Example:
edp-bridge@68 {
compatible = "toshiba,tc358767";
reg = <0x68>;
shutdown-gpios = <&gpio3 23 GPIO_ACTIVE_HIGH>;
reset-gpios = <&gpio3 24 GPIO_ACTIVE_LOW>;
clock-names = "ref";
clocks = <&edp_refclk>;
ports {
#address-cells = <1>;
#size-cells = <0>;
port@1 {
reg = <1>;
bridge_in: endpoint {
remote-endpoint = <&dpi_out>;
};
};
port@2 {
reg = <2>;
bridge_out: endpoint {
remote-endpoint = <&panel_in>;
};
};
};
};
...@@ -58,6 +58,17 @@ config DRM_SII902X ...@@ -58,6 +58,17 @@ config DRM_SII902X
---help--- ---help---
Silicon Image sii902x bridge chip driver. Silicon Image sii902x bridge chip driver.
config DRM_TOSHIBA_TC358767
tristate "Toshiba TC358767 eDP bridge"
depends on OF
select DRM_KMS_HELPER
select REGMAP_I2C
select DRM_PANEL
---help---
Toshiba TC358767 eDP bridge chip driver.
source "drivers/gpu/drm/bridge/analogix/Kconfig" source "drivers/gpu/drm/bridge/analogix/Kconfig"
source "drivers/gpu/drm/bridge/adv7511/Kconfig"
endmenu endmenu
...@@ -6,4 +6,6 @@ obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o ...@@ -6,4 +6,6 @@ obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o
obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o obj-$(CONFIG_DRM_PARADE_PS8622) += parade-ps8622.o
obj-$(CONFIG_DRM_SII902X) += sii902x.o obj-$(CONFIG_DRM_SII902X) += sii902x.o
obj-$(CONFIG_DRM_TOSHIBA_TC358767) += tc358767.o
obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/ obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix/
obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511/
config DRM_I2C_ADV7511
tristate "AV7511 encoder"
depends on OF
select DRM_KMS_HELPER
select REGMAP_I2C
help
Support for the Analog Device ADV7511(W) and ADV7513 HDMI encoders.
config DRM_I2C_ADV7533
bool "ADV7533 encoder"
depends on DRM_I2C_ADV7511
select DRM_MIPI_DSI
default y
help
Support for the Analog Devices ADV7533 DSI to HDMI encoder.
adv7511-y := adv7511_drv.o
adv7511-$(CONFIG_DRM_I2C_ADV7533) += adv7533.o
obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o
...@@ -10,6 +10,11 @@ ...@@ -10,6 +10,11 @@
#define __DRM_I2C_ADV7511_H__ #define __DRM_I2C_ADV7511_H__
#include <linux/hdmi.h> #include <linux/hdmi.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_mipi_dsi.h>
#define ADV7511_REG_CHIP_REVISION 0x00 #define ADV7511_REG_CHIP_REVISION 0x00
#define ADV7511_REG_N0 0x01 #define ADV7511_REG_N0 0x01
...@@ -286,4 +291,102 @@ struct adv7511_video_config { ...@@ -286,4 +291,102 @@ struct adv7511_video_config {
struct hdmi_avi_infoframe avi_infoframe; struct hdmi_avi_infoframe avi_infoframe;
}; };
enum adv7511_type {
ADV7511,
ADV7533,
};
struct adv7511 {
struct i2c_client *i2c_main;
struct i2c_client *i2c_edid;
struct i2c_client *i2c_cec;
struct regmap *regmap;
struct regmap *regmap_cec;
enum drm_connector_status status;
bool powered;
struct drm_display_mode curr_mode;
unsigned int f_tmds;
unsigned int current_edid_segment;
uint8_t edid_buf[256];
bool edid_read;
wait_queue_head_t wq;
struct drm_bridge bridge;
struct drm_connector connector;
bool embedded_sync;
enum adv7511_sync_polarity vsync_polarity;
enum adv7511_sync_polarity hsync_polarity;
bool rgb;
struct edid *edid;
struct gpio_desc *gpio_pd;
/* ADV7533 DSI RX related params */
struct device_node *host_node;
struct mipi_dsi_device *dsi;
u8 num_dsi_lanes;
bool use_timing_gen;
enum adv7511_type type;
};
#ifdef CONFIG_DRM_I2C_ADV7533
void adv7533_dsi_power_on(struct adv7511 *adv);
void adv7533_dsi_power_off(struct adv7511 *adv);
void adv7533_mode_set(struct adv7511 *adv, struct drm_display_mode *mode);
int adv7533_patch_registers(struct adv7511 *adv);
void adv7533_uninit_cec(struct adv7511 *adv);
int adv7533_init_cec(struct adv7511 *adv);
int adv7533_attach_dsi(struct adv7511 *adv);
void adv7533_detach_dsi(struct adv7511 *adv);
int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv);
#else
static inline void adv7533_dsi_power_on(struct adv7511 *adv)
{
}
static inline void adv7533_dsi_power_off(struct adv7511 *adv)
{
}
static inline void adv7533_mode_set(struct adv7511 *adv,
struct drm_display_mode *mode)
{
}
static inline int adv7533_patch_registers(struct adv7511 *adv)
{
return -ENODEV;
}
static inline void adv7533_uninit_cec(struct adv7511 *adv)
{
}
static inline int adv7533_init_cec(struct adv7511 *adv)
{
return -ENODEV;
}
static inline int adv7533_attach_dsi(struct adv7511 *adv)
{
return -ENODEV;
}
static inline void adv7533_detach_dsi(struct adv7511 *adv)
{
}
static inline int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv)
{
return -ENODEV;
}
#endif
#endif /* __DRM_I2C_ADV7511_H__ */ #endif /* __DRM_I2C_ADV7511_H__ */
/*
* Copyright (c) 2016, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only 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.
*/
#include <linux/of_graph.h>
#include "adv7511.h"
static const struct reg_sequence adv7533_fixed_registers[] = {
{ 0x16, 0x20 },
{ 0x9a, 0xe0 },
{ 0xba, 0x70 },
{ 0xde, 0x82 },
{ 0xe4, 0x40 },
{ 0xe5, 0x80 },
};
static const struct reg_sequence adv7533_cec_fixed_registers[] = {
{ 0x15, 0xd0 },
{ 0x17, 0xd0 },
{ 0x24, 0x20 },
{ 0x57, 0x11 },
};
static const struct regmap_config adv7533_cec_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = 0xff,
.cache_type = REGCACHE_RBTREE,
};
static void adv7511_dsi_config_timing_gen(struct adv7511 *adv)
{
struct mipi_dsi_device *dsi = adv->dsi;
struct drm_display_mode *mode = &adv->curr_mode;
unsigned int hsw, hfp, hbp, vsw, vfp, vbp;
u8 clock_div_by_lanes[] = { 6, 4, 3 }; /* 2, 3, 4 lanes */
hsw = mode->hsync_end - mode->hsync_start;
hfp = mode->hsync_start - mode->hdisplay;
hbp = mode->htotal - mode->hsync_end;
vsw = mode->vsync_end - mode->vsync_start;
vfp = mode->vsync_start - mode->vdisplay;
vbp = mode->vtotal - mode->vsync_end;
/* set pixel clock divider mode */
regmap_write(adv->regmap_cec, 0x16,
clock_div_by_lanes[dsi->lanes - 2] << 3);
/* horizontal porch params */
regmap_write(adv->regmap_cec, 0x28, mode->htotal >> 4);
regmap_write(adv->regmap_cec, 0x29, (mode->htotal << 4) & 0xff);
regmap_write(adv->regmap_cec, 0x2a, hsw >> 4);
regmap_write(adv->regmap_cec, 0x2b, (hsw << 4) & 0xff);
regmap_write(adv->regmap_cec, 0x2c, hfp >> 4);
regmap_write(adv->regmap_cec, 0x2d, (hfp << 4) & 0xff);
regmap_write(adv->regmap_cec, 0x2e, hbp >> 4);
regmap_write(adv->regmap_cec, 0x2f, (hbp << 4) & 0xff);
/* vertical porch params */
regmap_write(adv->regmap_cec, 0x30, mode->vtotal >> 4);
regmap_write(adv->regmap_cec, 0x31, (mode->vtotal << 4) & 0xff);
regmap_write(adv->regmap_cec, 0x32, vsw >> 4);
regmap_write(adv->regmap_cec, 0x33, (vsw << 4) & 0xff);
regmap_write(adv->regmap_cec, 0x34, vfp >> 4);
regmap_write(adv->regmap_cec, 0x35, (vfp << 4) & 0xff);
regmap_write(adv->regmap_cec, 0x36, vbp >> 4);
regmap_write(adv->regmap_cec, 0x37, (vbp << 4) & 0xff);
}
void adv7533_dsi_power_on(struct adv7511 *adv)
{
struct mipi_dsi_device *dsi = adv->dsi;
if (adv->use_timing_gen)
adv7511_dsi_config_timing_gen(adv);
/* set number of dsi lanes */
regmap_write(adv->regmap_cec, 0x1c, dsi->lanes << 4);
if (adv->use_timing_gen) {
/* reset internal timing generator */
regmap_write(adv->regmap_cec, 0x27, 0xcb);
regmap_write(adv->regmap_cec, 0x27, 0x8b);
regmap_write(adv->regmap_cec, 0x27, 0xcb);
} else {
/* disable internal timing generator */
regmap_write(adv->regmap_cec, 0x27, 0x0b);
}
/* enable hdmi */
regmap_write(adv->regmap_cec, 0x03, 0x89);
/* disable test mode */
regmap_write(adv->regmap_cec, 0x55, 0x00);
regmap_register_patch(adv->regmap_cec, adv7533_cec_fixed_registers,
ARRAY_SIZE(adv7533_cec_fixed_registers));
}
void adv7533_dsi_power_off(struct adv7511 *adv)
{
/* disable hdmi */
regmap_write(adv->regmap_cec, 0x03, 0x0b);
/* disable internal timing generator */
regmap_write(adv->regmap_cec, 0x27, 0x0b);
}
void adv7533_mode_set(struct adv7511 *adv, struct drm_display_mode *mode)
{
struct mipi_dsi_device *dsi = adv->dsi;
int lanes, ret;
if (adv->num_dsi_lanes != 4)
return;
if (mode->clock > 80000)
lanes = 4;
else
lanes = 3;
if (lanes != dsi->lanes) {
mipi_dsi_detach(dsi);
dsi->lanes = lanes;
ret = mipi_dsi_attach(dsi);
if (ret)
dev_err(&dsi->dev, "failed to change host lanes\n");
}
}
int adv7533_patch_registers(struct adv7511 *adv)
{
return regmap_register_patch(adv->regmap,
adv7533_fixed_registers,
ARRAY_SIZE(adv7533_fixed_registers));
}
void adv7533_uninit_cec(struct adv7511 *adv)
{
i2c_unregister_device(adv->i2c_cec);
}
static const int cec_i2c_addr = 0x78;
int adv7533_init_cec(struct adv7511 *adv)
{
int ret;
adv->i2c_cec = i2c_new_dummy(adv->i2c_main->adapter, cec_i2c_addr >> 1);
if (!adv->i2c_cec)
return -ENOMEM;
adv->regmap_cec = devm_regmap_init_i2c(adv->i2c_cec,
&adv7533_cec_regmap_config);
if (IS_ERR(adv->regmap_cec)) {
ret = PTR_ERR(adv->regmap_cec);
goto err;
}
ret = regmap_register_patch(adv->regmap_cec,
adv7533_cec_fixed_registers,
ARRAY_SIZE(adv7533_cec_fixed_registers));
if (ret)
goto err;
return 0;
err:
adv7533_uninit_cec(adv);
return ret;
}
int adv7533_attach_dsi(struct adv7511 *adv)
{
struct device *dev = &adv->i2c_main->dev;
struct mipi_dsi_host *host;
struct mipi_dsi_device *dsi;
int ret = 0;
const struct mipi_dsi_device_info info = { .type = "adv7533",
.channel = 0,
.node = NULL,
};
host = of_find_mipi_dsi_host_by_node(adv->host_node);
if (!host) {
dev_err(dev, "failed to find dsi host\n");
return -EPROBE_DEFER;
}
dsi = mipi_dsi_device_register_full(host, &info);
if (IS_ERR(dsi)) {
dev_err(dev, "failed to create dsi device\n");
ret = PTR_ERR(dsi);
goto err_dsi_device;
}
adv->dsi = dsi;
dsi->lanes = adv->num_dsi_lanes;
dsi->format = MIPI_DSI_FMT_RGB888;
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
MIPI_DSI_MODE_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE;
ret = mipi_dsi_attach(dsi);
if (ret < 0) {
dev_err(dev, "failed to attach dsi to host\n");
goto err_dsi_attach;
}
return 0;
err_dsi_attach:
mipi_dsi_device_unregister(dsi);
err_dsi_device:
return ret;
}
void adv7533_detach_dsi(struct adv7511 *adv)
{
mipi_dsi_detach(adv->dsi);
mipi_dsi_device_unregister(adv->dsi);
}
int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv)
{
u32 num_lanes;
struct device_node *endpoint;
of_property_read_u32(np, "adi,dsi-lanes", &num_lanes);
if (num_lanes < 1 || num_lanes > 4)
return -EINVAL;
adv->num_dsi_lanes = num_lanes;
endpoint = of_graph_get_next_endpoint(np, NULL);
if (!endpoint)
return -ENODEV;
adv->host_node = of_graph_get_remote_port_parent(endpoint);
if (!adv->host_node) {
of_node_put(endpoint);
return -ENODEV;
}
of_node_put(endpoint);
of_node_put(adv->host_node);
adv->use_timing_gen = !of_property_read_bool(np,
"adi,disable-timing-generator");
/* TODO: Check if these need to be parsed by DT or not */
adv->rgb = true;
adv->embedded_sync = false;
return 0;
}
This diff is collapsed.
menu "I2C encoder or helper chips" menu "I2C encoder or helper chips"
depends on DRM && DRM_KMS_HELPER && I2C depends on DRM && DRM_KMS_HELPER && I2C
config DRM_I2C_ADV7511
tristate "AV7511 encoder"
select REGMAP_I2C
help
Support for the Analog Device ADV7511(W) and ADV7513 HDMI encoders.
config DRM_I2C_CH7006 config DRM_I2C_CH7006
tristate "Chrontel ch7006 TV encoder" tristate "Chrontel ch7006 TV encoder"
default m if DRM_NOUVEAU default m if DRM_NOUVEAU
......
ccflags-y := -Iinclude/drm ccflags-y := -Iinclude/drm
obj-$(CONFIG_DRM_I2C_ADV7511) += adv7511.o
ch7006-y := ch7006_drv.o ch7006_mode.o ch7006-y := ch7006_drv.o ch7006_mode.o
obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o obj-$(CONFIG_DRM_I2C_CH7006) += ch7006.o
......
...@@ -7,8 +7,8 @@ rcar-du-drm-y := rcar_du_crtc.o \ ...@@ -7,8 +7,8 @@ rcar-du-drm-y := rcar_du_crtc.o \
rcar_du_plane.o \ rcar_du_plane.o \
rcar_du_vgacon.o rcar_du_vgacon.o
rcar-du-drm-$(CONFIG_DRM_RCAR_HDMI) += rcar_du_hdmicon.o \ rcar-du-drm-$(CONFIG_DRM_RCAR_HDMI) += rcar_du_hdmienc.o
rcar_du_hdmienc.o
rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_lvdsenc.o rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_lvdsenc.o
rcar-du-drm-$(CONFIG_DRM_RCAR_VSP) += rcar_du_vsp.o rcar-du-drm-$(CONFIG_DRM_RCAR_VSP) += rcar_du_vsp.o
......
...@@ -19,7 +19,6 @@ ...@@ -19,7 +19,6 @@
#include "rcar_du_drv.h" #include "rcar_du_drv.h"
#include "rcar_du_encoder.h" #include "rcar_du_encoder.h"
#include "rcar_du_hdmicon.h"
#include "rcar_du_hdmienc.h" #include "rcar_du_hdmienc.h"
#include "rcar_du_kms.h" #include "rcar_du_kms.h"
#include "rcar_du_lvdscon.h" #include "rcar_du_lvdscon.h"
...@@ -174,7 +173,7 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu, ...@@ -174,7 +173,7 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
break; break;
case DRM_MODE_ENCODER_TMDS: case DRM_MODE_ENCODER_TMDS:
ret = rcar_du_hdmi_connector_init(rcdu, renc); /* connector managed by the bridge driver */
break; break;
default: default:
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
#define __RCAR_DU_ENCODER_H__ #define __RCAR_DU_ENCODER_H__
#include <drm/drm_crtc.h> #include <drm/drm_crtc.h>
#include <drm/drm_encoder_slave.h>
struct rcar_du_device; struct rcar_du_device;
struct rcar_du_hdmienc; struct rcar_du_hdmienc;
...@@ -30,16 +29,16 @@ enum rcar_du_encoder_type { ...@@ -30,16 +29,16 @@ enum rcar_du_encoder_type {
}; };
struct rcar_du_encoder { struct rcar_du_encoder {
struct drm_encoder_slave slave; struct drm_encoder base;
enum rcar_du_output output; enum rcar_du_output output;
struct rcar_du_hdmienc *hdmi; struct rcar_du_hdmienc *hdmi;
struct rcar_du_lvdsenc *lvds; struct rcar_du_lvdsenc *lvds;
}; };
#define to_rcar_encoder(e) \ #define to_rcar_encoder(e) \
container_of(e, struct rcar_du_encoder, slave.base) container_of(e, struct rcar_du_encoder, base)
#define rcar_encoder_to_drm_encoder(e) (&(e)->slave.base) #define rcar_encoder_to_drm_encoder(e) (&(e)->base)
struct rcar_du_connector { struct rcar_du_connector {
struct drm_connector connector; struct drm_connector connector;
......
/*
* R-Car Display Unit HDMI Connector
*
* Copyright (C) 2014 Renesas Electronics 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 <drm/drmP.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_encoder_slave.h>
#include "rcar_du_drv.h"
#include "rcar_du_encoder.h"
#include "rcar_du_hdmicon.h"
#include "rcar_du_kms.h"
#define to_slave_funcs(e) (to_rcar_encoder(e)->slave.slave_funcs)
static int rcar_du_hdmi_connector_get_modes(struct drm_connector *connector)
{
struct rcar_du_connector *con = to_rcar_connector(connector);
struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(con->encoder);
const struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder);
if (sfuncs->get_modes == NULL)
return 0;
return sfuncs->get_modes(encoder, connector);
}
static int rcar_du_hdmi_connector_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode)
{
struct rcar_du_connector *con = to_rcar_connector(connector);
struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(con->encoder);
const struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder);
if (sfuncs->mode_valid == NULL)
return MODE_OK;
return sfuncs->mode_valid(encoder, mode);
}
static const struct drm_connector_helper_funcs connector_helper_funcs = {
.get_modes = rcar_du_hdmi_connector_get_modes,
.mode_valid = rcar_du_hdmi_connector_mode_valid,
};
static enum drm_connector_status
rcar_du_hdmi_connector_detect(struct drm_connector *connector, bool force)
{
struct rcar_du_connector *con = to_rcar_connector(connector);
struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(con->encoder);
const struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder);
if (sfuncs->detect == NULL)
return connector_status_unknown;
return sfuncs->detect(encoder, connector);
}
static const struct drm_connector_funcs connector_funcs = {
.dpms = drm_atomic_helper_connector_dpms,
.reset = drm_atomic_helper_connector_reset,
.detect = rcar_du_hdmi_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
int rcar_du_hdmi_connector_init(struct rcar_du_device *rcdu,
struct rcar_du_encoder *renc)
{
struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc);
struct rcar_du_connector *rcon;
struct drm_connector *connector;
int ret;
rcon = devm_kzalloc(rcdu->dev, sizeof(*rcon), GFP_KERNEL);
if (rcon == NULL)
return -ENOMEM;
connector = &rcon->connector;
connector->display_info.width_mm = 0;
connector->display_info.height_mm = 0;
connector->interlace_allowed = true;
connector->polled = DRM_CONNECTOR_POLL_HPD;
ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs,
DRM_MODE_CONNECTOR_HDMIA);
if (ret < 0)
return ret;
drm_connector_helper_add(connector, &connector_helper_funcs);
connector->dpms = DRM_MODE_DPMS_OFF;
drm_object_property_set_value(&connector->base,
rcdu->ddev->mode_config.dpms_property, DRM_MODE_DPMS_OFF);
ret = drm_mode_connector_attach_encoder(connector, encoder);
if (ret < 0)
return ret;
rcon->encoder = renc;
return 0;
}
/*
* R-Car Display Unit HDMI Connector
*
* Copyright (C) 2014 Renesas Electronics 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.
*/
#ifndef __RCAR_DU_HDMICON_H__
#define __RCAR_DU_HDMICON_H__
struct rcar_du_device;
struct rcar_du_encoder;
#if IS_ENABLED(CONFIG_DRM_RCAR_HDMI)
int rcar_du_hdmi_connector_init(struct rcar_du_device *rcdu,
struct rcar_du_encoder *renc);
#else
static inline int rcar_du_hdmi_connector_init(struct rcar_du_device *rcdu,
struct rcar_du_encoder *renc)
{
return -ENOSYS;
}
#endif
#endif /* __RCAR_DU_HDMICON_H__ */
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
#include <drm/drmP.h> #include <drm/drmP.h>
#include <drm/drm_crtc.h> #include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h> #include <drm/drm_crtc_helper.h>
#include <drm/drm_encoder_slave.h>
#include "rcar_du_drv.h" #include "rcar_du_drv.h"
#include "rcar_du_encoder.h" #include "rcar_du_encoder.h"
...@@ -25,20 +24,14 @@ ...@@ -25,20 +24,14 @@
struct rcar_du_hdmienc { struct rcar_du_hdmienc {
struct rcar_du_encoder *renc; struct rcar_du_encoder *renc;
struct device *dev;
bool enabled; bool enabled;
}; };
#define to_rcar_hdmienc(e) (to_rcar_encoder(e)->hdmi) #define to_rcar_hdmienc(e) (to_rcar_encoder(e)->hdmi)
#define to_slave_funcs(e) (to_rcar_encoder(e)->slave.slave_funcs)
static void rcar_du_hdmienc_disable(struct drm_encoder *encoder) static void rcar_du_hdmienc_disable(struct drm_encoder *encoder)
{ {
struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder); struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
const struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder);
if (sfuncs->dpms)
sfuncs->dpms(encoder, DRM_MODE_DPMS_OFF);
if (hdmienc->renc->lvds) if (hdmienc->renc->lvds)
rcar_du_lvdsenc_enable(hdmienc->renc->lvds, encoder->crtc, rcar_du_lvdsenc_enable(hdmienc->renc->lvds, encoder->crtc,
...@@ -50,15 +43,11 @@ static void rcar_du_hdmienc_disable(struct drm_encoder *encoder) ...@@ -50,15 +43,11 @@ static void rcar_du_hdmienc_disable(struct drm_encoder *encoder)
static void rcar_du_hdmienc_enable(struct drm_encoder *encoder) static void rcar_du_hdmienc_enable(struct drm_encoder *encoder)
{ {
struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder); struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
const struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder);
if (hdmienc->renc->lvds) if (hdmienc->renc->lvds)
rcar_du_lvdsenc_enable(hdmienc->renc->lvds, encoder->crtc, rcar_du_lvdsenc_enable(hdmienc->renc->lvds, encoder->crtc,
true); true);
if (sfuncs->dpms)
sfuncs->dpms(encoder, DRM_MODE_DPMS_ON);
hdmienc->enabled = true; hdmienc->enabled = true;
} }
...@@ -67,29 +56,21 @@ static int rcar_du_hdmienc_atomic_check(struct drm_encoder *encoder, ...@@ -67,29 +56,21 @@ static int rcar_du_hdmienc_atomic_check(struct drm_encoder *encoder,
struct drm_connector_state *conn_state) struct drm_connector_state *conn_state)
{ {
struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder); struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
const struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder);
struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
const struct drm_display_mode *mode = &crtc_state->mode;
if (hdmienc->renc->lvds) if (hdmienc->renc->lvds)
rcar_du_lvdsenc_atomic_check(hdmienc->renc->lvds, rcar_du_lvdsenc_atomic_check(hdmienc->renc->lvds,
adjusted_mode); adjusted_mode);
if (sfuncs->mode_fixup == NULL) return 0;
return 0;
return sfuncs->mode_fixup(encoder, mode, adjusted_mode) ? 0 : -EINVAL;
} }
static void rcar_du_hdmienc_mode_set(struct drm_encoder *encoder, static void rcar_du_hdmienc_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode, struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode) struct drm_display_mode *adjusted_mode)
{ {
struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder); struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
const struct drm_encoder_slave_funcs *sfuncs = to_slave_funcs(encoder);
if (sfuncs->mode_set)
sfuncs->mode_set(encoder, mode, adjusted_mode);
rcar_du_crtc_route_output(encoder->crtc, hdmienc->renc->output); rcar_du_crtc_route_output(encoder->crtc, hdmienc->renc->output);
} }
...@@ -109,7 +90,6 @@ static void rcar_du_hdmienc_cleanup(struct drm_encoder *encoder) ...@@ -109,7 +90,6 @@ static void rcar_du_hdmienc_cleanup(struct drm_encoder *encoder)
rcar_du_hdmienc_disable(encoder); rcar_du_hdmienc_disable(encoder);
drm_encoder_cleanup(encoder); drm_encoder_cleanup(encoder);
put_device(hdmienc->dev);
} }
static const struct drm_encoder_funcs encoder_funcs = { static const struct drm_encoder_funcs encoder_funcs = {
...@@ -120,8 +100,7 @@ int rcar_du_hdmienc_init(struct rcar_du_device *rcdu, ...@@ -120,8 +100,7 @@ int rcar_du_hdmienc_init(struct rcar_du_device *rcdu,
struct rcar_du_encoder *renc, struct device_node *np) struct rcar_du_encoder *renc, struct device_node *np)
{ {
struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc); struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc);
struct drm_i2c_encoder_driver *driver; struct drm_bridge *bridge;
struct i2c_client *i2c_slave;
struct rcar_du_hdmienc *hdmienc; struct rcar_du_hdmienc *hdmienc;
int ret; int ret;
...@@ -129,44 +108,29 @@ int rcar_du_hdmienc_init(struct rcar_du_device *rcdu, ...@@ -129,44 +108,29 @@ int rcar_du_hdmienc_init(struct rcar_du_device *rcdu,
if (hdmienc == NULL) if (hdmienc == NULL)
return -ENOMEM; return -ENOMEM;
/* Locate the slave I2C device and driver. */ /* Locate drm bridge from the hdmi encoder DT node */
i2c_slave = of_find_i2c_device_by_node(np); bridge = of_drm_find_bridge(np);
if (!i2c_slave || !i2c_get_clientdata(i2c_slave)) { if (!bridge)
dev_dbg(rcdu->dev,
"can't get I2C slave for %s, deferring probe\n",
of_node_full_name(np));
return -EPROBE_DEFER; return -EPROBE_DEFER;
}
hdmienc->dev = &i2c_slave->dev;
if (hdmienc->dev->driver == NULL) {
dev_dbg(rcdu->dev,
"I2C slave %s not probed yet, deferring probe\n",
dev_name(hdmienc->dev));
ret = -EPROBE_DEFER;
goto error;
}
/* Initialize the slave encoder. */
driver = to_drm_i2c_encoder_driver(to_i2c_driver(hdmienc->dev->driver));
ret = driver->encoder_init(i2c_slave, rcdu->ddev, &renc->slave);
if (ret < 0)
goto error;
ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs, ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs,
DRM_MODE_ENCODER_TMDS, NULL); DRM_MODE_ENCODER_TMDS, NULL);
if (ret < 0) if (ret < 0)
goto error; return ret;
drm_encoder_helper_add(encoder, &encoder_helper_funcs); drm_encoder_helper_add(encoder, &encoder_helper_funcs);
renc->hdmi = hdmienc; renc->hdmi = hdmienc;
hdmienc->renc = renc; hdmienc->renc = renc;
return 0; /* Link drm_bridge to encoder */
bridge->encoder = encoder;
ret = drm_bridge_attach(rcdu->ddev, bridge);
if (ret) {
drm_encoder_cleanup(encoder);
return ret;
}
error: return 0;
put_device(hdmienc->dev);
return ret;
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment