Merge remote-tracking branch 'drm/drm-next' into drm-misc-next-fixes

Some fixes have been accidentally pushed to this, so I cannot fost-forward.
Required to pull in the remove-fbcon-notifiers fixes.

Signed-off-by: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
This commit is contained in:
Maarten Lankhorst
2019-06-26 12:22:54 +02:00
26546 changed files with 564256 additions and 462697 deletions

View File

@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_BRIDGE
def_bool y
depends on DRM
@@ -76,7 +77,6 @@ config DRM_PARADE_PS8622
depends on OF
select DRM_PANEL
select DRM_KMS_HELPER
select BACKLIGHT_LCD_SUPPORT
select BACKLIGHT_CLASS_DEVICE
---help---
Parade eDP-LVDS bridge chip driver.

View File

@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_I2C_ADV7511
tristate "ADV7511 encoder"
depends on OF

View File

@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
adv7511-y := adv7511_drv.o
adv7511-$(CONFIG_DRM_I2C_ADV7511_AUDIO) += adv7511_audio.o
adv7511-$(CONFIG_DRM_I2C_ADV7511_CEC) += adv7511_cec.o

View File

@@ -1,9 +1,8 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Analog Devices ADV7511 HDMI transmitter driver
*
* Copyright 2012 Analog Devices Inc.
*
* Licensed under the GPL-2.
*/
#ifndef __DRM_I2C_ADV7511_H__

View File

@@ -1,10 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Analog Devices ADV7511 HDMI transmitter driver
*
* Copyright 2012 Analog Devices Inc.
* Copyright (c) 2016, Linaro Limited
*
* Licensed under the GPL-2.
*/
#include <sound/core.h>

View File

@@ -1,26 +1,25 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Analog Devices ADV7511 HDMI transmitter driver
*
* Copyright 2012 Analog Devices Inc.
*
* Licensed under the GPL-2.
*/
#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. */

View File

@@ -1,14 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* 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>

View File

@@ -1,38 +1,28 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright(c) 2016, Analogix Semiconductor.
*
* 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.
*
* Based on anx7808 driver obtained from chromeos with copyright:
* Copyright(c) 2013, Google Inc.
*
*/
#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"

View File

@@ -1,15 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright(c) 2016, Analogix Semiconductor. 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.
*
*/
#ifndef __ANX78xx_H

View File

@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_ANALOGIX_DP
tristate
depends on DRM

View File

@@ -1,2 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
analogix_dp-objs := analogix_dp_core.o analogix_dp_reg.o
obj-$(CONFIG_DRM_ANALOGIX_DP) += analogix_dp.o

View File

@@ -1,35 +1,30 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Analogix DP (Display Port) core interface driver.
*
* Copyright (C) 2012 Samsung Electronics Co., Ltd.
* Author: Jingoo Han <jg1.han@samsung.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/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"
@@ -115,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;
@@ -127,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);
}
@@ -136,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)
@@ -149,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) {
@@ -1411,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;
@@ -1585,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
@@ -1598,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;
}

View File

@@ -1,13 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Header file for Analogix DP (Display Port) core interface driver.
*
* Copyright (C) 2012 Samsung Electronics Co., Ltd.
* Author: Jingoo Han <jg1.han@samsung.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 _ANALOGIX_DP_CORE_H
@@ -38,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,
@@ -171,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;
@@ -254,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);

View File

@@ -1,18 +1,14 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Analogix DP (Display port) core register interface driver.
*
* Copyright (C) 2012 Samsung Electronics Co., Ltd.
* Author: Jingoo Han <jg1.han@samsung.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/io.h>
#include <linux/iopoll.h>
@@ -397,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;
@@ -411,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);
@@ -434,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
@@ -507,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);
@@ -1041,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;
@@ -1069,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);
@@ -1092,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);

View File

@@ -1,13 +1,9 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2015-2016 Free Electrons
* Copyright (C) 2015-2016 NextThing Co
*
* Maxime Ripard <maxime.ripard@free-electrons.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*/
#include <linux/module.h>
@@ -15,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 {

View File

@@ -1,19 +1,17 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2016 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 <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;

View File

@@ -1,3 +1,4 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Driver for MegaChips STDP4028 with GE B850v3 firmware (LVDS-DP)
* Driver for MegaChips STDP2690 with GE B850v3 firmware (DP-DP++)
@@ -5,17 +6,6 @@
* Copyright (c) 2017, Collabora Ltd.
* Copyright (c) 2017, General Electric Company
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* This driver creates a drm_bridge and a drm_connector for the LVDS to DP++
* display bridge of the GE B850v3. There are two physical bridges on the video
@@ -27,18 +17,18 @@
* signal pipeline is as follows:
*
* Host -> LVDS|--(STDP4028)--|DP -> DP|--(STDP2690)--|DP++ -> Video output
*
*/
#include <linux/gpio.h>
#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

View File

@@ -1,16 +1,8 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* NXP PTN3460 DP/LVDS bridge driver
*
* Copyright (C) 2013 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/delay.h>
@@ -20,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

View File

@@ -1,21 +1,16 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2016 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
* Copyright (C) 2017 Broadcom
*
* 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_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;

View File

@@ -1,16 +1,8 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Parade PS8622 eDP/LVDS bridge driver
*
* Copyright (C) 2014 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/backlight.h>
@@ -24,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

View File

@@ -1,3 +1,4 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2018 Renesas Electronics
*
@@ -8,18 +9,7 @@
* Boris Brezillon <boris.brezillon@free-electrons.com>
* Wu, Songjun <Songjun.Wu@atmel.com>
*
*
* Copyright (C) 2010-2011 Freescale Semiconductor, Inc. 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 as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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/gpio/consumer.h>
@@ -27,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
@@ -74,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)
@@ -81,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 {
@@ -90,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)
@@ -161,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;
}
@@ -180,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);
}
@@ -193,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,
@@ -215,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,
@@ -239,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;
@@ -254,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)
@@ -315,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(&params->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 },
};
@@ -327,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,
};
@@ -336,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);
@@ -460,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)
{
@@ -493,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);
@@ -530,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,

View File

@@ -1,3 +1,4 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (C) 2017 Samsung Electronics
*
@@ -10,20 +11,6 @@
* Erik Gilling <konkers@android.com>
* Shankar Bandal <shankar.b@samsung.com>
* Dharam Kumar <dharam.kr@samsung.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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program
*
*/
#include <drm/bridge/mhl.h>
#include <drm/drm_crtc.h>
@@ -828,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) {
@@ -910,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;

View File

@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
config DRM_DW_HDMI
tristate
select DRM_KMS_HELPER

View File

@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_DRM_DW_HDMI) += dw-hdmi.o
obj-$(CONFIG_DRM_DW_HDMI_AHB_AUDIO) += dw-hdmi-ahb-audio.o
obj-$(CONFIG_DRM_DW_HDMI_I2S_AUDIO) += dw-hdmi-i2s-audio.o

View File

@@ -1,44 +1,41 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* DesignWare High-Definition Multimedia Interface (HDMI) driver
*
* Copyright (C) 2013-2015 Mentor Graphics Inc.
* Copyright (C) 2011-2013 Freescale Semiconductor, Inc.
* Copyright (C) 2010, Guennadi Liakhovetski <g.liakhovetski@gmx.de>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
*/
#include <linux/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
@@ -169,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 */
@@ -227,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);
@@ -247,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");
@@ -270,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);
}
@@ -289,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 */
@@ -307,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;
@@ -1046,6 +1117,10 @@ static bool dw_hdmi_support_scdc(struct dw_hdmi *hdmi)
if (hdmi->version < 0x200a)
return false;
/* Disable if no DDC bus */
if (!hdmi->ddc)
return false;
/* Disable if SCDC is not supported, or if an HF-VSDB block is absent */
if (!display->hdmi.scdc.supported ||
!display->hdmi.scdc.scrambling.supported)
@@ -1684,13 +1759,13 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
* Source Devices compliant shall set the
* Source Version = 1.
*/
drm_scdc_readb(&hdmi->i2c->adap, SCDC_SINK_VERSION,
drm_scdc_readb(hdmi->ddc, SCDC_SINK_VERSION,
&bytes);
drm_scdc_writeb(&hdmi->i2c->adap, SCDC_SOURCE_VERSION,
drm_scdc_writeb(hdmi->ddc, SCDC_SOURCE_VERSION,
min_t(u8, bytes, SCDC_MIN_SOURCE_VERSION));
/* Enabled Scrambling in the Sink */
drm_scdc_set_scrambling(&hdmi->i2c->adap, 1);
drm_scdc_set_scrambling(hdmi->ddc, 1);
/*
* To activate the scrambler feature, you must ensure
@@ -1706,7 +1781,7 @@ static void hdmi_av_composer(struct dw_hdmi *hdmi,
hdmi_writeb(hdmi, 0, HDMI_FC_SCRAMBLER_CTRL);
hdmi_writeb(hdmi, (u8)~HDMI_MC_SWRSTZ_TMDSSWRST_REQ,
HDMI_MC_SWRSTZ);
drm_scdc_set_scrambling(&hdmi->i2c->adap, 0);
drm_scdc_set_scrambling(hdmi->ddc, 0);
}
}
@@ -1800,6 +1875,8 @@ static void dw_hdmi_clear_overflow(struct dw_hdmi *hdmi)
* iteration for others.
* The Amlogic Meson GX SoCs (v2.01a) have been identified as needing
* the workaround with a single iteration.
* The Rockchip RK3288 SoC (v2.00a) and RK3328/RK3399 SoCs (v2.11a) have
* been identified as needing the workaround with a single iteration.
*/
switch (hdmi->version) {
@@ -1808,7 +1885,9 @@ static void dw_hdmi_clear_overflow(struct dw_hdmi *hdmi)
break;
case 0x131a:
case 0x132a:
case 0x200a:
case 0x201a:
case 0x211a:
case 0x212a:
count = 1;
break;
@@ -1917,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;
@@ -2427,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)
@@ -2578,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) {
@@ -2606,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;
@@ -2617,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;
@@ -2673,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:
@@ -2780,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>");

View File

@@ -1,10 +1,6 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2011 Freescale Semiconductor, Inc.
*
* 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 __DW_HDMI_H__

View File

@@ -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

View File

@@ -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))

View File

@@ -1,3 +1,4 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* tc358767 eDP bridge driver
*
@@ -12,16 +13,6 @@
*
* Copyright (C) 2012 Texas Instruments
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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/clk.h>
@@ -71,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)
@@ -78,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
@@ -186,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 {
@@ -208,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;
@@ -216,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)
@@ -286,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;
}
@@ -387,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)
@@ -545,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) {
@@ -570,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,
@@ -590,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 |
@@ -627,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)
@@ -646,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);
@@ -744,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;
@@ -837,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 */
@@ -872,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);
@@ -881,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);
@@ -934,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;
}
}
@@ -948,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 */
@@ -968,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:
@@ -1020,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;
@@ -1070,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;
}
@@ -1092,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)
@@ -1116,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;
@@ -1142,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)
@@ -1150,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);
@@ -1170,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,
@@ -1207,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 :
@@ -1215,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);
@@ -1231,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,
@@ -1250,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 = {
@@ -1278,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;
@@ -1329,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);
@@ -1342,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;
@@ -1354,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);
@@ -1367,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)
@@ -1379,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;
}

View File

@@ -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);

View File

@@ -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

View File

@@ -11,15 +11,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