Merge tag 'drm-next-2019-07-16' of git://anongit.freedesktop.org/drm/drm
Pull drm updates from Dave Airlie: "The biggest thing in this is the AMD Navi GPU support, this again contains a bunch of header files that are large. These are the new AMD RX5700 GPUs that just recently became available. New drivers: - ST-Ericsson MCDE driver - Ingenic JZ47xx SoC UAPI change: - HDR source metadata property Core: - HDR inforframes and EDID parsing - drm hdmi infoframe unpacking - remove prime sg_table caching into dma-buf - New gem vram helpers to reduce driver code - Lots of drmP.h removal - reservation fencing fix - documentation updates - drm_fb_helper_connector removed - mode name command handler rewrite fbcon: - Remove the fbcon notifiers ttm: - forward progress fixes dma-buf: - make mmap call optional - debugfs refcount fixes - dma-fence free with pending signals fix - each dma-buf gets an inode Panels: - Lots of additional panel bindings amdgpu: - initial navi10 support - avoid hw reset - HDR metadata support - new thermal sensors for vega asics - RAS fixes - use HMM rather than MMU notifier - xgmi topology via kfd - SR-IOV fixes - driver reload fixes - DC use a core bpc attribute - Aux fixes for DC - Bandwidth calc updates for DC - Clock handling refactor - kfd VEGAM support vmwgfx: - Coherent memory support changes i915: - HDR Support - HDMI i2c link - Icelake multi-segmented gamma support - GuC firmware update - Mule Creek Canyon PCH support for EHL - EHL platform updtes - move i915.alpha_support to i915.force_probe - runtime PM refactoring - VBT parsing refactoring - DSI fixes - struct mutex dependency reduction - GEM code reorg mali-dp: - Komeda driver features msm: - dsi vs EPROBE_DEFER fixes - msm8998 snapdragon 835 support - a540 gpu support - mdp5 and dpu interconnect support exynos: - drmP.h removal tegra: - misc fixes tda998x: - audio support improvements - pixel repeated mode support - quantisation range handling corrections - HDMI vendor info fix armada: - interlace support fix - overlay/video plane register handling refactor - add gamma support rockchip: - RX3328 support panfrost: - expose perf counters via hidden ioctls vkms: - enumerate CRC sources list ast: - rework BO handling mgag200: - rework BO handling dw-hdmi: - suspend/resume support rcar-du: - R8A774A1 Soc Support - LVDS dual-link mode support - Additional formats - Misc fixes omapdrm: - DSI command mode display support stm - fb modifier support - runtime PM support sun4i: - use vmap ops vc4: - binner bo binding rework v3d: - compute shader support - resync/sync fixes - job management refactoring lima: - NULL pointer in irq handler fix - scheduler default timeout virtio: - fence seqno support - trace events bochs: - misc fixes tc458767: - IRQ/HDP handling sii902x: - HDMI audio support atmel-hlcdc: - misc fixes meson: - zpos support" * tag 'drm-next-2019-07-16' of git://anongit.freedesktop.org/drm/drm: (1815 commits) Revert "Merge branch 'vmwgfx-next' of git://people.freedesktop.org/~thomash/linux into drm-next" Revert "mm: adjust apply_to_pfn_range interface for dropped token." mm: adjust apply_to_pfn_range interface for dropped token. drm/amdgpu/navi10: add uclk activity sensor drm/amdgpu: properly guard the generic discovery code drm/amdgpu: add missing documentation on new module parameters drm/amdgpu: don't invalidate caches in RELEASE_MEM, only do the writeback drm/amd/display: avoid 64-bit division drm/amdgpu/psp11: simplify the ucode register logic drm/amdgpu: properly guard DC support in navi code drm/amd/powerplay: vega20: fix uninitialized variable use drm/amd/display: dcn20: include linux/delay.h amdgpu: make pmu support optional drm/amd/powerplay: Zero initialize current_rpm in vega20_get_fan_speed_percent drm/amd/powerplay: Zero initialize freq in smu_v11_0_get_current_clk_freq drm/amd/powerplay: Use memset to initialize metrics structs drm/amdgpu/mes10.1: Fix header guard drm/amd/powerplay: add temperature sensor support for navi10 drm/amdgpu: fix scheduler timeout calc drm/amdgpu: Prepare for hmm_range_register API change (v2) ...
This commit is contained in:
@@ -5,21 +5,21 @@
|
||||
* Copyright 2012 Analog Devices Inc.
|
||||
*/
|
||||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/clk.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <media/cec.h>
|
||||
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_edid.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
|
||||
#include <media/cec.h>
|
||||
|
||||
#include "adv7511.h"
|
||||
|
||||
/* ADI recommended values for proper operation. */
|
||||
|
@@ -7,23 +7,22 @@
|
||||
*/
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/of_irq.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_dp_helper.h>
|
||||
#include <drm/drm_edid.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
|
||||
#include "analogix-anx78xx.h"
|
||||
|
@@ -6,26 +6,25 @@
|
||||
* Author: Jingoo Han <jg1.han@samsung.com>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/component.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/component.h>
|
||||
#include <linux/phy/phy.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <drm/bridge/analogix_dp.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_device.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
|
||||
#include "analogix_dp_core.h"
|
||||
#include "analogix_dp_reg.h"
|
||||
@@ -111,7 +110,7 @@ EXPORT_SYMBOL_GPL(analogix_dp_psr_enabled);
|
||||
|
||||
int analogix_dp_enable_psr(struct analogix_dp_device *dp)
|
||||
{
|
||||
struct edp_vsc_psr psr_vsc;
|
||||
struct dp_sdp psr_vsc;
|
||||
|
||||
if (!dp->psr_enable)
|
||||
return 0;
|
||||
@@ -123,8 +122,8 @@ int analogix_dp_enable_psr(struct analogix_dp_device *dp)
|
||||
psr_vsc.sdp_header.HB2 = 0x2;
|
||||
psr_vsc.sdp_header.HB3 = 0x8;
|
||||
|
||||
psr_vsc.DB0 = 0;
|
||||
psr_vsc.DB1 = EDP_VSC_PSR_STATE_ACTIVE | EDP_VSC_PSR_CRC_VALUES_VALID;
|
||||
psr_vsc.db[0] = 0;
|
||||
psr_vsc.db[1] = EDP_VSC_PSR_STATE_ACTIVE | EDP_VSC_PSR_CRC_VALUES_VALID;
|
||||
|
||||
return analogix_dp_send_psr_spd(dp, &psr_vsc, true);
|
||||
}
|
||||
@@ -132,7 +131,7 @@ EXPORT_SYMBOL_GPL(analogix_dp_enable_psr);
|
||||
|
||||
int analogix_dp_disable_psr(struct analogix_dp_device *dp)
|
||||
{
|
||||
struct edp_vsc_psr psr_vsc;
|
||||
struct dp_sdp psr_vsc;
|
||||
int ret;
|
||||
|
||||
if (!dp->psr_enable)
|
||||
@@ -145,8 +144,8 @@ int analogix_dp_disable_psr(struct analogix_dp_device *dp)
|
||||
psr_vsc.sdp_header.HB2 = 0x2;
|
||||
psr_vsc.sdp_header.HB3 = 0x8;
|
||||
|
||||
psr_vsc.DB0 = 0;
|
||||
psr_vsc.DB1 = 0;
|
||||
psr_vsc.db[0] = 0;
|
||||
psr_vsc.db[1] = 0;
|
||||
|
||||
ret = drm_dp_dpcd_writeb(&dp->aux, DP_SET_POWER, DP_SET_POWER_D0);
|
||||
if (ret != 1) {
|
||||
@@ -1407,8 +1406,6 @@ static void analogix_dp_bridge_mode_set(struct drm_bridge *bridge,
|
||||
video->color_space = COLOR_YCBCR444;
|
||||
else if (display_info->color_formats & DRM_COLOR_FORMAT_YCRCB422)
|
||||
video->color_space = COLOR_YCBCR422;
|
||||
else if (display_info->color_formats & DRM_COLOR_FORMAT_RGB444)
|
||||
video->color_space = COLOR_RGB;
|
||||
else
|
||||
video->color_space = COLOR_RGB;
|
||||
|
||||
@@ -1581,12 +1578,18 @@ analogix_dp_bind(struct device *dev, struct drm_device *drm_dev,
|
||||
|
||||
dp->force_hpd = of_property_read_bool(dev->of_node, "force-hpd");
|
||||
|
||||
dp->hpd_gpio = of_get_named_gpio(dev->of_node, "hpd-gpios", 0);
|
||||
if (!gpio_is_valid(dp->hpd_gpio))
|
||||
dp->hpd_gpio = of_get_named_gpio(dev->of_node,
|
||||
"samsung,hpd-gpio", 0);
|
||||
/* Try two different names */
|
||||
dp->hpd_gpiod = devm_gpiod_get_optional(dev, "hpd", GPIOD_IN);
|
||||
if (!dp->hpd_gpiod)
|
||||
dp->hpd_gpiod = devm_gpiod_get_optional(dev, "samsung,hpd",
|
||||
GPIOD_IN);
|
||||
if (IS_ERR(dp->hpd_gpiod)) {
|
||||
dev_err(dev, "error getting HDP GPIO: %ld\n",
|
||||
PTR_ERR(dp->hpd_gpiod));
|
||||
return ERR_CAST(dp->hpd_gpiod);
|
||||
}
|
||||
|
||||
if (gpio_is_valid(dp->hpd_gpio)) {
|
||||
if (dp->hpd_gpiod) {
|
||||
/*
|
||||
* Set up the hotplug GPIO from the device tree as an interrupt.
|
||||
* Simply specifying a different interrupt in the device tree
|
||||
@@ -1594,16 +1597,9 @@ analogix_dp_bind(struct device *dev, struct drm_device *drm_dev,
|
||||
* using a GPIO. We also need the actual GPIO specifier so
|
||||
* that we can get the current state of the GPIO.
|
||||
*/
|
||||
ret = devm_gpio_request_one(&pdev->dev, dp->hpd_gpio, GPIOF_IN,
|
||||
"hpd_gpio");
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to get hpd gpio\n");
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
dp->irq = gpio_to_irq(dp->hpd_gpio);
|
||||
dp->irq = gpiod_to_irq(dp->hpd_gpiod);
|
||||
irq_flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
|
||||
} else {
|
||||
dp->hpd_gpio = -ENODEV;
|
||||
dp->irq = platform_get_irq(pdev, 0);
|
||||
irq_flags = 0;
|
||||
}
|
||||
|
@@ -34,6 +34,8 @@
|
||||
#define DPCD_VOLTAGE_SWING_SET(x) (((x) & 0x3) << 0)
|
||||
#define DPCD_VOLTAGE_SWING_GET(x) (((x) >> 0) & 0x3)
|
||||
|
||||
struct gpio_desc;
|
||||
|
||||
enum link_lane_count_type {
|
||||
LANE_COUNT1 = 1,
|
||||
LANE_COUNT2 = 2,
|
||||
@@ -167,7 +169,7 @@ struct analogix_dp_device {
|
||||
struct link_train link_train;
|
||||
struct phy *phy;
|
||||
int dpms_mode;
|
||||
int hpd_gpio;
|
||||
struct gpio_desc *hpd_gpiod;
|
||||
bool force_hpd;
|
||||
bool psr_enable;
|
||||
bool fast_train_enable;
|
||||
@@ -250,7 +252,7 @@ void analogix_dp_enable_scrambling(struct analogix_dp_device *dp);
|
||||
void analogix_dp_disable_scrambling(struct analogix_dp_device *dp);
|
||||
void analogix_dp_enable_psr_crc(struct analogix_dp_device *dp);
|
||||
int analogix_dp_send_psr_spd(struct analogix_dp_device *dp,
|
||||
struct edp_vsc_psr *vsc, bool blocking);
|
||||
struct dp_sdp *vsc, bool blocking);
|
||||
ssize_t analogix_dp_transfer(struct analogix_dp_device *dp,
|
||||
struct drm_dp_aux_msg *msg);
|
||||
|
||||
|
@@ -8,7 +8,7 @@
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/iopoll.h>
|
||||
|
||||
@@ -393,7 +393,7 @@ void analogix_dp_clear_hotplug_interrupts(struct analogix_dp_device *dp)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
if (gpio_is_valid(dp->hpd_gpio))
|
||||
if (dp->hpd_gpiod)
|
||||
return;
|
||||
|
||||
reg = HOTPLUG_CHG | HPD_LOST | PLUG;
|
||||
@@ -407,7 +407,7 @@ void analogix_dp_init_hpd(struct analogix_dp_device *dp)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
if (gpio_is_valid(dp->hpd_gpio))
|
||||
if (dp->hpd_gpiod)
|
||||
return;
|
||||
|
||||
analogix_dp_clear_hotplug_interrupts(dp);
|
||||
@@ -430,8 +430,8 @@ enum dp_irq_type analogix_dp_get_irq_type(struct analogix_dp_device *dp)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
if (gpio_is_valid(dp->hpd_gpio)) {
|
||||
reg = gpio_get_value(dp->hpd_gpio);
|
||||
if (dp->hpd_gpiod) {
|
||||
reg = gpiod_get_value(dp->hpd_gpiod);
|
||||
if (reg)
|
||||
return DP_IRQ_TYPE_HP_CABLE_IN;
|
||||
else
|
||||
@@ -503,8 +503,8 @@ int analogix_dp_get_plug_in_status(struct analogix_dp_device *dp)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
if (gpio_is_valid(dp->hpd_gpio)) {
|
||||
if (gpio_get_value(dp->hpd_gpio))
|
||||
if (dp->hpd_gpiod) {
|
||||
if (gpiod_get_value(dp->hpd_gpiod))
|
||||
return 0;
|
||||
} else {
|
||||
reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_3);
|
||||
@@ -1037,7 +1037,7 @@ static ssize_t analogix_dp_get_psr_status(struct analogix_dp_device *dp)
|
||||
}
|
||||
|
||||
int analogix_dp_send_psr_spd(struct analogix_dp_device *dp,
|
||||
struct edp_vsc_psr *vsc, bool blocking)
|
||||
struct dp_sdp *vsc, bool blocking)
|
||||
{
|
||||
unsigned int val;
|
||||
int ret;
|
||||
@@ -1065,8 +1065,8 @@ int analogix_dp_send_psr_spd(struct analogix_dp_device *dp,
|
||||
writel(0x5D, dp->reg_base + ANALOGIX_DP_SPD_PB3);
|
||||
|
||||
/* configure DB0 / DB1 values */
|
||||
writel(vsc->DB0, dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB0);
|
||||
writel(vsc->DB1, dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB1);
|
||||
writel(vsc->db[0], dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB0);
|
||||
writel(vsc->db[1], dp->reg_base + ANALOGIX_DP_VSC_SHADOW_DB1);
|
||||
|
||||
/* set reuse spd inforframe */
|
||||
val = readl(dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3);
|
||||
@@ -1088,8 +1088,8 @@ int analogix_dp_send_psr_spd(struct analogix_dp_device *dp,
|
||||
|
||||
ret = readx_poll_timeout(analogix_dp_get_psr_status, dp, psr_status,
|
||||
psr_status >= 0 &&
|
||||
((vsc->DB1 && psr_status == DP_PSR_SINK_ACTIVE_RFB) ||
|
||||
(!vsc->DB1 && psr_status == DP_PSR_SINK_INACTIVE)), 1500,
|
||||
((vsc->db[1] && psr_status == DP_PSR_SINK_ACTIVE_RFB) ||
|
||||
(!vsc->db[1] && psr_status == DP_PSR_SINK_INACTIVE)), 1500,
|
||||
DP_TIMEOUT_PSR_LOOP_MS * 1000);
|
||||
if (ret) {
|
||||
dev_warn(dp->dev, "Failed to apply PSR %d\n", ret);
|
||||
|
@@ -11,9 +11,9 @@
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
|
||||
struct dumb_vga {
|
||||
|
@@ -3,13 +3,15 @@
|
||||
* Copyright (C) 2016 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
|
||||
*/
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <drm/drm_bridge.h>
|
||||
#include <drm/drm_panel.h>
|
||||
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/of_graph.h>
|
||||
|
||||
struct lvds_encoder {
|
||||
struct drm_bridge bridge;
|
||||
struct drm_bridge *panel_bridge;
|
||||
|
@@ -23,11 +23,12 @@
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_edid.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
#include <drm/drmP.h>
|
||||
|
||||
#define EDID_EXT_BLOCK_CNT 0x7E
|
||||
|
||||
|
@@ -12,13 +12,14 @@
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_gpio.h>
|
||||
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_edid.h>
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
#include <drm/drmP.h>
|
||||
|
||||
#define PTN3460_EDID_ADDR 0x0
|
||||
#define PTN3460_EDID_EMULATION_ADDR 0x84
|
||||
|
@@ -4,14 +4,13 @@
|
||||
* Copyright (C) 2017 Broadcom
|
||||
*/
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_connector.h>
|
||||
#include <drm/drm_encoder.h>
|
||||
#include <drm/drm_modeset_helper_vtables.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
|
||||
struct panel_bridge {
|
||||
struct drm_bridge bridge;
|
||||
|
@@ -16,12 +16,13 @@
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/pm.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
#include <drm/drmP.h>
|
||||
|
||||
/* Brightness scale on the Parade chip */
|
||||
#define PS8622_MAX_BRIGHTNESS 0xff
|
||||
|
@@ -17,12 +17,16 @@
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/clk.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_drv.h>
|
||||
#include <drm/drm_edid.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
|
||||
#include <sound/hdmi-codec.h>
|
||||
|
||||
#define SII902X_TPI_VIDEO_DATA 0x0
|
||||
|
||||
#define SII902X_TPI_PIXEL_REPETITION 0x8
|
||||
@@ -64,6 +68,77 @@
|
||||
#define SII902X_AVI_POWER_STATE_MSK GENMASK(1, 0)
|
||||
#define SII902X_AVI_POWER_STATE_D(l) ((l) & SII902X_AVI_POWER_STATE_MSK)
|
||||
|
||||
/* Audio */
|
||||
#define SII902X_TPI_I2S_ENABLE_MAPPING_REG 0x1f
|
||||
#define SII902X_TPI_I2S_CONFIG_FIFO0 (0 << 0)
|
||||
#define SII902X_TPI_I2S_CONFIG_FIFO1 (1 << 0)
|
||||
#define SII902X_TPI_I2S_CONFIG_FIFO2 (2 << 0)
|
||||
#define SII902X_TPI_I2S_CONFIG_FIFO3 (3 << 0)
|
||||
#define SII902X_TPI_I2S_LEFT_RIGHT_SWAP (1 << 2)
|
||||
#define SII902X_TPI_I2S_AUTO_DOWNSAMPLE (1 << 3)
|
||||
#define SII902X_TPI_I2S_SELECT_SD0 (0 << 4)
|
||||
#define SII902X_TPI_I2S_SELECT_SD1 (1 << 4)
|
||||
#define SII902X_TPI_I2S_SELECT_SD2 (2 << 4)
|
||||
#define SII902X_TPI_I2S_SELECT_SD3 (3 << 4)
|
||||
#define SII902X_TPI_I2S_FIFO_ENABLE (1 << 7)
|
||||
|
||||
#define SII902X_TPI_I2S_INPUT_CONFIG_REG 0x20
|
||||
#define SII902X_TPI_I2S_FIRST_BIT_SHIFT_YES (0 << 0)
|
||||
#define SII902X_TPI_I2S_FIRST_BIT_SHIFT_NO (1 << 0)
|
||||
#define SII902X_TPI_I2S_SD_DIRECTION_MSB_FIRST (0 << 1)
|
||||
#define SII902X_TPI_I2S_SD_DIRECTION_LSB_FIRST (1 << 1)
|
||||
#define SII902X_TPI_I2S_SD_JUSTIFY_LEFT (0 << 2)
|
||||
#define SII902X_TPI_I2S_SD_JUSTIFY_RIGHT (1 << 2)
|
||||
#define SII902X_TPI_I2S_WS_POLARITY_LOW (0 << 3)
|
||||
#define SII902X_TPI_I2S_WS_POLARITY_HIGH (1 << 3)
|
||||
#define SII902X_TPI_I2S_MCLK_MULTIPLIER_128 (0 << 4)
|
||||
#define SII902X_TPI_I2S_MCLK_MULTIPLIER_256 (1 << 4)
|
||||
#define SII902X_TPI_I2S_MCLK_MULTIPLIER_384 (2 << 4)
|
||||
#define SII902X_TPI_I2S_MCLK_MULTIPLIER_512 (3 << 4)
|
||||
#define SII902X_TPI_I2S_MCLK_MULTIPLIER_768 (4 << 4)
|
||||
#define SII902X_TPI_I2S_MCLK_MULTIPLIER_1024 (5 << 4)
|
||||
#define SII902X_TPI_I2S_MCLK_MULTIPLIER_1152 (6 << 4)
|
||||
#define SII902X_TPI_I2S_MCLK_MULTIPLIER_192 (7 << 4)
|
||||
#define SII902X_TPI_I2S_SCK_EDGE_FALLING (0 << 7)
|
||||
#define SII902X_TPI_I2S_SCK_EDGE_RISING (1 << 7)
|
||||
|
||||
#define SII902X_TPI_I2S_STRM_HDR_BASE 0x21
|
||||
#define SII902X_TPI_I2S_STRM_HDR_SIZE 5
|
||||
|
||||
#define SII902X_TPI_AUDIO_CONFIG_BYTE2_REG 0x26
|
||||
#define SII902X_TPI_AUDIO_CODING_STREAM_HEADER (0 << 0)
|
||||
#define SII902X_TPI_AUDIO_CODING_PCM (1 << 0)
|
||||
#define SII902X_TPI_AUDIO_CODING_AC3 (2 << 0)
|
||||
#define SII902X_TPI_AUDIO_CODING_MPEG1 (3 << 0)
|
||||
#define SII902X_TPI_AUDIO_CODING_MP3 (4 << 0)
|
||||
#define SII902X_TPI_AUDIO_CODING_MPEG2 (5 << 0)
|
||||
#define SII902X_TPI_AUDIO_CODING_AAC (6 << 0)
|
||||
#define SII902X_TPI_AUDIO_CODING_DTS (7 << 0)
|
||||
#define SII902X_TPI_AUDIO_CODING_ATRAC (8 << 0)
|
||||
#define SII902X_TPI_AUDIO_MUTE_DISABLE (0 << 4)
|
||||
#define SII902X_TPI_AUDIO_MUTE_ENABLE (1 << 4)
|
||||
#define SII902X_TPI_AUDIO_LAYOUT_2_CHANNELS (0 << 5)
|
||||
#define SII902X_TPI_AUDIO_LAYOUT_8_CHANNELS (1 << 5)
|
||||
#define SII902X_TPI_AUDIO_INTERFACE_DISABLE (0 << 6)
|
||||
#define SII902X_TPI_AUDIO_INTERFACE_SPDIF (1 << 6)
|
||||
#define SII902X_TPI_AUDIO_INTERFACE_I2S (2 << 6)
|
||||
|
||||
#define SII902X_TPI_AUDIO_CONFIG_BYTE3_REG 0x27
|
||||
#define SII902X_TPI_AUDIO_FREQ_STREAM (0 << 3)
|
||||
#define SII902X_TPI_AUDIO_FREQ_32KHZ (1 << 3)
|
||||
#define SII902X_TPI_AUDIO_FREQ_44KHZ (2 << 3)
|
||||
#define SII902X_TPI_AUDIO_FREQ_48KHZ (3 << 3)
|
||||
#define SII902X_TPI_AUDIO_FREQ_88KHZ (4 << 3)
|
||||
#define SII902X_TPI_AUDIO_FREQ_96KHZ (5 << 3)
|
||||
#define SII902X_TPI_AUDIO_FREQ_176KHZ (6 << 3)
|
||||
#define SII902X_TPI_AUDIO_FREQ_192KHZ (7 << 3)
|
||||
#define SII902X_TPI_AUDIO_SAMPLE_SIZE_STREAM (0 << 6)
|
||||
#define SII902X_TPI_AUDIO_SAMPLE_SIZE_16 (1 << 6)
|
||||
#define SII902X_TPI_AUDIO_SAMPLE_SIZE_20 (2 << 6)
|
||||
#define SII902X_TPI_AUDIO_SAMPLE_SIZE_24 (3 << 6)
|
||||
|
||||
#define SII902X_TPI_AUDIO_CONFIG_BYTE4_REG 0x28
|
||||
|
||||
#define SII902X_INT_ENABLE 0x3c
|
||||
#define SII902X_INT_STATUS 0x3d
|
||||
#define SII902X_HOTPLUG_EVENT BIT(0)
|
||||
@@ -71,6 +146,16 @@
|
||||
|
||||
#define SII902X_REG_TPI_RQB 0xc7
|
||||
|
||||
/* Indirect internal register access */
|
||||
#define SII902X_IND_SET_PAGE 0xbc
|
||||
#define SII902X_IND_OFFSET 0xbd
|
||||
#define SII902X_IND_VALUE 0xbe
|
||||
|
||||
#define SII902X_TPI_MISC_INFOFRAME_BASE 0xbf
|
||||
#define SII902X_TPI_MISC_INFOFRAME_END 0xde
|
||||
#define SII902X_TPI_MISC_INFOFRAME_SIZE \
|
||||
(SII902X_TPI_MISC_INFOFRAME_END - SII902X_TPI_MISC_INFOFRAME_BASE)
|
||||
|
||||
#define SII902X_I2C_BUS_ACQUISITION_TIMEOUT_MS 500
|
||||
|
||||
struct sii902x {
|
||||
@@ -80,6 +165,16 @@ struct sii902x {
|
||||
struct drm_connector connector;
|
||||
struct gpio_desc *reset_gpio;
|
||||
struct i2c_mux_core *i2cmux;
|
||||
/*
|
||||
* Mutex protects audio and video functions from interfering
|
||||
* each other, by keeping their i2c command sequences atomic.
|
||||
*/
|
||||
struct mutex mutex;
|
||||
struct sii902x_audio {
|
||||
struct platform_device *pdev;
|
||||
struct clk *mclk;
|
||||
u32 i2s_fifo_sequence[4];
|
||||
} audio;
|
||||
};
|
||||
|
||||
static int sii902x_read_unlocked(struct i2c_client *i2c, u8 reg, u8 *val)
|
||||
@@ -151,8 +246,12 @@ sii902x_connector_detect(struct drm_connector *connector, bool force)
|
||||
struct sii902x *sii902x = connector_to_sii902x(connector);
|
||||
unsigned int status;
|
||||
|
||||
mutex_lock(&sii902x->mutex);
|
||||
|
||||
regmap_read(sii902x->regmap, SII902X_INT_STATUS, &status);
|
||||
|
||||
mutex_unlock(&sii902x->mutex);
|
||||
|
||||
return (status & SII902X_PLUGGED_STATUS) ?
|
||||
connector_status_connected : connector_status_disconnected;
|
||||
}
|
||||
@@ -170,12 +269,18 @@ static int sii902x_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct sii902x *sii902x = connector_to_sii902x(connector);
|
||||
u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
|
||||
u8 output_mode = SII902X_SYS_CTRL_OUTPUT_DVI;
|
||||
struct edid *edid;
|
||||
int num = 0, ret;
|
||||
|
||||
mutex_lock(&sii902x->mutex);
|
||||
|
||||
edid = drm_get_edid(connector, sii902x->i2cmux->adapter[0]);
|
||||
drm_connector_update_edid_property(connector, edid);
|
||||
if (edid) {
|
||||
if (drm_detect_hdmi_monitor(edid))
|
||||
output_mode = SII902X_SYS_CTRL_OUTPUT_HDMI;
|
||||
|
||||
num = drm_add_edid_modes(connector, edid);
|
||||
kfree(edid);
|
||||
}
|
||||
@@ -183,9 +288,19 @@ static int sii902x_get_modes(struct drm_connector *connector)
|
||||
ret = drm_display_info_set_bus_formats(&connector->display_info,
|
||||
&bus_format, 1);
|
||||
if (ret)
|
||||
return ret;
|
||||
goto error_out;
|
||||
|
||||
return num;
|
||||
ret = regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA,
|
||||
SII902X_SYS_CTRL_OUTPUT_MODE, output_mode);
|
||||
if (ret)
|
||||
goto error_out;
|
||||
|
||||
ret = num;
|
||||
|
||||
error_out:
|
||||
mutex_unlock(&sii902x->mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum drm_mode_status sii902x_mode_valid(struct drm_connector *connector,
|
||||
@@ -205,20 +320,28 @@ static void sii902x_bridge_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct sii902x *sii902x = bridge_to_sii902x(bridge);
|
||||
|
||||
mutex_lock(&sii902x->mutex);
|
||||
|
||||
regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA,
|
||||
SII902X_SYS_CTRL_PWR_DWN,
|
||||
SII902X_SYS_CTRL_PWR_DWN);
|
||||
|
||||
mutex_unlock(&sii902x->mutex);
|
||||
}
|
||||
|
||||
static void sii902x_bridge_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct sii902x *sii902x = bridge_to_sii902x(bridge);
|
||||
|
||||
mutex_lock(&sii902x->mutex);
|
||||
|
||||
regmap_update_bits(sii902x->regmap, SII902X_PWR_STATE_CTRL,
|
||||
SII902X_AVI_POWER_STATE_MSK,
|
||||
SII902X_AVI_POWER_STATE_D(0));
|
||||
regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA,
|
||||
SII902X_SYS_CTRL_PWR_DWN, 0);
|
||||
|
||||
mutex_unlock(&sii902x->mutex);
|
||||
}
|
||||
|
||||
static void sii902x_bridge_mode_set(struct drm_bridge *bridge,
|
||||
@@ -229,10 +352,11 @@ static void sii902x_bridge_mode_set(struct drm_bridge *bridge,
|
||||
struct regmap *regmap = sii902x->regmap;
|
||||
u8 buf[HDMI_INFOFRAME_SIZE(AVI)];
|
||||
struct hdmi_avi_infoframe frame;
|
||||
u16 pixel_clock_10kHz = adj->clock / 10;
|
||||
int ret;
|
||||
|
||||
buf[0] = adj->clock;
|
||||
buf[1] = adj->clock >> 8;
|
||||
buf[0] = pixel_clock_10kHz & 0xff;
|
||||
buf[1] = pixel_clock_10kHz >> 8;
|
||||
buf[2] = adj->vrefresh;
|
||||
buf[3] = 0x00;
|
||||
buf[4] = adj->hdisplay;
|
||||
@@ -244,27 +368,32 @@ static void sii902x_bridge_mode_set(struct drm_bridge *bridge,
|
||||
buf[9] = SII902X_TPI_AVI_INPUT_RANGE_AUTO |
|
||||
SII902X_TPI_AVI_INPUT_COLORSPACE_RGB;
|
||||
|
||||
mutex_lock(&sii902x->mutex);
|
||||
|
||||
ret = regmap_bulk_write(regmap, SII902X_TPI_VIDEO_DATA, buf, 10);
|
||||
if (ret)
|
||||
return;
|
||||
goto out;
|
||||
|
||||
ret = drm_hdmi_avi_infoframe_from_display_mode(&frame,
|
||||
&sii902x->connector, adj);
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("couldn't fill AVI infoframe\n");
|
||||
return;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = hdmi_avi_infoframe_pack(&frame, buf, sizeof(buf));
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("failed to pack AVI infoframe: %d\n", ret);
|
||||
return;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Do not send the infoframe header, but keep the CRC field. */
|
||||
regmap_bulk_write(regmap, SII902X_TPI_AVI_INFOFRAME,
|
||||
buf + HDMI_INFOFRAME_HEADER_SIZE - 1,
|
||||
HDMI_AVI_INFOFRAME_SIZE + 1);
|
||||
|
||||
out:
|
||||
mutex_unlock(&sii902x->mutex);
|
||||
}
|
||||
|
||||
static int sii902x_bridge_attach(struct drm_bridge *bridge)
|
||||
@@ -305,6 +434,335 @@ static const struct drm_bridge_funcs sii902x_bridge_funcs = {
|
||||
.enable = sii902x_bridge_enable,
|
||||
};
|
||||
|
||||
static int sii902x_mute(struct sii902x *sii902x, bool mute)
|
||||
{
|
||||
struct device *dev = &sii902x->i2c->dev;
|
||||
unsigned int val = mute ? SII902X_TPI_AUDIO_MUTE_ENABLE :
|
||||
SII902X_TPI_AUDIO_MUTE_DISABLE;
|
||||
|
||||
dev_dbg(dev, "%s: %s\n", __func__, mute ? "Muted" : "Unmuted");
|
||||
|
||||
return regmap_update_bits(sii902x->regmap,
|
||||
SII902X_TPI_AUDIO_CONFIG_BYTE2_REG,
|
||||
SII902X_TPI_AUDIO_MUTE_ENABLE, val);
|
||||
}
|
||||
|
||||
static const int sii902x_mclk_div_table[] = {
|
||||
128, 256, 384, 512, 768, 1024, 1152, 192 };
|
||||
|
||||
static int sii902x_select_mclk_div(u8 *i2s_config_reg, unsigned int rate,
|
||||
unsigned int mclk)
|
||||
{
|
||||
int div = mclk / rate;
|
||||
int distance = 100000;
|
||||
u8 i, nearest = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(sii902x_mclk_div_table); i++) {
|
||||
unsigned int d = abs(div - sii902x_mclk_div_table[i]);
|
||||
|
||||
if (d >= distance)
|
||||
continue;
|
||||
|
||||
nearest = i;
|
||||
distance = d;
|
||||
if (d == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
*i2s_config_reg |= nearest << 4;
|
||||
|
||||
return sii902x_mclk_div_table[nearest];
|
||||
}
|
||||
|
||||
static const struct sii902x_sample_freq {
|
||||
u32 freq;
|
||||
u8 val;
|
||||
} sii902x_sample_freq[] = {
|
||||
{ .freq = 32000, .val = SII902X_TPI_AUDIO_FREQ_32KHZ },
|
||||
{ .freq = 44000, .val = SII902X_TPI_AUDIO_FREQ_44KHZ },
|
||||
{ .freq = 48000, .val = SII902X_TPI_AUDIO_FREQ_48KHZ },
|
||||
{ .freq = 88000, .val = SII902X_TPI_AUDIO_FREQ_88KHZ },
|
||||
{ .freq = 96000, .val = SII902X_TPI_AUDIO_FREQ_96KHZ },
|
||||
{ .freq = 176000, .val = SII902X_TPI_AUDIO_FREQ_176KHZ },
|
||||
{ .freq = 192000, .val = SII902X_TPI_AUDIO_FREQ_192KHZ },
|
||||
};
|
||||
|
||||
static int sii902x_audio_hw_params(struct device *dev, void *data,
|
||||
struct hdmi_codec_daifmt *daifmt,
|
||||
struct hdmi_codec_params *params)
|
||||
{
|
||||
struct sii902x *sii902x = dev_get_drvdata(dev);
|
||||
u8 i2s_config_reg = SII902X_TPI_I2S_SD_DIRECTION_MSB_FIRST;
|
||||
u8 config_byte2_reg = (SII902X_TPI_AUDIO_INTERFACE_I2S |
|
||||
SII902X_TPI_AUDIO_MUTE_ENABLE |
|
||||
SII902X_TPI_AUDIO_CODING_PCM);
|
||||
u8 config_byte3_reg = 0;
|
||||
u8 infoframe_buf[HDMI_INFOFRAME_SIZE(AUDIO)];
|
||||
unsigned long mclk_rate;
|
||||
int i, ret;
|
||||
|
||||
if (daifmt->bit_clk_master || daifmt->frame_clk_master) {
|
||||
dev_dbg(dev, "%s: I2S master mode not supported\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (daifmt->fmt) {
|
||||
case HDMI_I2S:
|
||||
i2s_config_reg |= SII902X_TPI_I2S_FIRST_BIT_SHIFT_YES |
|
||||
SII902X_TPI_I2S_SD_JUSTIFY_LEFT;
|
||||
break;
|
||||
case HDMI_RIGHT_J:
|
||||
i2s_config_reg |= SII902X_TPI_I2S_SD_JUSTIFY_RIGHT;
|
||||
break;
|
||||
case HDMI_LEFT_J:
|
||||
i2s_config_reg |= SII902X_TPI_I2S_SD_JUSTIFY_LEFT;
|
||||
break;
|
||||
default:
|
||||
dev_dbg(dev, "%s: Unsupported i2s format %u\n", __func__,
|
||||
daifmt->fmt);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (daifmt->bit_clk_inv)
|
||||
i2s_config_reg |= SII902X_TPI_I2S_SCK_EDGE_FALLING;
|
||||
else
|
||||
i2s_config_reg |= SII902X_TPI_I2S_SCK_EDGE_RISING;
|
||||
|
||||
if (daifmt->frame_clk_inv)
|
||||
i2s_config_reg |= SII902X_TPI_I2S_WS_POLARITY_LOW;
|
||||
else
|
||||
i2s_config_reg |= SII902X_TPI_I2S_WS_POLARITY_HIGH;
|
||||
|
||||
if (params->channels > 2)
|
||||
config_byte2_reg |= SII902X_TPI_AUDIO_LAYOUT_8_CHANNELS;
|
||||
else
|
||||
config_byte2_reg |= SII902X_TPI_AUDIO_LAYOUT_2_CHANNELS;
|
||||
|
||||
switch (params->sample_width) {
|
||||
case 16:
|
||||
config_byte3_reg |= SII902X_TPI_AUDIO_SAMPLE_SIZE_16;
|
||||
break;
|
||||
case 20:
|
||||
config_byte3_reg |= SII902X_TPI_AUDIO_SAMPLE_SIZE_20;
|
||||
break;
|
||||
case 24:
|
||||
case 32:
|
||||
config_byte3_reg |= SII902X_TPI_AUDIO_SAMPLE_SIZE_24;
|
||||
break;
|
||||
default:
|
||||
dev_err(dev, "%s: Unsupported sample width %u\n", __func__,
|
||||
params->sample_width);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(sii902x_sample_freq); i++) {
|
||||
if (params->sample_rate == sii902x_sample_freq[i].freq) {
|
||||
config_byte3_reg |= sii902x_sample_freq[i].val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret = clk_prepare_enable(sii902x->audio.mclk);
|
||||
if (ret) {
|
||||
dev_err(dev, "Enabling mclk failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
mclk_rate = clk_get_rate(sii902x->audio.mclk);
|
||||
|
||||
ret = sii902x_select_mclk_div(&i2s_config_reg, params->sample_rate,
|
||||
mclk_rate);
|
||||
if (mclk_rate != ret * params->sample_rate)
|
||||
dev_dbg(dev, "Inaccurate reference clock (%ld/%d != %u)\n",
|
||||
mclk_rate, ret, params->sample_rate);
|
||||
|
||||
mutex_lock(&sii902x->mutex);
|
||||
|
||||
ret = regmap_write(sii902x->regmap,
|
||||
SII902X_TPI_AUDIO_CONFIG_BYTE2_REG,
|
||||
config_byte2_reg);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = regmap_write(sii902x->regmap, SII902X_TPI_I2S_INPUT_CONFIG_REG,
|
||||
i2s_config_reg);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(sii902x->audio.i2s_fifo_sequence) &&
|
||||
sii902x->audio.i2s_fifo_sequence[i]; i++)
|
||||
regmap_write(sii902x->regmap,
|
||||
SII902X_TPI_I2S_ENABLE_MAPPING_REG,
|
||||
sii902x->audio.i2s_fifo_sequence[i]);
|
||||
|
||||
ret = regmap_write(sii902x->regmap, SII902X_TPI_AUDIO_CONFIG_BYTE3_REG,
|
||||
config_byte3_reg);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = regmap_bulk_write(sii902x->regmap, SII902X_TPI_I2S_STRM_HDR_BASE,
|
||||
params->iec.status,
|
||||
min((size_t) SII902X_TPI_I2S_STRM_HDR_SIZE,
|
||||
sizeof(params->iec.status)));
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = hdmi_audio_infoframe_pack(¶ms->cea, infoframe_buf,
|
||||
sizeof(infoframe_buf));
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%s: Failed to pack audio infoframe: %d\n",
|
||||
__func__, ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = regmap_bulk_write(sii902x->regmap,
|
||||
SII902X_TPI_MISC_INFOFRAME_BASE,
|
||||
infoframe_buf,
|
||||
min(ret, SII902X_TPI_MISC_INFOFRAME_SIZE));
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* Decode Level 0 Packets */
|
||||
ret = regmap_write(sii902x->regmap, SII902X_IND_SET_PAGE, 0x02);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = regmap_write(sii902x->regmap, SII902X_IND_OFFSET, 0x24);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = regmap_write(sii902x->regmap, SII902X_IND_VALUE, 0x02);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
dev_dbg(dev, "%s: hdmi audio enabled\n", __func__);
|
||||
out:
|
||||
mutex_unlock(&sii902x->mutex);
|
||||
|
||||
if (ret) {
|
||||
clk_disable_unprepare(sii902x->audio.mclk);
|
||||
dev_err(dev, "%s: hdmi audio enable failed: %d\n", __func__,
|
||||
ret);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sii902x_audio_shutdown(struct device *dev, void *data)
|
||||
{
|
||||
struct sii902x *sii902x = dev_get_drvdata(dev);
|
||||
|
||||
mutex_lock(&sii902x->mutex);
|
||||
|
||||
regmap_write(sii902x->regmap, SII902X_TPI_AUDIO_CONFIG_BYTE2_REG,
|
||||
SII902X_TPI_AUDIO_INTERFACE_DISABLE);
|
||||
|
||||
mutex_unlock(&sii902x->mutex);
|
||||
|
||||
clk_disable_unprepare(sii902x->audio.mclk);
|
||||
}
|
||||
|
||||
int sii902x_audio_digital_mute(struct device *dev, void *data, bool enable)
|
||||
{
|
||||
struct sii902x *sii902x = dev_get_drvdata(dev);
|
||||
|
||||
mutex_lock(&sii902x->mutex);
|
||||
|
||||
sii902x_mute(sii902x, enable);
|
||||
|
||||
mutex_unlock(&sii902x->mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sii902x_audio_get_eld(struct device *dev, void *data,
|
||||
uint8_t *buf, size_t len)
|
||||
{
|
||||
struct sii902x *sii902x = dev_get_drvdata(dev);
|
||||
|
||||
mutex_lock(&sii902x->mutex);
|
||||
|
||||
memcpy(buf, sii902x->connector.eld,
|
||||
min(sizeof(sii902x->connector.eld), len));
|
||||
|
||||
mutex_unlock(&sii902x->mutex);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hdmi_codec_ops sii902x_audio_codec_ops = {
|
||||
.hw_params = sii902x_audio_hw_params,
|
||||
.audio_shutdown = sii902x_audio_shutdown,
|
||||
.digital_mute = sii902x_audio_digital_mute,
|
||||
.get_eld = sii902x_audio_get_eld,
|
||||
};
|
||||
|
||||
static int sii902x_audio_codec_init(struct sii902x *sii902x,
|
||||
struct device *dev)
|
||||
{
|
||||
static const u8 audio_fifo_id[] = {
|
||||
SII902X_TPI_I2S_CONFIG_FIFO0,
|
||||
SII902X_TPI_I2S_CONFIG_FIFO1,
|
||||
SII902X_TPI_I2S_CONFIG_FIFO2,
|
||||
SII902X_TPI_I2S_CONFIG_FIFO3,
|
||||
};
|
||||
static const u8 i2s_lane_id[] = {
|
||||
SII902X_TPI_I2S_SELECT_SD0,
|
||||
SII902X_TPI_I2S_SELECT_SD1,
|
||||
SII902X_TPI_I2S_SELECT_SD2,
|
||||
SII902X_TPI_I2S_SELECT_SD3,
|
||||
};
|
||||
struct hdmi_codec_pdata codec_data = {
|
||||
.ops = &sii902x_audio_codec_ops,
|
||||
.i2s = 1, /* Only i2s support for now. */
|
||||
.spdif = 0,
|
||||
.max_i2s_channels = 0,
|
||||
};
|
||||
u8 lanes[4];
|
||||
int num_lanes, i;
|
||||
|
||||
if (!of_property_read_bool(dev->of_node, "#sound-dai-cells")) {
|
||||
dev_dbg(dev, "%s: No \"#sound-dai-cells\", no audio\n",
|
||||
__func__);
|
||||
return 0;
|
||||
}
|
||||
|
||||
num_lanes = of_property_read_variable_u8_array(dev->of_node,
|
||||
"sil,i2s-data-lanes",
|
||||
lanes, 1,
|
||||
ARRAY_SIZE(lanes));
|
||||
|
||||
if (num_lanes == -EINVAL) {
|
||||
dev_dbg(dev,
|
||||
"%s: No \"sil,i2s-data-lanes\", use default <0>\n",
|
||||
__func__);
|
||||
num_lanes = 1;
|
||||
lanes[0] = 0;
|
||||
} else if (num_lanes < 0) {
|
||||
dev_err(dev,
|
||||
"%s: Error gettin \"sil,i2s-data-lanes\": %d\n",
|
||||
__func__, num_lanes);
|
||||
return num_lanes;
|
||||
}
|
||||
codec_data.max_i2s_channels = 2 * num_lanes;
|
||||
|
||||
for (i = 0; i < num_lanes; i++)
|
||||
sii902x->audio.i2s_fifo_sequence[i] |= audio_fifo_id[i] |
|
||||
i2s_lane_id[lanes[i]] | SII902X_TPI_I2S_FIFO_ENABLE;
|
||||
|
||||
if (IS_ERR(sii902x->audio.mclk)) {
|
||||
dev_err(dev, "%s: No clock (audio mclk) found: %ld\n",
|
||||
__func__, PTR_ERR(sii902x->audio.mclk));
|
||||
return 0;
|
||||
}
|
||||
|
||||
sii902x->audio.pdev = platform_device_register_data(
|
||||
dev, HDMI_CODEC_DRV_NAME, PLATFORM_DEVID_AUTO,
|
||||
&codec_data, sizeof(codec_data));
|
||||
|
||||
return PTR_ERR_OR_ZERO(sii902x->audio.pdev);
|
||||
}
|
||||
|
||||
static const struct regmap_range sii902x_volatile_ranges[] = {
|
||||
{ .range_min = 0, .range_max = 0xff },
|
||||
};
|
||||
@@ -317,6 +775,8 @@ static const struct regmap_access_table sii902x_volatile_table = {
|
||||
static const struct regmap_config sii902x_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.disable_locking = true, /* struct sii902x mutex should be enough */
|
||||
.max_register = SII902X_TPI_MISC_INFOFRAME_END,
|
||||
.volatile_table = &sii902x_volatile_table,
|
||||
.cache_type = REGCACHE_NONE,
|
||||
};
|
||||
@@ -326,9 +786,13 @@ static irqreturn_t sii902x_interrupt(int irq, void *data)
|
||||
struct sii902x *sii902x = data;
|
||||
unsigned int status = 0;
|
||||
|
||||
mutex_lock(&sii902x->mutex);
|
||||
|
||||
regmap_read(sii902x->regmap, SII902X_INT_STATUS, &status);
|
||||
regmap_write(sii902x->regmap, SII902X_INT_STATUS, status);
|
||||
|
||||
mutex_unlock(&sii902x->mutex);
|
||||
|
||||
if ((status & SII902X_HOTPLUG_EVENT) && sii902x->bridge.dev)
|
||||
drm_helper_hpd_irq_event(sii902x->bridge.dev);
|
||||
|
||||
@@ -450,6 +914,12 @@ static int sii902x_i2c_bypass_deselect(struct i2c_mux_core *mux, u32 chan_id)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct drm_bridge_timings default_sii902x_timings = {
|
||||
.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE
|
||||
| DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE
|
||||
| DRM_BUS_FLAG_DE_HIGH,
|
||||
};
|
||||
|
||||
static int sii902x_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
@@ -483,6 +953,8 @@ static int sii902x_probe(struct i2c_client *client,
|
||||
return PTR_ERR(sii902x->reset_gpio);
|
||||
}
|
||||
|
||||
mutex_init(&sii902x->mutex);
|
||||
|
||||
sii902x_reset(sii902x);
|
||||
|
||||
ret = regmap_write(sii902x->regmap, SII902X_REG_TPI_RQB, 0x0);
|
||||
@@ -520,8 +992,11 @@ static int sii902x_probe(struct i2c_client *client,
|
||||
|
||||
sii902x->bridge.funcs = &sii902x_bridge_funcs;
|
||||
sii902x->bridge.of_node = dev->of_node;
|
||||
sii902x->bridge.timings = &default_sii902x_timings;
|
||||
drm_bridge_add(&sii902x->bridge);
|
||||
|
||||
sii902x_audio_codec_init(sii902x, dev);
|
||||
|
||||
i2c_set_clientdata(client, sii902x);
|
||||
|
||||
sii902x->i2cmux = i2c_mux_alloc(client->adapter, dev,
|
||||
|
@@ -815,7 +815,7 @@ static irqreturn_t sii9234_irq_thread(int irq, void *data)
|
||||
static int sii9234_init_resources(struct sii9234 *ctx,
|
||||
struct i2c_client *client)
|
||||
{
|
||||
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
|
||||
struct i2c_adapter *adapter = client->adapter;
|
||||
int ret;
|
||||
|
||||
if (!ctx->dev->of_node) {
|
||||
@@ -897,7 +897,7 @@ static const struct drm_bridge_funcs sii9234_bridge_funcs = {
|
||||
static int sii9234_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
|
||||
struct i2c_adapter *adapter = client->adapter;
|
||||
struct sii9234 *ctx;
|
||||
struct device *dev = &client->dev;
|
||||
int ret;
|
||||
|
@@ -6,34 +6,36 @@
|
||||
* Copyright (C) 2011-2013 Freescale Semiconductor, Inc.
|
||||
* Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/hdmi.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/pinctrl/consumer.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/dma-mapping.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_edid.h>
|
||||
#include <drm/drm_encoder_slave.h>
|
||||
#include <drm/drm_scdc_helper.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
#include <drm/bridge/dw_hdmi.h>
|
||||
#include <media/cec-notifier.h>
|
||||
|
||||
#include <uapi/linux/media-bus-format.h>
|
||||
#include <uapi/linux/videodev2.h>
|
||||
|
||||
#include "dw-hdmi.h"
|
||||
#include <drm/bridge/dw_hdmi.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_edid.h>
|
||||
#include <drm/drm_encoder_slave.h>
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
#include <drm/drm_scdc_helper.h>
|
||||
|
||||
#include "dw-hdmi-audio.h"
|
||||
#include "dw-hdmi-cec.h"
|
||||
|
||||
#include <media/cec-notifier.h>
|
||||
#include "dw-hdmi.h"
|
||||
|
||||
#define DDC_SEGMENT_ADDR 0x30
|
||||
|
||||
@@ -164,6 +166,10 @@ struct dw_hdmi {
|
||||
bool sink_is_hdmi;
|
||||
bool sink_has_audio;
|
||||
|
||||
struct pinctrl *pinctrl;
|
||||
struct pinctrl_state *default_state;
|
||||
struct pinctrl_state *unwedge_state;
|
||||
|
||||
struct mutex mutex; /* for state below and previous_mode */
|
||||
enum drm_connector_force force; /* mutex-protected force state */
|
||||
bool disabled; /* DRM has disabled our bridge */
|
||||
@@ -222,6 +228,13 @@ static void hdmi_mask_writeb(struct dw_hdmi *hdmi, u8 data, unsigned int reg,
|
||||
|
||||
static void dw_hdmi_i2c_init(struct dw_hdmi *hdmi)
|
||||
{
|
||||
hdmi_writeb(hdmi, HDMI_PHY_I2CM_INT_ADDR_DONE_POL,
|
||||
HDMI_PHY_I2CM_INT_ADDR);
|
||||
|
||||
hdmi_writeb(hdmi, HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL |
|
||||
HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL,
|
||||
HDMI_PHY_I2CM_CTLINT_ADDR);
|
||||
|
||||
/* Software reset */
|
||||
hdmi_writeb(hdmi, 0x00, HDMI_I2CM_SOFTRSTZ);
|
||||
|
||||
@@ -242,11 +255,82 @@ static void dw_hdmi_i2c_init(struct dw_hdmi *hdmi)
|
||||
HDMI_IH_MUTE_I2CM_STAT0);
|
||||
}
|
||||
|
||||
static bool dw_hdmi_i2c_unwedge(struct dw_hdmi *hdmi)
|
||||
{
|
||||
/* If no unwedge state then give up */
|
||||
if (!hdmi->unwedge_state)
|
||||
return false;
|
||||
|
||||
dev_info(hdmi->dev, "Attempting to unwedge stuck i2c bus\n");
|
||||
|
||||
/*
|
||||
* This is a huge hack to workaround a problem where the dw_hdmi i2c
|
||||
* bus could sometimes get wedged. Once wedged there doesn't appear
|
||||
* to be any way to unwedge it (including the HDMI_I2CM_SOFTRSTZ)
|
||||
* other than pulsing the SDA line.
|
||||
*
|
||||
* We appear to be able to pulse the SDA line (in the eyes of dw_hdmi)
|
||||
* by:
|
||||
* 1. Remux the pin as a GPIO output, driven low.
|
||||
* 2. Wait a little while. 1 ms seems to work, but we'll do 10.
|
||||
* 3. Immediately jump to remux the pin as dw_hdmi i2c again.
|
||||
*
|
||||
* At the moment of remuxing, the line will still be low due to its
|
||||
* recent stint as an output, but then it will be pulled high by the
|
||||
* (presumed) external pullup. dw_hdmi seems to see this as a rising
|
||||
* edge and that seems to get it out of its jam.
|
||||
*
|
||||
* This wedging was only ever seen on one TV, and only on one of
|
||||
* its HDMI ports. It happened when the TV was powered on while the
|
||||
* device was plugged in. A scope trace shows the TV bringing both SDA
|
||||
* and SCL low, then bringing them both back up at roughly the same
|
||||
* time. Presumably this confuses dw_hdmi because it saw activity but
|
||||
* no real STOP (maybe it thinks there's another master on the bus?).
|
||||
* Giving it a clean rising edge of SDA while SCL is already high
|
||||
* presumably makes dw_hdmi see a STOP which seems to bring dw_hdmi out
|
||||
* of its stupor.
|
||||
*
|
||||
* Note that after coming back alive, transfers seem to immediately
|
||||
* resume, so if we unwedge due to a timeout we should wait a little
|
||||
* longer for our transfer to finish, since it might have just started
|
||||
* now.
|
||||
*/
|
||||
pinctrl_select_state(hdmi->pinctrl, hdmi->unwedge_state);
|
||||
msleep(10);
|
||||
pinctrl_select_state(hdmi->pinctrl, hdmi->default_state);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int dw_hdmi_i2c_wait(struct dw_hdmi *hdmi)
|
||||
{
|
||||
struct dw_hdmi_i2c *i2c = hdmi->i2c;
|
||||
int stat;
|
||||
|
||||
stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
|
||||
if (!stat) {
|
||||
/* If we can't unwedge, return timeout */
|
||||
if (!dw_hdmi_i2c_unwedge(hdmi))
|
||||
return -EAGAIN;
|
||||
|
||||
/* We tried to unwedge; give it another chance */
|
||||
stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
|
||||
if (!stat)
|
||||
return -EAGAIN;
|
||||
}
|
||||
|
||||
/* Check for error condition on the bus */
|
||||
if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dw_hdmi_i2c_read(struct dw_hdmi *hdmi,
|
||||
unsigned char *buf, unsigned int length)
|
||||
{
|
||||
struct dw_hdmi_i2c *i2c = hdmi->i2c;
|
||||
int stat;
|
||||
int ret;
|
||||
|
||||
if (!i2c->is_regaddr) {
|
||||
dev_dbg(hdmi->dev, "set read register address to 0\n");
|
||||
@@ -265,13 +349,9 @@ static int dw_hdmi_i2c_read(struct dw_hdmi *hdmi,
|
||||
hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_READ,
|
||||
HDMI_I2CM_OPERATION);
|
||||
|
||||
stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
|
||||
if (!stat)
|
||||
return -EAGAIN;
|
||||
|
||||
/* Check for error condition on the bus */
|
||||
if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR)
|
||||
return -EIO;
|
||||
ret = dw_hdmi_i2c_wait(hdmi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*buf++ = hdmi_readb(hdmi, HDMI_I2CM_DATAI);
|
||||
}
|
||||
@@ -284,7 +364,7 @@ static int dw_hdmi_i2c_write(struct dw_hdmi *hdmi,
|
||||
unsigned char *buf, unsigned int length)
|
||||
{
|
||||
struct dw_hdmi_i2c *i2c = hdmi->i2c;
|
||||
int stat;
|
||||
int ret;
|
||||
|
||||
if (!i2c->is_regaddr) {
|
||||
/* Use the first write byte as register address */
|
||||
@@ -302,13 +382,9 @@ static int dw_hdmi_i2c_write(struct dw_hdmi *hdmi,
|
||||
hdmi_writeb(hdmi, HDMI_I2CM_OPERATION_WRITE,
|
||||
HDMI_I2CM_OPERATION);
|
||||
|
||||
stat = wait_for_completion_timeout(&i2c->cmp, HZ / 10);
|
||||
if (!stat)
|
||||
return -EAGAIN;
|
||||
|
||||
/* Check for error condition on the bus */
|
||||
if (i2c->stat & HDMI_IH_I2CM_STAT0_ERROR)
|
||||
return -EIO;
|
||||
ret = dw_hdmi_i2c_wait(hdmi);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -1920,16 +1996,6 @@ static int dw_hdmi_setup(struct dw_hdmi *hdmi, struct drm_display_mode *mode)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dw_hdmi_setup_i2c(struct dw_hdmi *hdmi)
|
||||
{
|
||||
hdmi_writeb(hdmi, HDMI_PHY_I2CM_INT_ADDR_DONE_POL,
|
||||
HDMI_PHY_I2CM_INT_ADDR);
|
||||
|
||||
hdmi_writeb(hdmi, HDMI_PHY_I2CM_CTLINT_ADDR_NAC_POL |
|
||||
HDMI_PHY_I2CM_CTLINT_ADDR_ARBITRATION_POL,
|
||||
HDMI_PHY_I2CM_CTLINT_ADDR);
|
||||
}
|
||||
|
||||
static void initialize_hdmi_ih_mutes(struct dw_hdmi *hdmi)
|
||||
{
|
||||
u8 ih_mute;
|
||||
@@ -2430,6 +2496,21 @@ static const struct regmap_config hdmi_regmap_32bit_config = {
|
||||
.max_register = HDMI_I2CM_FS_SCL_LCNT_0_ADDR << 2,
|
||||
};
|
||||
|
||||
static void dw_hdmi_init_hw(struct dw_hdmi *hdmi)
|
||||
{
|
||||
initialize_hdmi_ih_mutes(hdmi);
|
||||
|
||||
/*
|
||||
* Reset HDMI DDC I2C master controller and mute I2CM interrupts.
|
||||
* Even if we are using a separate i2c adapter doing this doesn't
|
||||
* hurt.
|
||||
*/
|
||||
dw_hdmi_i2c_init(hdmi);
|
||||
|
||||
if (hdmi->phy.ops->setup_hpd)
|
||||
hdmi->phy.ops->setup_hpd(hdmi, hdmi->phy.data);
|
||||
}
|
||||
|
||||
static struct dw_hdmi *
|
||||
__dw_hdmi_probe(struct platform_device *pdev,
|
||||
const struct dw_hdmi_plat_data *plat_data)
|
||||
@@ -2581,7 +2662,7 @@ __dw_hdmi_probe(struct platform_device *pdev,
|
||||
prod_id1 & HDMI_PRODUCT_ID1_HDCP ? "with" : "without",
|
||||
hdmi->phy.name);
|
||||
|
||||
initialize_hdmi_ih_mutes(hdmi);
|
||||
dw_hdmi_init_hw(hdmi);
|
||||
|
||||
irq = platform_get_irq(pdev, 0);
|
||||
if (irq < 0) {
|
||||
@@ -2609,6 +2690,24 @@ __dw_hdmi_probe(struct platform_device *pdev,
|
||||
|
||||
/* If DDC bus is not specified, try to register HDMI I2C bus */
|
||||
if (!hdmi->ddc) {
|
||||
/* Look for (optional) stuff related to unwedging */
|
||||
hdmi->pinctrl = devm_pinctrl_get(dev);
|
||||
if (!IS_ERR(hdmi->pinctrl)) {
|
||||
hdmi->unwedge_state =
|
||||
pinctrl_lookup_state(hdmi->pinctrl, "unwedge");
|
||||
hdmi->default_state =
|
||||
pinctrl_lookup_state(hdmi->pinctrl, "default");
|
||||
|
||||
if (IS_ERR(hdmi->default_state) ||
|
||||
IS_ERR(hdmi->unwedge_state)) {
|
||||
if (!IS_ERR(hdmi->unwedge_state))
|
||||
dev_warn(dev,
|
||||
"Unwedge requires default pinctrl\n");
|
||||
hdmi->default_state = NULL;
|
||||
hdmi->unwedge_state = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
hdmi->ddc = dw_hdmi_i2c_adapter(hdmi);
|
||||
if (IS_ERR(hdmi->ddc))
|
||||
hdmi->ddc = NULL;
|
||||
@@ -2620,10 +2719,6 @@ __dw_hdmi_probe(struct platform_device *pdev,
|
||||
hdmi->bridge.of_node = pdev->dev.of_node;
|
||||
#endif
|
||||
|
||||
dw_hdmi_setup_i2c(hdmi);
|
||||
if (hdmi->phy.ops->setup_hpd)
|
||||
hdmi->phy.ops->setup_hpd(hdmi, hdmi->phy.data);
|
||||
|
||||
memset(&pdevinfo, 0, sizeof(pdevinfo));
|
||||
pdevinfo.parent = dev;
|
||||
pdevinfo.id = PLATFORM_DEVID_AUTO;
|
||||
@@ -2676,10 +2771,6 @@ __dw_hdmi_probe(struct platform_device *pdev,
|
||||
hdmi->cec = platform_device_register_full(&pdevinfo);
|
||||
}
|
||||
|
||||
/* Reset HDMI DDC I2C master controller and mute I2CM interrupts */
|
||||
if (hdmi->i2c)
|
||||
dw_hdmi_i2c_init(hdmi);
|
||||
|
||||
return hdmi;
|
||||
|
||||
err_iahb:
|
||||
@@ -2783,6 +2874,12 @@ void dw_hdmi_unbind(struct dw_hdmi *hdmi)
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dw_hdmi_unbind);
|
||||
|
||||
void dw_hdmi_resume(struct dw_hdmi *hdmi)
|
||||
{
|
||||
dw_hdmi_init_hw(hdmi);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dw_hdmi_resume);
|
||||
|
||||
MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");
|
||||
MODULE_AUTHOR("Andy Yan <andy.yan@rock-chips.com>");
|
||||
MODULE_AUTHOR("Yakir Yang <ykk@rock-chips.com>");
|
||||
|
@@ -15,15 +15,18 @@
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/reset.h>
|
||||
#include <drm/drmP.h>
|
||||
|
||||
#include <video/mipi_display.h>
|
||||
|
||||
#include <drm/bridge/dw_mipi_dsi.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_bridge.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_mipi_dsi.h>
|
||||
#include <drm/drm_modes.h>
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
#include <drm/bridge/dw_mipi_dsi.h>
|
||||
#include <video/mipi_display.h>
|
||||
|
||||
#define HWVER_131 0x31333100 /* IP version 1.31 */
|
||||
|
||||
@@ -775,6 +778,10 @@ static void dw_mipi_dsi_clear_err(struct dw_mipi_dsi *dsi)
|
||||
static void dw_mipi_dsi_bridge_post_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge);
|
||||
const struct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops;
|
||||
|
||||
if (phy_ops->power_off)
|
||||
phy_ops->power_off(dsi->plat_data->priv_data);
|
||||
|
||||
/*
|
||||
* Switch to command mode before panel-bridge post_disable &
|
||||
@@ -874,11 +881,15 @@ static void dw_mipi_dsi_bridge_mode_set(struct drm_bridge *bridge,
|
||||
static void dw_mipi_dsi_bridge_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct dw_mipi_dsi *dsi = bridge_to_dsi(bridge);
|
||||
const struct dw_mipi_dsi_phy_ops *phy_ops = dsi->plat_data->phy_ops;
|
||||
|
||||
/* Switch to video mode for panel-bridge enable & panel enable */
|
||||
dw_mipi_dsi_set_mode(dsi, MIPI_DSI_MODE_VIDEO);
|
||||
if (dsi->slave)
|
||||
dw_mipi_dsi_set_mode(dsi->slave, MIPI_DSI_MODE_VIDEO);
|
||||
|
||||
if (phy_ops->power_on)
|
||||
phy_ops->power_on(dsi->plat_data->priv_data);
|
||||
}
|
||||
|
||||
static enum drm_mode_status
|
||||
|
@@ -7,18 +7,22 @@
|
||||
* Maciej Purski <m.purski@samsung.com>
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <video/mipi_display.h>
|
||||
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_fb_helper.h>
|
||||
#include <drm/drm_mipi_dsi.h>
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
#include <drm/drmP.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <video/mipi_display.h>
|
||||
|
||||
#define FLD_MASK(start, end) (((1 << ((start) - (end) + 1)) - 1) << (end))
|
||||
#define FLD_VAL(val, start, end) (((val) << (end)) & FLD_MASK(start, end))
|
||||
|
@@ -62,6 +62,7 @@
|
||||
|
||||
/* System */
|
||||
#define TC_IDREG 0x0500
|
||||
#define SYSSTAT 0x0508
|
||||
#define SYSCTRL 0x0510
|
||||
#define DP0_AUDSRC_NO_INPUT (0 << 3)
|
||||
#define DP0_AUDSRC_I2S_RX (1 << 3)
|
||||
@@ -69,6 +70,19 @@
|
||||
#define DP0_VIDSRC_DSI_RX (1 << 0)
|
||||
#define DP0_VIDSRC_DPI_RX (2 << 0)
|
||||
#define DP0_VIDSRC_COLOR_BAR (3 << 0)
|
||||
#define GPIOM 0x0540
|
||||
#define GPIOC 0x0544
|
||||
#define GPIOO 0x0548
|
||||
#define GPIOI 0x054c
|
||||
#define INTCTL_G 0x0560
|
||||
#define INTSTS_G 0x0564
|
||||
|
||||
#define INT_SYSERR BIT(16)
|
||||
#define INT_GPIO_H(x) (1 << (x == 0 ? 2 : 10))
|
||||
#define INT_GPIO_LC(x) (1 << (x == 0 ? 3 : 11))
|
||||
|
||||
#define INT_GP0_LCNT 0x0584
|
||||
#define INT_GP1_LCNT 0x0588
|
||||
|
||||
/* Control */
|
||||
#define DP0CTL 0x0600
|
||||
@@ -177,11 +191,8 @@ module_param_named(test, tc_test_pattern, bool, 0644);
|
||||
struct tc_edp_link {
|
||||
struct drm_dp_link base;
|
||||
u8 assr;
|
||||
int scrambler_dis;
|
||||
int spread;
|
||||
int coding8b10b;
|
||||
u8 swing;
|
||||
u8 preemp;
|
||||
bool scrambler_dis;
|
||||
bool spread;
|
||||
};
|
||||
|
||||
struct tc_data {
|
||||
@@ -199,7 +210,7 @@ struct tc_data {
|
||||
/* display edid */
|
||||
struct edid *edid;
|
||||
/* current mode */
|
||||
const struct drm_display_mode *mode;
|
||||
struct drm_display_mode mode;
|
||||
|
||||
u32 rev;
|
||||
u8 assr;
|
||||
@@ -207,6 +218,12 @@ struct tc_data {
|
||||
struct gpio_desc *sd_gpio;
|
||||
struct gpio_desc *reset_gpio;
|
||||
struct clk *refclk;
|
||||
|
||||
/* do we have IRQ */
|
||||
bool have_irq;
|
||||
|
||||
/* HPD pin number (0 or 1) or -ENODEV */
|
||||
int hpd_pin;
|
||||
};
|
||||
|
||||
static inline struct tc_data *aux_to_tc(struct drm_dp_aux *a)
|
||||
@@ -277,14 +294,17 @@ static int tc_aux_get_status(struct tc_data *tc, u8 *reply)
|
||||
ret = regmap_read(tc->regmap, DP0_AUXSTATUS, &value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (value & AUX_BUSY) {
|
||||
if (value & AUX_TIMEOUT) {
|
||||
dev_err(tc->dev, "i2c access timeout!\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
dev_err(tc->dev, "aux busy!\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (value & AUX_TIMEOUT) {
|
||||
dev_err(tc->dev, "aux access timeout!\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
*reply = (value & AUX_STATUS_MASK) >> AUX_STATUS_SHIFT;
|
||||
return 0;
|
||||
}
|
||||
@@ -378,13 +398,10 @@ static u32 tc_srcctrl(struct tc_data *tc)
|
||||
* No training pattern, skew lane 1 data by two LSCLK cycles with
|
||||
* respect to lane 0 data, AutoCorrect Mode = 0
|
||||
*/
|
||||
u32 reg = DP0_SRCCTRL_NOTP | DP0_SRCCTRL_LANESKEW;
|
||||
u32 reg = DP0_SRCCTRL_NOTP | DP0_SRCCTRL_LANESKEW | DP0_SRCCTRL_EN810B;
|
||||
|
||||
if (tc->link.scrambler_dis)
|
||||
reg |= DP0_SRCCTRL_SCRMBLDIS; /* Scrambler Disabled */
|
||||
if (tc->link.coding8b10b)
|
||||
/* Enable 8/10B Encoder (TxData[19:16] not used) */
|
||||
reg |= DP0_SRCCTRL_EN810B;
|
||||
if (tc->link.spread)
|
||||
reg |= DP0_SRCCTRL_SSCG; /* Spread Spectrum Enable */
|
||||
if (tc->link.base.num_lanes == 2)
|
||||
@@ -536,7 +553,6 @@ static int tc_aux_link_setup(struct tc_data *tc)
|
||||
unsigned long rate;
|
||||
u32 value;
|
||||
int ret;
|
||||
u32 dp_phy_ctrl;
|
||||
|
||||
rate = clk_get_rate(tc->refclk);
|
||||
switch (rate) {
|
||||
@@ -561,10 +577,7 @@ static int tc_aux_link_setup(struct tc_data *tc)
|
||||
value |= SYSCLK_SEL_LSCLK | LSCLK_DIV_2;
|
||||
tc_write(SYS_PLLPARAM, value);
|
||||
|
||||
dp_phy_ctrl = BGREN | PWR_SW_EN | PHY_A0_EN;
|
||||
if (tc->link.base.num_lanes == 2)
|
||||
dp_phy_ctrl |= PHY_2LANE;
|
||||
tc_write(DP_PHY_CTRL, dp_phy_ctrl);
|
||||
tc_write(DP_PHY_CTRL, BGREN | PWR_SW_EN | PHY_A0_EN);
|
||||
|
||||
/*
|
||||
* Initially PLLs are in bypass. Force PLL parameter update,
|
||||
@@ -581,8 +594,9 @@ static int tc_aux_link_setup(struct tc_data *tc)
|
||||
if (ret == -ETIMEDOUT) {
|
||||
dev_err(tc->dev, "Timeout waiting for PHY to become ready");
|
||||
return ret;
|
||||
} else if (ret)
|
||||
} else if (ret) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Setup AUX link */
|
||||
tc_write(DP0_AUXCFG1, AUX_RX_FILTER_EN |
|
||||
@@ -618,13 +632,13 @@ static int tc_get_display_props(struct tc_data *tc)
|
||||
ret = drm_dp_dpcd_readb(&tc->aux, DP_MAX_DOWNSPREAD, tmp);
|
||||
if (ret < 0)
|
||||
goto err_dpcd_read;
|
||||
tc->link.spread = tmp[0] & BIT(0); /* 0.5% down spread */
|
||||
tc->link.spread = tmp[0] & DP_MAX_DOWNSPREAD_0_5;
|
||||
|
||||
ret = drm_dp_dpcd_readb(&tc->aux, DP_MAIN_LINK_CHANNEL_CODING, tmp);
|
||||
if (ret < 0)
|
||||
goto err_dpcd_read;
|
||||
tc->link.coding8b10b = tmp[0] & BIT(0);
|
||||
tc->link.scrambler_dis = 0;
|
||||
|
||||
tc->link.scrambler_dis = false;
|
||||
/* read assr */
|
||||
ret = drm_dp_dpcd_readb(&tc->aux, DP_EDP_CONFIGURATION_SET, tmp);
|
||||
if (ret < 0)
|
||||
@@ -637,7 +651,9 @@ static int tc_get_display_props(struct tc_data *tc)
|
||||
tc->link.base.num_lanes,
|
||||
(tc->link.base.capabilities & DP_LINK_CAP_ENHANCED_FRAMING) ?
|
||||
"enhanced" : "non-enhanced");
|
||||
dev_dbg(tc->dev, "ANSI 8B/10B: %d\n", tc->link.coding8b10b);
|
||||
dev_dbg(tc->dev, "Downspread: %s, scrambler: %s\n",
|
||||
tc->link.spread ? "0.5%" : "0.0%",
|
||||
tc->link.scrambler_dis ? "disabled" : "enabled");
|
||||
dev_dbg(tc->dev, "Display ASSR: %d, TC358767 ASSR: %d\n",
|
||||
tc->link.assr, tc->assr);
|
||||
|
||||
@@ -735,89 +751,29 @@ err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tc_link_training(struct tc_data *tc, int pattern)
|
||||
static int tc_wait_link_training(struct tc_data *tc)
|
||||
{
|
||||
const char * const *errors;
|
||||
u32 srcctrl = tc_srcctrl(tc) | DP0_SRCCTRL_SCRMBLDIS |
|
||||
DP0_SRCCTRL_AUTOCORRECT;
|
||||
int timeout;
|
||||
int retry;
|
||||
u32 timeout = 1000;
|
||||
u32 value;
|
||||
int ret;
|
||||
|
||||
if (pattern == DP_TRAINING_PATTERN_1) {
|
||||
srcctrl |= DP0_SRCCTRL_TP1;
|
||||
errors = training_pattern1_errors;
|
||||
} else {
|
||||
srcctrl |= DP0_SRCCTRL_TP2;
|
||||
errors = training_pattern2_errors;
|
||||
}
|
||||
|
||||
/* Set DPCD 0x102 for Training Part 1 or 2 */
|
||||
tc_write(DP0_SNKLTCTRL, DP_LINK_SCRAMBLING_DISABLE | pattern);
|
||||
|
||||
tc_write(DP0_LTLOOPCTRL,
|
||||
(0x0f << 28) | /* Defer Iteration Count */
|
||||
(0x0f << 24) | /* Loop Iteration Count */
|
||||
(0x0d << 0)); /* Loop Timer Delay */
|
||||
|
||||
retry = 5;
|
||||
do {
|
||||
/* Set DP0 Training Pattern */
|
||||
tc_write(DP0_SRCCTRL, srcctrl);
|
||||
udelay(1);
|
||||
tc_read(DP0_LTSTAT, &value);
|
||||
} while ((!(value & LT_LOOPDONE)) && (--timeout));
|
||||
|
||||
/* Enable DP0 to start Link Training */
|
||||
tc_write(DP0CTL, DP_EN);
|
||||
|
||||
/* wait */
|
||||
timeout = 1000;
|
||||
do {
|
||||
tc_read(DP0_LTSTAT, &value);
|
||||
udelay(1);
|
||||
} while ((!(value & LT_LOOPDONE)) && (--timeout));
|
||||
if (timeout == 0) {
|
||||
dev_err(tc->dev, "Link training timeout!\n");
|
||||
} else {
|
||||
int pattern = (value >> 11) & 0x3;
|
||||
int error = (value >> 8) & 0x7;
|
||||
|
||||
dev_dbg(tc->dev,
|
||||
"Link training phase %d done after %d uS: %s\n",
|
||||
pattern, 1000 - timeout, errors[error]);
|
||||
if (pattern == DP_TRAINING_PATTERN_1 && error == 0)
|
||||
break;
|
||||
if (pattern == DP_TRAINING_PATTERN_2) {
|
||||
value &= LT_CHANNEL1_EQ_BITS |
|
||||
LT_INTERLANE_ALIGN_DONE |
|
||||
LT_CHANNEL0_EQ_BITS;
|
||||
/* in case of two lanes */
|
||||
if ((tc->link.base.num_lanes == 2) &&
|
||||
(value == (LT_CHANNEL1_EQ_BITS |
|
||||
LT_INTERLANE_ALIGN_DONE |
|
||||
LT_CHANNEL0_EQ_BITS)))
|
||||
break;
|
||||
/* in case of one line */
|
||||
if ((tc->link.base.num_lanes == 1) &&
|
||||
(value == (LT_INTERLANE_ALIGN_DONE |
|
||||
LT_CHANNEL0_EQ_BITS)))
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* restart */
|
||||
tc_write(DP0CTL, 0);
|
||||
usleep_range(10, 20);
|
||||
} while (--retry);
|
||||
if (retry == 0) {
|
||||
dev_err(tc->dev, "Failed to finish training phase %d\n",
|
||||
pattern);
|
||||
if (timeout == 0) {
|
||||
dev_err(tc->dev, "Link training timeout waiting for LT_LOOPDONE!\n");
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return (value >> 8) & 0x7;
|
||||
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tc_main_link_setup(struct tc_data *tc)
|
||||
static int tc_main_link_enable(struct tc_data *tc)
|
||||
{
|
||||
struct drm_dp_aux *aux = &tc->aux;
|
||||
struct device *dev = tc->dev;
|
||||
@@ -828,9 +784,11 @@ static int tc_main_link_setup(struct tc_data *tc)
|
||||
int ret;
|
||||
u8 tmp[8];
|
||||
|
||||
/* display mode should be set at this point */
|
||||
if (!tc->mode)
|
||||
return -EINVAL;
|
||||
dev_dbg(tc->dev, "link enable\n");
|
||||
|
||||
tc_read(DP0CTL, &value);
|
||||
if (WARN_ON(value & DP_EN))
|
||||
tc_write(DP0CTL, 0);
|
||||
|
||||
tc_write(DP0_SRCCTRL, tc_srcctrl(tc));
|
||||
/* SSCG and BW27 on DP1 must be set to the same as on DP0 */
|
||||
@@ -863,7 +821,6 @@ static int tc_main_link_setup(struct tc_data *tc)
|
||||
if (tc->link.base.num_lanes == 2)
|
||||
dp_phy_ctrl |= PHY_2LANE;
|
||||
tc_write(DP_PHY_CTRL, dp_phy_ctrl);
|
||||
msleep(100);
|
||||
|
||||
/* PLL setup */
|
||||
tc_write(DP0_PLLCTRL, PLLUPDATE | PLLEN);
|
||||
@@ -872,14 +829,6 @@ static int tc_main_link_setup(struct tc_data *tc)
|
||||
tc_write(DP1_PLLCTRL, PLLUPDATE | PLLEN);
|
||||
tc_wait_pll_lock(tc);
|
||||
|
||||
/* PXL PLL setup */
|
||||
if (tc_test_pattern) {
|
||||
ret = tc_pxl_pll_en(tc, clk_get_rate(tc->refclk),
|
||||
1000 * tc->mode->clock);
|
||||
if (ret)
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Reset/Enable Main Links */
|
||||
dp_phy_ctrl |= DP_PHY_RST | PHY_M1_RST | PHY_M0_RST;
|
||||
tc_write(DP_PHY_CTRL, dp_phy_ctrl);
|
||||
@@ -925,9 +874,9 @@ static int tc_main_link_setup(struct tc_data *tc)
|
||||
|
||||
if (tmp[0] != tc->assr) {
|
||||
dev_dbg(dev, "Failed to switch display ASSR to %d, falling back to unscrambled mode\n",
|
||||
tc->assr);
|
||||
tc->assr);
|
||||
/* trying with disabled scrambler */
|
||||
tc->link.scrambler_dis = 1;
|
||||
tc->link.scrambler_dis = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -939,18 +888,81 @@ static int tc_main_link_setup(struct tc_data *tc)
|
||||
/* DOWNSPREAD_CTRL */
|
||||
tmp[0] = tc->link.spread ? DP_SPREAD_AMP_0_5 : 0x00;
|
||||
/* MAIN_LINK_CHANNEL_CODING_SET */
|
||||
tmp[1] = tc->link.coding8b10b ? DP_SET_ANSI_8B10B : 0x00;
|
||||
tmp[1] = DP_SET_ANSI_8B10B;
|
||||
ret = drm_dp_dpcd_write(aux, DP_DOWNSPREAD_CTRL, tmp, 2);
|
||||
if (ret < 0)
|
||||
goto err_dpcd_write;
|
||||
|
||||
ret = tc_link_training(tc, DP_TRAINING_PATTERN_1);
|
||||
if (ret)
|
||||
/* Reset voltage-swing & pre-emphasis */
|
||||
tmp[0] = tmp[1] = DP_TRAIN_VOLTAGE_SWING_LEVEL_0 |
|
||||
DP_TRAIN_PRE_EMPH_LEVEL_0;
|
||||
ret = drm_dp_dpcd_write(aux, DP_TRAINING_LANE0_SET, tmp, 2);
|
||||
if (ret < 0)
|
||||
goto err_dpcd_write;
|
||||
|
||||
/* Clock-Recovery */
|
||||
|
||||
/* Set DPCD 0x102 for Training Pattern 1 */
|
||||
tc_write(DP0_SNKLTCTRL, DP_LINK_SCRAMBLING_DISABLE |
|
||||
DP_TRAINING_PATTERN_1);
|
||||
|
||||
tc_write(DP0_LTLOOPCTRL,
|
||||
(15 << 28) | /* Defer Iteration Count */
|
||||
(15 << 24) | /* Loop Iteration Count */
|
||||
(0xd << 0)); /* Loop Timer Delay */
|
||||
|
||||
tc_write(DP0_SRCCTRL, tc_srcctrl(tc) | DP0_SRCCTRL_SCRMBLDIS |
|
||||
DP0_SRCCTRL_AUTOCORRECT | DP0_SRCCTRL_TP1);
|
||||
|
||||
/* Enable DP0 to start Link Training */
|
||||
tc_write(DP0CTL,
|
||||
((tc->link.base.capabilities & DP_LINK_CAP_ENHANCED_FRAMING) ? EF_EN : 0) |
|
||||
DP_EN);
|
||||
|
||||
/* wait */
|
||||
ret = tc_wait_link_training(tc);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
ret = tc_link_training(tc, DP_TRAINING_PATTERN_2);
|
||||
if (ret)
|
||||
if (ret) {
|
||||
dev_err(tc->dev, "Link training phase 1 failed: %s\n",
|
||||
training_pattern1_errors[ret]);
|
||||
ret = -ENODEV;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* Channel Equalization */
|
||||
|
||||
/* Set DPCD 0x102 for Training Pattern 2 */
|
||||
tc_write(DP0_SNKLTCTRL, DP_LINK_SCRAMBLING_DISABLE |
|
||||
DP_TRAINING_PATTERN_2);
|
||||
|
||||
tc_write(DP0_SRCCTRL, tc_srcctrl(tc) | DP0_SRCCTRL_SCRMBLDIS |
|
||||
DP0_SRCCTRL_AUTOCORRECT | DP0_SRCCTRL_TP2);
|
||||
|
||||
/* wait */
|
||||
ret = tc_wait_link_training(tc);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
if (ret) {
|
||||
dev_err(tc->dev, "Link training phase 2 failed: %s\n",
|
||||
training_pattern2_errors[ret]);
|
||||
ret = -ENODEV;
|
||||
goto err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Toshiba's documentation suggests to first clear DPCD 0x102, then
|
||||
* clear the training pattern bit in DP0_SRCCTRL. Testing shows
|
||||
* that the link sometimes drops if those steps are done in that order,
|
||||
* but if the steps are done in reverse order, the link stays up.
|
||||
*
|
||||
* So we do the steps differently than documented here.
|
||||
*/
|
||||
|
||||
/* Clear Training Pattern, set AutoCorrect Mode = 1 */
|
||||
tc_write(DP0_SRCCTRL, tc_srcctrl(tc) | DP0_SRCCTRL_AUTOCORRECT);
|
||||
|
||||
/* Clear DPCD 0x102 */
|
||||
/* Note: Can Not use DP0_SNKLTCTRL (0x06E4) short cut */
|
||||
@@ -959,47 +971,43 @@ static int tc_main_link_setup(struct tc_data *tc)
|
||||
if (ret < 0)
|
||||
goto err_dpcd_write;
|
||||
|
||||
/* Clear Training Pattern, set AutoCorrect Mode = 1 */
|
||||
tc_write(DP0_SRCCTRL, tc_srcctrl(tc) | DP0_SRCCTRL_AUTOCORRECT);
|
||||
/* Check link status */
|
||||
ret = drm_dp_dpcd_read_link_status(aux, tmp);
|
||||
if (ret < 0)
|
||||
goto err_dpcd_read;
|
||||
|
||||
/* Wait */
|
||||
timeout = 100;
|
||||
do {
|
||||
udelay(1);
|
||||
/* Read DPCD 0x202-0x207 */
|
||||
ret = drm_dp_dpcd_read_link_status(aux, tmp + 2);
|
||||
if (ret < 0)
|
||||
goto err_dpcd_read;
|
||||
} while ((--timeout) &&
|
||||
!(drm_dp_channel_eq_ok(tmp + 2, tc->link.base.num_lanes)));
|
||||
ret = 0;
|
||||
|
||||
if (timeout == 0) {
|
||||
/* Read DPCD 0x200-0x201 */
|
||||
ret = drm_dp_dpcd_read(aux, DP_SINK_COUNT, tmp, 2);
|
||||
if (ret < 0)
|
||||
goto err_dpcd_read;
|
||||
dev_err(dev, "channel(s) EQ not ok\n");
|
||||
dev_info(dev, "0x0200 SINK_COUNT: 0x%02x\n", tmp[0]);
|
||||
dev_info(dev, "0x0201 DEVICE_SERVICE_IRQ_VECTOR: 0x%02x\n",
|
||||
tmp[1]);
|
||||
dev_info(dev, "0x0202 LANE0_1_STATUS: 0x%02x\n", tmp[2]);
|
||||
dev_info(dev, "0x0204 LANE_ALIGN_STATUS_UPDATED: 0x%02x\n",
|
||||
tmp[4]);
|
||||
dev_info(dev, "0x0205 SINK_STATUS: 0x%02x\n", tmp[5]);
|
||||
dev_info(dev, "0x0206 ADJUST_REQUEST_LANE0_1: 0x%02x\n",
|
||||
tmp[6]);
|
||||
value = tmp[0] & DP_CHANNEL_EQ_BITS;
|
||||
|
||||
return -EAGAIN;
|
||||
if (value != DP_CHANNEL_EQ_BITS) {
|
||||
dev_err(tc->dev, "Lane 0 failed: %x\n", value);
|
||||
ret = -ENODEV;
|
||||
}
|
||||
|
||||
ret = tc_set_video_mode(tc, tc->mode);
|
||||
if (ret)
|
||||
goto err;
|
||||
if (tc->link.base.num_lanes == 2) {
|
||||
value = (tmp[0] >> 4) & DP_CHANNEL_EQ_BITS;
|
||||
|
||||
/* Set M/N */
|
||||
ret = tc_stream_clock_calc(tc);
|
||||
if (ret)
|
||||
if (value != DP_CHANNEL_EQ_BITS) {
|
||||
dev_err(tc->dev, "Lane 1 failed: %x\n", value);
|
||||
ret = -ENODEV;
|
||||
}
|
||||
|
||||
if (!(tmp[2] & DP_INTERLANE_ALIGN_DONE)) {
|
||||
dev_err(tc->dev, "Interlane align failed\n");
|
||||
ret = -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
dev_err(dev, "0x0202 LANE0_1_STATUS: 0x%02x\n", tmp[0]);
|
||||
dev_err(dev, "0x0203 LANE2_3_STATUS 0x%02x\n", tmp[1]);
|
||||
dev_err(dev, "0x0204 LANE_ALIGN_STATUS_UPDATED: 0x%02x\n", tmp[2]);
|
||||
dev_err(dev, "0x0205 SINK_STATUS: 0x%02x\n", tmp[3]);
|
||||
dev_err(dev, "0x0206 ADJUST_REQUEST_LANE0_1: 0x%02x\n", tmp[4]);
|
||||
dev_err(dev, "0x0207 ADJUST_REQUEST_LANE2_3: 0x%02x\n", tmp[5]);
|
||||
goto err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
err_dpcd_read:
|
||||
@@ -1011,39 +1019,84 @@ err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tc_main_link_stream(struct tc_data *tc, int state)
|
||||
static int tc_main_link_disable(struct tc_data *tc)
|
||||
{
|
||||
int ret;
|
||||
|
||||
dev_dbg(tc->dev, "link disable\n");
|
||||
|
||||
tc_write(DP0_SRCCTRL, 0);
|
||||
tc_write(DP0CTL, 0);
|
||||
|
||||
return 0;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tc_stream_enable(struct tc_data *tc)
|
||||
{
|
||||
int ret;
|
||||
u32 value;
|
||||
|
||||
dev_dbg(tc->dev, "stream: %d\n", state);
|
||||
dev_dbg(tc->dev, "enable video stream\n");
|
||||
|
||||
if (state) {
|
||||
value = VID_MN_GEN | DP_EN;
|
||||
if (tc->link.base.capabilities & DP_LINK_CAP_ENHANCED_FRAMING)
|
||||
value |= EF_EN;
|
||||
tc_write(DP0CTL, value);
|
||||
/*
|
||||
* VID_EN assertion should be delayed by at least N * LSCLK
|
||||
* cycles from the time VID_MN_GEN is enabled in order to
|
||||
* generate stable values for VID_M. LSCLK is 270 MHz or
|
||||
* 162 MHz, VID_N is set to 32768 in tc_stream_clock_calc(),
|
||||
* so a delay of at least 203 us should suffice.
|
||||
*/
|
||||
usleep_range(500, 1000);
|
||||
value |= VID_EN;
|
||||
tc_write(DP0CTL, value);
|
||||
/* Set input interface */
|
||||
value = DP0_AUDSRC_NO_INPUT;
|
||||
if (tc_test_pattern)
|
||||
value |= DP0_VIDSRC_COLOR_BAR;
|
||||
else
|
||||
value |= DP0_VIDSRC_DPI_RX;
|
||||
tc_write(SYSCTRL, value);
|
||||
} else {
|
||||
tc_write(DP0CTL, 0);
|
||||
/* PXL PLL setup */
|
||||
if (tc_test_pattern) {
|
||||
ret = tc_pxl_pll_en(tc, clk_get_rate(tc->refclk),
|
||||
1000 * tc->mode.clock);
|
||||
if (ret)
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = tc_set_video_mode(tc, &tc->mode);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Set M/N */
|
||||
ret = tc_stream_clock_calc(tc);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
value = VID_MN_GEN | DP_EN;
|
||||
if (tc->link.base.capabilities & DP_LINK_CAP_ENHANCED_FRAMING)
|
||||
value |= EF_EN;
|
||||
tc_write(DP0CTL, value);
|
||||
/*
|
||||
* VID_EN assertion should be delayed by at least N * LSCLK
|
||||
* cycles from the time VID_MN_GEN is enabled in order to
|
||||
* generate stable values for VID_M. LSCLK is 270 MHz or
|
||||
* 162 MHz, VID_N is set to 32768 in tc_stream_clock_calc(),
|
||||
* so a delay of at least 203 us should suffice.
|
||||
*/
|
||||
usleep_range(500, 1000);
|
||||
value |= VID_EN;
|
||||
tc_write(DP0CTL, value);
|
||||
/* Set input interface */
|
||||
value = DP0_AUDSRC_NO_INPUT;
|
||||
if (tc_test_pattern)
|
||||
value |= DP0_VIDSRC_COLOR_BAR;
|
||||
else
|
||||
value |= DP0_VIDSRC_DPI_RX;
|
||||
tc_write(SYSCTRL, value);
|
||||
|
||||
return 0;
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tc_stream_disable(struct tc_data *tc)
|
||||
{
|
||||
int ret;
|
||||
u32 val;
|
||||
|
||||
dev_dbg(tc->dev, "disable video stream\n");
|
||||
|
||||
tc_read(DP0CTL, &val);
|
||||
val &= ~VID_EN;
|
||||
tc_write(DP0CTL, val);
|
||||
|
||||
tc_pxl_pll_dis(tc);
|
||||
|
||||
return 0;
|
||||
err:
|
||||
return ret;
|
||||
@@ -1061,15 +1114,22 @@ static void tc_bridge_enable(struct drm_bridge *bridge)
|
||||
struct tc_data *tc = bridge_to_tc(bridge);
|
||||
int ret;
|
||||
|
||||
ret = tc_main_link_setup(tc);
|
||||
ret = tc_get_display_props(tc);
|
||||
if (ret < 0) {
|
||||
dev_err(tc->dev, "main link setup error: %d\n", ret);
|
||||
dev_err(tc->dev, "failed to read display props: %d\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = tc_main_link_stream(tc, 1);
|
||||
ret = tc_main_link_enable(tc);
|
||||
if (ret < 0) {
|
||||
dev_err(tc->dev, "main link enable error: %d\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = tc_stream_enable(tc);
|
||||
if (ret < 0) {
|
||||
dev_err(tc->dev, "main link stream start error: %d\n", ret);
|
||||
tc_main_link_disable(tc);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1083,9 +1143,13 @@ static void tc_bridge_disable(struct drm_bridge *bridge)
|
||||
|
||||
drm_panel_disable(tc->panel);
|
||||
|
||||
ret = tc_main_link_stream(tc, 0);
|
||||
ret = tc_stream_disable(tc);
|
||||
if (ret < 0)
|
||||
dev_err(tc->dev, "main link stream stop error: %d\n", ret);
|
||||
|
||||
ret = tc_main_link_disable(tc);
|
||||
if (ret < 0)
|
||||
dev_err(tc->dev, "main link disable error: %d\n", ret);
|
||||
}
|
||||
|
||||
static void tc_bridge_post_disable(struct drm_bridge *bridge)
|
||||
@@ -1107,10 +1171,10 @@ static bool tc_bridge_mode_fixup(struct drm_bridge *bridge,
|
||||
return true;
|
||||
}
|
||||
|
||||
static enum drm_mode_status tc_connector_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
static enum drm_mode_status tc_mode_valid(struct drm_bridge *bridge,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
struct tc_data *tc = connector_to_tc(connector);
|
||||
struct tc_data *tc = bridge_to_tc(bridge);
|
||||
u32 req, avail;
|
||||
u32 bits_per_pixel = 24;
|
||||
|
||||
@@ -1133,7 +1197,7 @@ static void tc_bridge_mode_set(struct drm_bridge *bridge,
|
||||
{
|
||||
struct tc_data *tc = bridge_to_tc(bridge);
|
||||
|
||||
tc->mode = mode;
|
||||
tc->mode = *mode;
|
||||
}
|
||||
|
||||
static int tc_connector_get_modes(struct drm_connector *connector)
|
||||
@@ -1141,6 +1205,13 @@ static int tc_connector_get_modes(struct drm_connector *connector)
|
||||
struct tc_data *tc = connector_to_tc(connector);
|
||||
struct edid *edid;
|
||||
unsigned int count;
|
||||
int ret;
|
||||
|
||||
ret = tc_get_display_props(tc);
|
||||
if (ret < 0) {
|
||||
dev_err(tc->dev, "failed to read display props: %d\n", ret);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (tc->panel && tc->panel->funcs && tc->panel->funcs->get_modes) {
|
||||
count = tc->panel->funcs->get_modes(tc->panel);
|
||||
@@ -1161,29 +1232,40 @@ static int tc_connector_get_modes(struct drm_connector *connector)
|
||||
return count;
|
||||
}
|
||||
|
||||
static void tc_connector_set_polling(struct tc_data *tc,
|
||||
struct drm_connector *connector)
|
||||
{
|
||||
/* TODO: add support for HPD */
|
||||
connector->polled = DRM_CONNECTOR_POLL_CONNECT |
|
||||
DRM_CONNECTOR_POLL_DISCONNECT;
|
||||
}
|
||||
|
||||
static struct drm_encoder *
|
||||
tc_connector_best_encoder(struct drm_connector *connector)
|
||||
{
|
||||
struct tc_data *tc = connector_to_tc(connector);
|
||||
|
||||
return tc->bridge.encoder;
|
||||
}
|
||||
|
||||
static const struct drm_connector_helper_funcs tc_connector_helper_funcs = {
|
||||
.get_modes = tc_connector_get_modes,
|
||||
.mode_valid = tc_connector_mode_valid,
|
||||
.best_encoder = tc_connector_best_encoder,
|
||||
};
|
||||
|
||||
static enum drm_connector_status tc_connector_detect(struct drm_connector *connector,
|
||||
bool force)
|
||||
{
|
||||
struct tc_data *tc = connector_to_tc(connector);
|
||||
bool conn;
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (tc->hpd_pin < 0) {
|
||||
if (tc->panel)
|
||||
return connector_status_connected;
|
||||
else
|
||||
return connector_status_unknown;
|
||||
}
|
||||
|
||||
tc_read(GPIOI, &val);
|
||||
|
||||
conn = val & BIT(tc->hpd_pin);
|
||||
|
||||
if (conn)
|
||||
return connector_status_connected;
|
||||
else
|
||||
return connector_status_disconnected;
|
||||
|
||||
err:
|
||||
return connector_status_unknown;
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs tc_connector_funcs = {
|
||||
.detect = tc_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = drm_connector_cleanup,
|
||||
.reset = drm_atomic_helper_connector_reset,
|
||||
@@ -1198,7 +1280,7 @@ static int tc_bridge_attach(struct drm_bridge *bridge)
|
||||
struct drm_device *drm = bridge->dev;
|
||||
int ret;
|
||||
|
||||
/* Create eDP connector */
|
||||
/* Create DP/eDP connector */
|
||||
drm_connector_helper_add(&tc->connector, &tc_connector_helper_funcs);
|
||||
ret = drm_connector_init(drm, &tc->connector, &tc_connector_funcs,
|
||||
tc->panel ? DRM_MODE_CONNECTOR_eDP :
|
||||
@@ -1206,6 +1288,15 @@ static int tc_bridge_attach(struct drm_bridge *bridge)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Don't poll if don't have HPD connected */
|
||||
if (tc->hpd_pin >= 0) {
|
||||
if (tc->have_irq)
|
||||
tc->connector.polled = DRM_CONNECTOR_POLL_HPD;
|
||||
else
|
||||
tc->connector.polled = DRM_CONNECTOR_POLL_CONNECT |
|
||||
DRM_CONNECTOR_POLL_DISCONNECT;
|
||||
}
|
||||
|
||||
if (tc->panel)
|
||||
drm_panel_attach(tc->panel, &tc->connector);
|
||||
|
||||
@@ -1222,6 +1313,7 @@ static int tc_bridge_attach(struct drm_bridge *bridge)
|
||||
|
||||
static const struct drm_bridge_funcs tc_bridge_funcs = {
|
||||
.attach = tc_bridge_attach,
|
||||
.mode_valid = tc_mode_valid,
|
||||
.mode_set = tc_bridge_mode_set,
|
||||
.pre_enable = tc_bridge_pre_enable,
|
||||
.enable = tc_bridge_enable,
|
||||
@@ -1241,6 +1333,8 @@ static const struct regmap_range tc_volatile_ranges[] = {
|
||||
regmap_reg_range(DP_PHY_CTRL, DP_PHY_CTRL),
|
||||
regmap_reg_range(DP0_PLLCTRL, PXL_PLLCTRL),
|
||||
regmap_reg_range(VFUEN0, VFUEN0),
|
||||
regmap_reg_range(INTSTS_G, INTSTS_G),
|
||||
regmap_reg_range(GPIOI, GPIOI),
|
||||
};
|
||||
|
||||
static const struct regmap_access_table tc_volatile_table = {
|
||||
@@ -1269,6 +1363,49 @@ static const struct regmap_config tc_regmap_config = {
|
||||
.val_format_endian = REGMAP_ENDIAN_LITTLE,
|
||||
};
|
||||
|
||||
static irqreturn_t tc_irq_handler(int irq, void *arg)
|
||||
{
|
||||
struct tc_data *tc = arg;
|
||||
u32 val;
|
||||
int r;
|
||||
|
||||
r = regmap_read(tc->regmap, INTSTS_G, &val);
|
||||
if (r)
|
||||
return IRQ_NONE;
|
||||
|
||||
if (!val)
|
||||
return IRQ_NONE;
|
||||
|
||||
if (val & INT_SYSERR) {
|
||||
u32 stat = 0;
|
||||
|
||||
regmap_read(tc->regmap, SYSSTAT, &stat);
|
||||
|
||||
dev_err(tc->dev, "syserr %x\n", stat);
|
||||
}
|
||||
|
||||
if (tc->hpd_pin >= 0 && tc->bridge.dev) {
|
||||
/*
|
||||
* H is triggered when the GPIO goes high.
|
||||
*
|
||||
* LC is triggered when the GPIO goes low and stays low for
|
||||
* the duration of LCNT
|
||||
*/
|
||||
bool h = val & INT_GPIO_H(tc->hpd_pin);
|
||||
bool lc = val & INT_GPIO_LC(tc->hpd_pin);
|
||||
|
||||
dev_dbg(tc->dev, "GPIO%d: %s %s\n", tc->hpd_pin,
|
||||
h ? "H" : "", lc ? "LC" : "");
|
||||
|
||||
if (h || lc)
|
||||
drm_kms_helper_hotplug_event(tc->bridge.dev);
|
||||
}
|
||||
|
||||
regmap_write(tc->regmap, INTSTS_G, val);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
@@ -1320,6 +1457,33 @@ static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = of_property_read_u32(dev->of_node, "toshiba,hpd-pin",
|
||||
&tc->hpd_pin);
|
||||
if (ret) {
|
||||
tc->hpd_pin = -ENODEV;
|
||||
} else {
|
||||
if (tc->hpd_pin < 0 || tc->hpd_pin > 1) {
|
||||
dev_err(dev, "failed to parse HPD number\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (client->irq > 0) {
|
||||
/* enable SysErr */
|
||||
regmap_write(tc->regmap, INTCTL_G, INT_SYSERR);
|
||||
|
||||
ret = devm_request_threaded_irq(dev, client->irq,
|
||||
NULL, tc_irq_handler,
|
||||
IRQF_ONESHOT,
|
||||
"tc358767-irq", tc);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to register dp interrupt\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
tc->have_irq = true;
|
||||
}
|
||||
|
||||
ret = regmap_read(tc->regmap, TC_IDREG, &tc->rev);
|
||||
if (ret) {
|
||||
dev_err(tc->dev, "can not read device ID: %d\n", ret);
|
||||
@@ -1333,6 +1497,22 @@ static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
|
||||
tc->assr = (tc->rev == 0x6601); /* Enable ASSR for eDP panels */
|
||||
|
||||
if (tc->hpd_pin >= 0) {
|
||||
u32 lcnt_reg = tc->hpd_pin == 0 ? INT_GP0_LCNT : INT_GP1_LCNT;
|
||||
u32 h_lc = INT_GPIO_H(tc->hpd_pin) | INT_GPIO_LC(tc->hpd_pin);
|
||||
|
||||
/* Set LCNT to 2ms */
|
||||
regmap_write(tc->regmap, lcnt_reg,
|
||||
clk_get_rate(tc->refclk) * 2 / 1000);
|
||||
/* We need the "alternate" mode for HPD */
|
||||
regmap_write(tc->regmap, GPIOM, BIT(tc->hpd_pin));
|
||||
|
||||
if (tc->have_irq) {
|
||||
/* enable H & LC */
|
||||
regmap_update_bits(tc->regmap, INTCTL_G, h_lc, h_lc);
|
||||
}
|
||||
}
|
||||
|
||||
ret = tc_aux_link_setup(tc);
|
||||
if (ret)
|
||||
return ret;
|
||||
@@ -1345,12 +1525,6 @@ static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = tc_get_display_props(tc);
|
||||
if (ret)
|
||||
goto err_unregister_aux;
|
||||
|
||||
tc_connector_set_polling(tc, &tc->connector);
|
||||
|
||||
tc->bridge.funcs = &tc_bridge_funcs;
|
||||
tc->bridge.of_node = dev->of_node;
|
||||
drm_bridge_add(&tc->bridge);
|
||||
@@ -1358,9 +1532,6 @@ static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
i2c_set_clientdata(client, tc);
|
||||
|
||||
return 0;
|
||||
err_unregister_aux:
|
||||
drm_dp_aux_unregister(&tc->aux);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tc_remove(struct i2c_client *client)
|
||||
@@ -1370,8 +1541,6 @@ static int tc_remove(struct i2c_client *client)
|
||||
drm_bridge_remove(&tc->bridge);
|
||||
drm_dp_aux_unregister(&tc->aux);
|
||||
|
||||
tc_pxl_pll_dis(tc);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@@ -5,15 +5,17 @@
|
||||
* Copyright (C) 2018 Jacopo Mondi <jacopo+renesas@jmondi.org>
|
||||
*/
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_bridge.h>
|
||||
#include <drm/drm_panel.h>
|
||||
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <drm/drm_bridge.h>
|
||||
#include <drm/drm_panel.h>
|
||||
|
||||
enum thc63_ports {
|
||||
THC63_LVDS_IN0,
|
||||
THC63_LVDS_IN1,
|
||||
@@ -31,6 +33,8 @@ struct thc63_dev {
|
||||
|
||||
struct drm_bridge bridge;
|
||||
struct drm_bridge *next;
|
||||
|
||||
struct drm_bridge_timings timings;
|
||||
};
|
||||
|
||||
static inline struct thc63_dev *to_thc63(struct drm_bridge *bridge)
|
||||
@@ -48,15 +52,28 @@ static int thc63_attach(struct drm_bridge *bridge)
|
||||
static enum drm_mode_status thc63_mode_valid(struct drm_bridge *bridge,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
struct thc63_dev *thc63 = to_thc63(bridge);
|
||||
unsigned int min_freq;
|
||||
unsigned int max_freq;
|
||||
|
||||
/*
|
||||
* The THC63LVD1024 clock frequency range is 8 to 135 MHz in single-in
|
||||
* mode. Note that the limits are different in dual-in, single-out mode,
|
||||
* and will need to be adjusted accordingly.
|
||||
* The THC63LVD1024 pixel rate range is 8 to 135 MHz in all modes but
|
||||
* dual-in, single-out where it is 40 to 150 MHz. As dual-in, dual-out
|
||||
* isn't supported by the driver yet, simply derive the limits from the
|
||||
* input mode.
|
||||
*/
|
||||
if (mode->clock < 8000)
|
||||
if (thc63->timings.dual_link) {
|
||||
min_freq = 40000;
|
||||
max_freq = 150000;
|
||||
} else {
|
||||
min_freq = 8000;
|
||||
max_freq = 135000;
|
||||
}
|
||||
|
||||
if (mode->clock < min_freq)
|
||||
return MODE_CLOCK_LOW;
|
||||
|
||||
if (mode->clock > 135000)
|
||||
if (mode->clock > max_freq)
|
||||
return MODE_CLOCK_HIGH;
|
||||
|
||||
return MODE_OK;
|
||||
@@ -101,19 +118,19 @@ static const struct drm_bridge_funcs thc63_bridge_func = {
|
||||
|
||||
static int thc63_parse_dt(struct thc63_dev *thc63)
|
||||
{
|
||||
struct device_node *thc63_out;
|
||||
struct device_node *endpoint;
|
||||
struct device_node *remote;
|
||||
|
||||
thc63_out = of_graph_get_endpoint_by_regs(thc63->dev->of_node,
|
||||
THC63_RGB_OUT0, -1);
|
||||
if (!thc63_out) {
|
||||
endpoint = of_graph_get_endpoint_by_regs(thc63->dev->of_node,
|
||||
THC63_RGB_OUT0, -1);
|
||||
if (!endpoint) {
|
||||
dev_err(thc63->dev, "Missing endpoint in port@%u\n",
|
||||
THC63_RGB_OUT0);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
remote = of_graph_get_remote_port_parent(thc63_out);
|
||||
of_node_put(thc63_out);
|
||||
remote = of_graph_get_remote_port_parent(endpoint);
|
||||
of_node_put(endpoint);
|
||||
if (!remote) {
|
||||
dev_err(thc63->dev, "Endpoint in port@%u unconnected\n",
|
||||
THC63_RGB_OUT0);
|
||||
@@ -132,6 +149,22 @@ static int thc63_parse_dt(struct thc63_dev *thc63)
|
||||
if (!thc63->next)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
endpoint = of_graph_get_endpoint_by_regs(thc63->dev->of_node,
|
||||
THC63_LVDS_IN1, -1);
|
||||
if (endpoint) {
|
||||
remote = of_graph_get_remote_port_parent(endpoint);
|
||||
of_node_put(endpoint);
|
||||
|
||||
if (remote) {
|
||||
if (of_device_is_available(remote))
|
||||
thc63->timings.dual_link = true;
|
||||
of_node_put(remote);
|
||||
}
|
||||
}
|
||||
|
||||
dev_dbg(thc63->dev, "operating in %s-link mode\n",
|
||||
thc63->timings.dual_link ? "dual" : "single");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -188,6 +221,7 @@ static int thc63_probe(struct platform_device *pdev)
|
||||
thc63->bridge.driver_private = thc63;
|
||||
thc63->bridge.of_node = pdev->dev.of_node;
|
||||
thc63->bridge.funcs = &thc63_bridge_func;
|
||||
thc63->bridge.timings = &thc63->timings;
|
||||
|
||||
drm_bridge_add(&thc63->bridge);
|
||||
|
||||
|
@@ -3,22 +3,24 @@
|
||||
* Copyright (c) 2018, The Linux Foundation. All rights reserved.
|
||||
*/
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_dp_helper.h>
|
||||
#include <drm/drm_mipi_dsi.h>
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/iopoll.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#define SN_DEVICE_REV_REG 0x08
|
||||
#define SN_DPPLL_SRC_REG 0x0A
|
||||
|
@@ -7,15 +7,15 @@
|
||||
#include <linux/delay.h>
|
||||
#include <linux/fwnode.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/i2c.h>
|
||||
|
||||
#include <drm/drmP.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
|
||||
#define HOTPLUG_DEBOUNCE_MS 1100
|
||||
@@ -66,7 +66,12 @@ static int tfp410_get_modes(struct drm_connector *connector)
|
||||
|
||||
drm_connector_update_edid_property(connector, edid);
|
||||
|
||||
return drm_add_edid_modes(connector, edid);
|
||||
ret = drm_add_edid_modes(connector, edid);
|
||||
|
||||
kfree(edid);
|
||||
|
||||
return ret;
|
||||
|
||||
fallback:
|
||||
/* No EDID, fallback on the XGA standard modes */
|
||||
ret = drm_add_modes_noedid(connector, 1920, 1200);
|
||||
@@ -372,7 +377,8 @@ static int tfp410_fini(struct device *dev)
|
||||
{
|
||||
struct tfp410 *dvi = dev_get_drvdata(dev);
|
||||
|
||||
cancel_delayed_work_sync(&dvi->hpd_work);
|
||||
if (dvi->hpd_irq >= 0)
|
||||
cancel_delayed_work_sync(&dvi->hpd_work);
|
||||
|
||||
drm_bridge_remove(&dvi->bridge);
|
||||
|
||||
|
Reference in New Issue
Block a user