From 8401e2edf77097bb6ac6ddac4aa7fb1aecd8e01a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Thu, 16 Jul 2020 10:11:13 +0200 Subject: drm/radeon: stop using TTM_MEMTYPE_FLAG_MAPPABLE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver doesn't expose any not-mapable memory resources. Signed-off-by: Christian König Reviewed-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/378239/ --- drivers/gpu/drm/radeon/radeon_ttm.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/drivers/gpu/drm/radeon/radeon_ttm.c b/drivers/gpu/drm/radeon/radeon_ttm.c index 54af06df865b..b474781a0920 100644 --- a/drivers/gpu/drm/radeon/radeon_ttm.c +++ b/drivers/gpu/drm/radeon/radeon_ttm.c @@ -76,7 +76,7 @@ static int radeon_init_mem_type(struct ttm_bo_device *bdev, uint32_t type, switch (type) { case TTM_PL_SYSTEM: /* System memory */ - man->flags = TTM_MEMTYPE_FLAG_MAPPABLE; + man->flags = 0; man->available_caching = TTM_PL_MASK_CACHING; man->default_caching = TTM_PL_FLAG_CACHED; break; @@ -84,7 +84,7 @@ static int radeon_init_mem_type(struct ttm_bo_device *bdev, uint32_t type, man->func = &ttm_bo_manager_func; man->available_caching = TTM_PL_MASK_CACHING; man->default_caching = TTM_PL_FLAG_CACHED; - man->flags = TTM_MEMTYPE_FLAG_MAPPABLE; + man->flags = 0; #if IS_ENABLED(CONFIG_AGP) if (rdev->flags & RADEON_IS_AGP) { if (!rdev->ddev->agp) { @@ -92,8 +92,6 @@ static int radeon_init_mem_type(struct ttm_bo_device *bdev, uint32_t type, (unsigned)type); return -EINVAL; } - if (!rdev->ddev->agp->cant_use_aperture) - man->flags = TTM_MEMTYPE_FLAG_MAPPABLE; man->available_caching = TTM_PL_FLAG_UNCACHED | TTM_PL_FLAG_WC; man->default_caching = TTM_PL_FLAG_WC; @@ -103,8 +101,7 @@ static int radeon_init_mem_type(struct ttm_bo_device *bdev, uint32_t type, case TTM_PL_VRAM: /* "On-card" video ram */ man->func = &ttm_bo_manager_func; - man->flags = TTM_MEMTYPE_FLAG_FIXED | - TTM_MEMTYPE_FLAG_MAPPABLE; + man->flags = TTM_MEMTYPE_FLAG_FIXED; man->available_caching = TTM_PL_FLAG_UNCACHED | TTM_PL_FLAG_WC; man->default_caching = TTM_PL_FLAG_WC; break; @@ -394,7 +391,6 @@ memcpy: static int radeon_ttm_io_mem_reserve(struct ttm_bo_device *bdev, struct ttm_mem_reg *mem) { - struct ttm_mem_type_manager *man = &bdev->man[mem->mem_type]; struct radeon_device *rdev = radeon_get_rdev(bdev); mem->bus.addr = NULL; @@ -402,8 +398,7 @@ static int radeon_ttm_io_mem_reserve(struct ttm_bo_device *bdev, struct ttm_mem_ mem->bus.size = mem->num_pages << PAGE_SHIFT; mem->bus.base = 0; mem->bus.is_iomem = false; - if (!(man->flags & TTM_MEMTYPE_FLAG_MAPPABLE)) - return -EINVAL; + switch (mem->mem_type) { case TTM_PL_SYSTEM: /* system memory */ -- cgit v1.2.3 From 902d77f3568a9f35493e1ebcfe25ba32cd3489e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Thu, 16 Jul 2020 10:28:04 +0200 Subject: drm/amdgpu: stop using TTM_MEMTYPE_FLAG_MAPPABLE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver does support some not-mapable resources, but those are already handled correctly in the switch/case statement in the code. Signed-off-by: Christian König Reviewed-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/378243/ --- drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c index 44fa8bc49d18..0dd5e802091d 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_ttm.c @@ -84,7 +84,7 @@ static int amdgpu_init_mem_type(struct ttm_bo_device *bdev, uint32_t type, switch (type) { case TTM_PL_SYSTEM: /* System memory */ - man->flags = TTM_MEMTYPE_FLAG_MAPPABLE; + man->flags = 0; man->available_caching = TTM_PL_MASK_CACHING; man->default_caching = TTM_PL_FLAG_CACHED; break; @@ -93,13 +93,12 @@ static int amdgpu_init_mem_type(struct ttm_bo_device *bdev, uint32_t type, man->func = &amdgpu_gtt_mgr_func; man->available_caching = TTM_PL_MASK_CACHING; man->default_caching = TTM_PL_FLAG_CACHED; - man->flags = TTM_MEMTYPE_FLAG_MAPPABLE; + man->flags = 0; break; case TTM_PL_VRAM: /* "On-card" video ram */ man->func = &amdgpu_vram_mgr_func; - man->flags = TTM_MEMTYPE_FLAG_FIXED | - TTM_MEMTYPE_FLAG_MAPPABLE; + man->flags = TTM_MEMTYPE_FLAG_FIXED; man->available_caching = TTM_PL_FLAG_UNCACHED | TTM_PL_FLAG_WC; man->default_caching = TTM_PL_FLAG_WC; break; @@ -796,7 +795,6 @@ memcpy: */ static int amdgpu_ttm_io_mem_reserve(struct ttm_bo_device *bdev, struct ttm_mem_reg *mem) { - struct ttm_mem_type_manager *man = &bdev->man[mem->mem_type]; struct amdgpu_device *adev = amdgpu_ttm_adev(bdev); struct drm_mm_node *mm_node = mem->mm_node; @@ -805,8 +803,7 @@ static int amdgpu_ttm_io_mem_reserve(struct ttm_bo_device *bdev, struct ttm_mem_ mem->bus.size = mem->num_pages << PAGE_SHIFT; mem->bus.base = 0; mem->bus.is_iomem = false; - if (!(man->flags & TTM_MEMTYPE_FLAG_MAPPABLE)) - return -EINVAL; + switch (mem->mem_type) { case TTM_PL_SYSTEM: /* system memory */ -- cgit v1.2.3 From 9fadd6d1e2977bbd449d4fb99cde41ed6f71f668 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20K=C3=B6nig?= Date: Thu, 16 Jul 2020 14:02:08 +0200 Subject: drm/ttm: remove TTM_MEMTYPE_FLAG_MAPPABLE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not used any more. And it is bad design to use a TTM flag to do a check inside a driver. Signed-off-by: Christian König Reviewed-by: Daniel Vetter Link: https://patchwork.freedesktop.org/patch/378245/ --- include/drm/ttm/ttm_bo_driver.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/drm/ttm/ttm_bo_driver.h b/include/drm/ttm/ttm_bo_driver.h index 71b195e78c7c..9b251853afe2 100644 --- a/include/drm/ttm/ttm_bo_driver.h +++ b/include/drm/ttm/ttm_bo_driver.h @@ -46,7 +46,6 @@ #define TTM_MAX_BO_PRIORITY 4U #define TTM_MEMTYPE_FLAG_FIXED (1 << 0) /* Fixed (on-card) PCI memory */ -#define TTM_MEMTYPE_FLAG_MAPPABLE (1 << 1) /* Memory mappable */ struct ttm_mem_type_manager; -- cgit v1.2.3 From 4031ead37239ec397fb9441555ae2d60e46dc333 Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Mon, 20 Jul 2020 15:03:27 +0200 Subject: drm: pl111: Update documentation Remove notes about migrating from the old driver which is retired as all users are now migrated. Update the text to reflect that we support PL110 and PL111 alike. Drop the bullet on memory bandwidth scaling: this has been implemented. Signed-off-by: Linus Walleij Acked-by: Daniel Vetter Cc: Russell King Link: https://patchwork.freedesktop.org/patch/msgid/20200720130327.92364-1-linus.walleij@linaro.org --- Documentation/gpu/pl111.rst | 8 ++++---- drivers/gpu/drm/pl111/pl111_drv.c | 20 +++++--------------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/Documentation/gpu/pl111.rst b/Documentation/gpu/pl111.rst index 9b03736d33dd..6d9a1b59a545 100644 --- a/Documentation/gpu/pl111.rst +++ b/Documentation/gpu/pl111.rst @@ -1,6 +1,6 @@ -========================================== - drm/pl111 ARM PrimeCell PL111 CLCD Driver -========================================== +==================================================== + drm/pl111 ARM PrimeCell PL110 and PL111 CLCD Driver +==================================================== .. kernel-doc:: drivers/gpu/drm/pl111/pl111_drv.c - :doc: ARM PrimeCell PL111 CLCD Driver + :doc: ARM PrimeCell PL110 and PL111 CLCD Driver diff --git a/drivers/gpu/drm/pl111/pl111_drv.c b/drivers/gpu/drm/pl111/pl111_drv.c index 96e58fda75d8..46b0d1c4a16c 100644 --- a/drivers/gpu/drm/pl111/pl111_drv.c +++ b/drivers/gpu/drm/pl111/pl111_drv.c @@ -10,18 +10,11 @@ */ /** - * DOC: ARM PrimeCell PL111 CLCD Driver + * DOC: ARM PrimeCell PL110 and PL111 CLCD Driver * - * The PL111 is a simple LCD controller that can support TFT and STN - * displays. This driver exposes a standard KMS interface for them. - * - * This driver uses the same Device Tree binding as the fbdev CLCD - * driver. While the fbdev driver supports panels that may be - * connected to the CLCD internally to the CLCD driver, in DRM the - * panels get split out to drivers/gpu/drm/panels/. This means that, - * in converting from using fbdev to using DRM, you also need to write - * a panel driver (which may be as simple as an entry in - * panel-simple.c). + * The PL110/PL111 is a simple LCD controller that can support TFT + * and STN displays. This driver exposes a standard KMS interface + * for them. * * The driver currently doesn't expose the cursor. The DRM API for * cursors requires support for 64x64 ARGB8888 cursor images, while @@ -29,16 +22,13 @@ * cursors. While one could imagine trying to hack something together * to look at the ARGB8888 and program reasonable in monochrome, we * just don't expose the cursor at all instead, and leave cursor - * support to the X11 software cursor layer. + * support to the application software cursor layer. * * TODO: * * - Fix race between setting plane base address and getting IRQ for * vsync firing the pageflip completion. * - * - Use the "max-memory-bandwidth" DT property to filter the - * supported formats. - * * - Read back hardware state at boot to skip reprogramming the * hardware when doing a no-op modeset. * -- cgit v1.2.3 From 6142b1b82ce43e6f7663f2e148ca48808f9d698f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Syrj=C3=A4l=C3=A4?= Date: Fri, 17 Jul 2020 16:54:39 +0300 Subject: drm/todo: Plumb drm_atomic_state all over MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a TODO for plumbing drm_atomic_state all over to ease the hurdles of accessing additional object states. Reviewed-by: Daniel Vetter #irc Signed-off-by: Ville Syrjälä Link: https://patchwork.freedesktop.org/patch/msgid/20200717135439.5996-1-ville.syrjala@linux.intel.com --- Documentation/gpu/todo.rst | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Documentation/gpu/todo.rst b/Documentation/gpu/todo.rst index 7969f106877d..b0ea17da8ff6 100644 --- a/Documentation/gpu/todo.rst +++ b/Documentation/gpu/todo.rst @@ -403,6 +403,52 @@ Contact: Emil Velikov, respective driver maintainers Level: Intermediate +Plumb drm_atomic_state all over +------------------------------- + +Currently various atomic functions take just a single or a handful of +object states (eg. plane state). While that single object state can +suffice for some simple cases, we often have to dig out additional +object states for dealing with various dependencies between the individual +objects or the hardware they represent. The process of digging out the +additional states is rather non-intuitive and error prone. + +To fix that most functions should rather take the overall +drm_atomic_state as one of their parameters. The other parameters +would generally be the object(s) we mainly want to interact with. + +For example, instead of + +.. code-block:: c + + int (*atomic_check)(struct drm_plane *plane, struct drm_plane_state *state); + +we would have something like + +.. code-block:: c + + int (*atomic_check)(struct drm_plane *plane, struct drm_atomic_state *state); + +The implementation can then trivially gain access to any required object +state(s) via drm_atomic_get_plane_state(), drm_atomic_get_new_plane_state(), +drm_atomic_get_old_plane_state(), and their equivalents for +other object types. + +Additionally many drivers currently access the object->state pointer +directly in their commit functions. That is not going to work if we +eg. want to allow deeper commit pipelines as those pointers could +then point to the states corresponding to a future commit instead of +the current commit we're trying to process. Also non-blocking commits +execute locklessly so there are serious concerns with dereferencing +the object->state pointers without holding the locks that protect them. +Use of drm_atomic_get_new_plane_state(), drm_atomic_get_old_plane_state(), +etc. avoids these problems as well since they relate to a specific +commit via the passed in drm_atomic_state. + +Contact: Ville Syrjälä, Daniel Vetter + +Level: Intermediate + Core refactorings ================= -- cgit v1.2.3 From 0d3df1c603de993163c6c9906373e8dde352c56a Mon Sep 17 00:00:00 2001 From: Vinod Koul Date: Thu, 23 Jul 2020 22:04:40 +0530 Subject: dt-bindings: vendor-prefixes: Add Lontium vendor prefix Add prefix for Lontium Semiconductor Corporation Acked-by: Rob Herring Tested-by: John Stultz Signed-off-by: Vinod Koul Signed-off-by: Sam Ravnborg Link: https://patchwork.freedesktop.org/patch/msgid/20200723163442.1280414-2-vkoul@kernel.org --- Documentation/devicetree/bindings/vendor-prefixes.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml index 740b116b179f..caf7dbed564c 100644 --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml @@ -595,6 +595,8 @@ patternProperties: description: Logic Technologies Limited "^longcheer,.*": description: Longcheer Technology (Shanghai) Co., Ltd. + "^lontium,.*": + description: Lontium Semiconductor Corporation "^loongson,.*": description: Loongson Technology Corporation Limited "^lsi,.*": -- cgit v1.2.3 From 8a72a2de0a4fc727970a06c565f1112ae82ed132 Mon Sep 17 00:00:00 2001 From: Vinod Koul Date: Thu, 23 Jul 2020 22:04:41 +0530 Subject: dt-bindings: display: bridge: Add documentation for LT9611 Lontium LT9611 is a DSI to HDMI bridge which supports 2 DSI ports and I2S port as input and one HDMI port as output Reviewed-by: Rob Herring Tested-by: John Stultz Signed-off-by: Vinod Koul Signed-off-by: Sam Ravnborg Link: https://patchwork.freedesktop.org/patch/msgid/20200723163442.1280414-3-vkoul@kernel.org --- .../bindings/display/bridge/lontium,lt9611.yaml | 176 +++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml diff --git a/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml new file mode 100644 index 000000000000..d60208359234 --- /dev/null +++ b/Documentation/devicetree/bindings/display/bridge/lontium,lt9611.yaml @@ -0,0 +1,176 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/display/bridge/lontium,lt9611.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Lontium LT9611 2 Port MIPI to HDMI Bridge + +maintainers: + - Vinod Koul + +description: | + The LT9611 is a bridge device which converts DSI to HDMI + +properties: + compatible: + enum: + - lontium,lt9611 + + reg: + maxItems: 1 + + "#sound-dai-cells": + const: 1 + + interrupts: + maxItems: 1 + + reset-gpios: + maxItems: 1 + description: GPIO connected to active high RESET pin. + + vdd-supply: + description: Regulator for 1.8V MIPI phy power. + + vcc-supply: + description: Regulator for 3.3V IO power. + + ports: + type: object + + properties: + "#address-cells": + const: 1 + + "#size-cells": + const: 0 + + port@0: + type: object + description: | + Primary MIPI port-1 for MIPI input + + properties: + reg: + const: 0 + + patternProperties: + "^endpoint(@[0-9])$": + type: object + additionalProperties: false + + properties: + remote-endpoint: + $ref: /schemas/types.yaml#/definitions/phandle + + required: + - reg + + port@1: + type: object + description: | + Additional MIPI port-2 for MIPI input, used in combination + with primary MIPI port-1 to drive higher resolution displays + + properties: + reg: + const: 1 + + patternProperties: + "^endpoint(@[0-9])$": + type: object + additionalProperties: false + + properties: + remote-endpoint: + $ref: /schemas/types.yaml#/definitions/phandle + + required: + - reg + + port@2: + type: object + description: | + HDMI port for HDMI output + + properties: + reg: + const: 2 + + patternProperties: + "^endpoint(@[0-9])$": + type: object + additionalProperties: false + + properties: + remote-endpoint: + $ref: /schemas/types.yaml#/definitions/phandle + + required: + - reg + + required: + - "#address-cells" + - "#size-cells" + - port@0 + - port@2 + +required: + - compatible + - reg + - interrupts + - vdd-supply + - vcc-supply + - ports + +additionalProperties: false + +examples: + - | + #include + #include + + i2c10 { + #address-cells = <1>; + #size-cells = <0>; + + hdmi-bridge@3b { + compatible = "lontium,lt9611"; + reg = <0x3b>; + + reset-gpios = <&tlmm 128 GPIO_ACTIVE_HIGH>; + interrupts-extended = <&tlmm 84 IRQ_TYPE_EDGE_FALLING>; + + vdd-supply = <<9611_1v8>; + vcc-supply = <<9611_3v3>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@0 { + reg = <0>; + lt9611_a: endpoint { + remote-endpoint = <&dsi0_out>; + }; + }; + + port@1 { + reg = <1>; + lt9611_b: endpoint { + remote-endpoint = <&dsi1_out>; + }; + }; + + port@2 { + reg = <2>; + lt9611_out: endpoint { + remote-endpoint = <&hdmi_con>; + }; + }; + }; + }; + }; + +... -- cgit v1.2.3 From 23278bf54afe180967069bdc8c0f1c7a365fc63e Mon Sep 17 00:00:00 2001 From: Vinod Koul Date: Thu, 23 Jul 2020 22:04:42 +0530 Subject: drm/bridge: Introduce LT9611 DSI to HDMI bridge Lontium Lt9611 is a DSI to HDMI bridge which supports two DSI ports and I2S port as an input and HDMI port as output Co-developed-by: Bjorn Andersson Signed-off-by: Bjorn Andersson Co-developed-by: Srinivas Kandagatla Signed-off-by: Srinivas Kandagatla Tested-by: John Stultz Signed-off-by: Vinod Koul Signed-off-by: Sam Ravnborg # fix lt9611_bridge_mode_valid Link: https://patchwork.freedesktop.org/patch/msgid/20200723163442.1280414-4-vkoul@kernel.org --- drivers/gpu/drm/bridge/Kconfig | 13 + drivers/gpu/drm/bridge/Makefile | 1 + drivers/gpu/drm/bridge/lontium-lt9611.c | 1230 +++++++++++++++++++++++++++++++ 3 files changed, 1244 insertions(+) create mode 100644 drivers/gpu/drm/bridge/lontium-lt9611.c diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig index 43271c21d3fc..c7f0dacfb57a 100644 --- a/drivers/gpu/drm/bridge/Kconfig +++ b/drivers/gpu/drm/bridge/Kconfig @@ -48,6 +48,19 @@ config DRM_DISPLAY_CONNECTOR on ARM-based platforms. Saying Y here when this driver is not needed will not cause any issue. +config DRM_LONTIUM_LT9611 + tristate "Lontium LT9611 DSI/HDMI bridge" + select SND_SOC_HDMI_CODEC if SND_SOC + depends on OF + select DRM_PANEL_BRIDGE + select DRM_KMS_HELPER + select REGMAP_I2C + help + Driver for Lontium LT9611 DSI to HDMI bridge + chip driver that converts dual DSI and I2S to + HDMI signals + Please say Y if you have such hardware. + config DRM_LVDS_CODEC tristate "Transparent LVDS encoders and decoders support" depends on OF diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile index d63d4b7e4347..7d7c123a95e4 100644 --- a/drivers/gpu/drm/bridge/Makefile +++ b/drivers/gpu/drm/bridge/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_DRM_CDNS_DSI) += cdns-dsi.o obj-$(CONFIG_DRM_CHRONTEL_CH7033) += chrontel-ch7033.o obj-$(CONFIG_DRM_DISPLAY_CONNECTOR) += display-connector.o +obj-$(CONFIG_DRM_LONTIUM_LT9611) += lontium-lt9611.o obj-$(CONFIG_DRM_LVDS_CODEC) += lvds-codec.o obj-$(CONFIG_DRM_MEGACHIPS_STDPXXXX_GE_B850V3_FW) += megachips-stdpxxxx-ge-b850v3-fw.o obj-$(CONFIG_DRM_NXP_PTN3460) += nxp-ptn3460.o diff --git a/drivers/gpu/drm/bridge/lontium-lt9611.c b/drivers/gpu/drm/bridge/lontium-lt9611.c new file mode 100644 index 000000000000..1009fc4ed4ed --- /dev/null +++ b/drivers/gpu/drm/bridge/lontium-lt9611.c @@ -0,0 +1,1230 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + * Copyright (c) 2019-2020. Linaro Limited. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#define EDID_SEG_SIZE 256 +#define EDID_LEN 32 +#define EDID_LOOP 8 +#define KEY_DDC_ACCS_DONE 0x02 +#define DDC_NO_ACK 0x50 + +#define LT9611_4LANES 0 + +struct lt9611 { + struct device *dev; + struct drm_bridge bridge; + struct drm_connector connector; + + struct regmap *regmap; + + struct device_node *dsi0_node; + struct device_node *dsi1_node; + struct mipi_dsi_device *dsi0; + struct mipi_dsi_device *dsi1; + struct platform_device *audio_pdev; + + bool ac_mode; + + struct gpio_desc *reset_gpio; + struct gpio_desc *enable_gpio; + + bool power_on; + bool sleep; + + struct regulator_bulk_data supplies[2]; + + struct i2c_client *client; + + enum drm_connector_status status; + + u8 edid_buf[EDID_SEG_SIZE]; + u32 vic; +}; + +#define LT9611_PAGE_CONTROL 0xff + +static const struct regmap_range_cfg lt9611_ranges[] = { + { + .name = "register_range", + .range_min = 0, + .range_max = 0x85ff, + .selector_reg = LT9611_PAGE_CONTROL, + .selector_mask = 0xff, + .selector_shift = 0, + .window_start = 0, + .window_len = 0x100, + }, +}; + +static const struct regmap_config lt9611_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xffff, + .ranges = lt9611_ranges, + .num_ranges = ARRAY_SIZE(lt9611_ranges), +}; + +struct lt9611_mode { + u16 hdisplay; + u16 vdisplay; + u8 vrefresh; + u8 lanes; + u8 intfs; +}; + +static struct lt9611_mode lt9611_modes[] = { + { 3840, 2160, 30, 4, 2 }, /* 3840x2160 24bit 30Hz 4Lane 2ports */ + { 1920, 1080, 60, 4, 1 }, /* 1080P 24bit 60Hz 4lane 1port */ + { 1920, 1080, 30, 3, 1 }, /* 1080P 24bit 30Hz 3lane 1port */ + { 1920, 1080, 24, 3, 1 }, + { 720, 480, 60, 4, 1 }, + { 720, 576, 50, 2, 1 }, + { 640, 480, 60, 2, 1 }, +}; + +static struct lt9611 *bridge_to_lt9611(struct drm_bridge *bridge) +{ + return container_of(bridge, struct lt9611, bridge); +} + +static struct lt9611 *connector_to_lt9611(struct drm_connector *connector) +{ + return container_of(connector, struct lt9611, connector); +} + +static int lt9611_mipi_input_analog(struct lt9611 *lt9611) +{ + const struct reg_sequence reg_cfg[] = { + { 0x8106, 0x40 }, /* port A rx current */ + { 0x810a, 0xfe }, /* port A ldo voltage set */ + { 0x810b, 0xbf }, /* enable port A lprx */ + { 0x8111, 0x40 }, /* port B rx current */ + { 0x8115, 0xfe }, /* port B ldo voltage set */ + { 0x8116, 0xbf }, /* enable port B lprx */ + + { 0x811c, 0x03 }, /* PortA clk lane no-LP mode */ + { 0x8120, 0x03 }, /* PortB clk lane with-LP mode */ + }; + + return regmap_multi_reg_write(lt9611->regmap, reg_cfg, ARRAY_SIZE(reg_cfg)); +} + +static int lt9611_mipi_input_digital(struct lt9611 *lt9611, + const struct drm_display_mode *mode) +{ + struct reg_sequence reg_cfg[] = { + { 0x8300, LT9611_4LANES }, + { 0x830a, 0x00 }, + { 0x824f, 0x80 }, + { 0x8250, 0x10 }, + { 0x8302, 0x0a }, + { 0x8306, 0x0a }, + }; + + if (mode->hdisplay == 3840) + reg_cfg[1].def = 0x03; + + return regmap_multi_reg_write(lt9611->regmap, reg_cfg, ARRAY_SIZE(reg_cfg)); +} + +static void lt9611_mipi_video_setup(struct lt9611 *lt9611, + const struct drm_display_mode *mode) +{ + u32 h_total, hactive, hsync_len, hfront_porch, hsync_porch; + u32 v_total, vactive, vsync_len, vfront_porch, vsync_porch; + + h_total = mode->htotal; + v_total = mode->vtotal; + + hactive = mode->hdisplay; + hsync_len = mode->hsync_end - mode->hsync_start; + hfront_porch = mode->hsync_start - mode->hdisplay; + hsync_porch = hsync_len + mode->htotal - mode->hsync_end; + + vactive = mode->vdisplay; + vsync_len = mode->vsync_end - mode->vsync_start; + vfront_porch = mode->vsync_start - mode->vdisplay; + vsync_porch = vsync_len + mode->vtotal - mode->vsync_end; + + regmap_write(lt9611->regmap, 0x830d, (u8)(v_total / 256)); + regmap_write(lt9611->regmap, 0x830e, (u8)(v_total % 256)); + + regmap_write(lt9611->regmap, 0x830f, (u8)(vactive / 256)); + regmap_write(lt9611->regmap, 0x8310, (u8)(vactive % 256)); + + regmap_write(lt9611->regmap, 0x8311, (u8)(h_total / 256)); + regmap_write(lt9611->regmap, 0x8312, (u8)(h_total % 256)); + + regmap_write(lt9611->regmap, 0x8313, (u8)(hactive / 256)); + regmap_write(lt9611->regmap, 0x8314, (u8)(hactive % 256)); + + regmap_write(lt9611->regmap, 0x8315, (u8)(vsync_len % 256)); + regmap_write(lt9611->regmap, 0x8316, (u8)(hsync_len % 256)); + + regmap_write(lt9611->regmap, 0x8317, (u8)(vfront_porch % 256)); + + regmap_write(lt9611->regmap, 0x8318, (u8)(vsync_porch % 256)); + + regmap_write(lt9611->regmap, 0x8319, (u8)(hfront_porch % 256)); + + regmap_write(lt9611->regmap, 0x831a, (u8)(hsync_porch / 256)); + regmap_write(lt9611->regmap, 0x831b, (u8)(hsync_porch % 256)); +} + +static void lt9611_pcr_setup(struct lt9611 *lt9611, const struct drm_display_mode *mode) +{ + const struct reg_sequence reg_cfg[] = { + { 0x830b, 0x01 }, + { 0x830c, 0x10 }, + { 0x8348, 0x00 }, + { 0x8349, 0x81 }, + + /* stage 1 */ + { 0x8321, 0x4a }, + { 0x8324, 0x71 }, + { 0x8325, 0x30 }, + { 0x832a, 0x01 }, + + /* stage 2 */ + { 0x834a, 0x40 }, + { 0x831d, 0x10 }, + + /* MK limit */ + { 0x832d, 0x38 }, + { 0x8331, 0x08 }, + }; + const struct reg_sequence reg_cfg2[] = { + { 0x830b, 0x03 }, + { 0x830c, 0xd0 }, + { 0x8348, 0x03 }, + { 0x8349, 0xe0 }, + { 0x8324, 0x72 }, + { 0x8325, 0x00 }, + { 0x832a, 0x01 }, + { 0x834a, 0x10 }, + { 0x831d, 0x10 }, + { 0x8326, 0x37 }, + }; + + regmap_multi_reg_write(lt9611->regmap, reg_cfg, ARRAY_SIZE(reg_cfg)); + + switch (mode->hdisplay) { + case 640: + regmap_write(lt9611->regmap, 0x8326, 0x14); + break; + case 1920: + regmap_write(lt9611->regmap, 0x8326, 0x37); + break; + case 3840: + regmap_multi_reg_write(lt9611->regmap, reg_cfg2, ARRAY_SIZE(reg_cfg2)); + break; + } + + /* pcr rst */ + regmap_write(lt9611->regmap, 0x8011, 0x5a); + regmap_write(lt9611->regmap, 0x8011, 0xfa); +} + +static int lt9611_pll_setup(struct lt9611 *lt9611, const struct drm_display_mode *mode) +{ + unsigned int pclk = mode->clock; + const struct reg_sequence reg_cfg[] = { + /* txpll init */ + { 0x8123, 0x40 }, + { 0x8124, 0x64 }, + { 0x8125, 0x80 }, + { 0x8126, 0x55 }, + { 0x812c, 0x37 }, + { 0x812f, 0x01 }, + { 0x8126, 0x55 }, + { 0x8127, 0x66 }, + { 0x8128, 0x88 }, + }; + + regmap_multi_reg_write(lt9611->regmap, reg_cfg, ARRAY_SIZE(reg_cfg)); + + if (pclk > 150000) + regmap_write(lt9611->regmap, 0x812d, 0x88); + else if (pclk > 70000) + regmap_write(lt9611->regmap, 0x812d, 0x99); + else + regmap_write(lt9611->regmap, 0x812d, 0xaa); + + /* + * first divide pclk by 2 first + * - write divide by 64k to 19:16 bits which means shift by 17 + * - write divide by 256 to 15:8 bits which means shift by 9 + * - write remainder to 7:0 bits, which means shift by 1 + */ + regmap_write(lt9611->regmap, 0x82e3, pclk >> 17); /* pclk[19:16] */ + regmap_write(lt9611->regmap, 0x82e4, pclk >> 9); /* pclk[15:8] */ + regmap_write(lt9611->regmap, 0x82e5, pclk >> 1); /* pclk[7:0] */ + + regmap_write(lt9611->regmap, 0x82de, 0x20); + regmap_write(lt9611->regmap, 0x82de, 0xe0); + + regmap_write(lt9611->regmap, 0x8016, 0xf1); + regmap_write(lt9611->regmap, 0x8016, 0xf3); + + return 0; +} + +static int lt9611_read_video_check(struct lt9611 *lt9611, unsigned int reg) +{ + unsigned int temp, temp2; + int ret; + + ret = regmap_read(lt9611->regmap, reg, &temp); + if (ret) + return ret; + temp <<= 8; + ret = regmap_read(lt9611->regmap, reg + 1, &temp2); + if (ret) + return ret; + + return (temp + temp2); +} + +static int lt9611_video_check(struct lt9611 *lt9611) +{ + u32 v_total, vactive, hactive_a, hactive_b, h_total_sysclk; + int temp; + + /* top module video check */ + + /* vactive */ + temp = lt9611_read_video_check(lt9611, 0x8282); + if (temp < 0) + goto end; + vactive = temp; + + /* v_total */ + temp = lt9611_read_video_check(lt9611, 0x826c); + if (temp < 0) + goto end; + v_total = temp; + + /* h_total_sysclk */ + temp = lt9611_read_video_check(lt9611, 0x8286); + if (temp < 0) + goto end; + h_total_sysclk = temp; + + /* hactive_a */ + temp = lt9611_read_video_check(lt9611, 0x8382); + if (temp < 0) + goto end; + hactive_a = temp / 3; + + /* hactive_b */ + temp = lt9611_read_video_check(lt9611, 0x8386); + if (temp < 0) + goto end; + hactive_b = temp / 3; + + dev_info(lt9611->dev, + "video check: hactive_a=%d, hactive_b=%d, vactive=%d, v_total=%d, h_total_sysclk=%d\n", + hactive_a, hactive_b, vactive, v_total, h_total_sysclk); + + return 0; + +end: + dev_err(lt9611->dev, "read video check error\n"); + return temp; +} + +static void lt9611_hdmi_tx_digital(struct lt9611 *lt9611) +{ + regmap_write(lt9611->regmap, 0x8443, 0x46 - lt9611->vic); + regmap_write(lt9611->regmap, 0x8447, lt9611->vic); + regmap_write(lt9611->regmap, 0x843d, 0x0a); /* UD1 infoframe */ + + regmap_write(lt9611->regmap, 0x82d6, 0x8c); + regmap_write(lt9611->regmap, 0x82d7, 0x04); +} + +static void lt9611_hdmi_tx_phy(struct lt9611 *lt9611) +{ + struct reg_sequence reg_cfg[] = { + { 0x8130, 0x6a }, + { 0x8131, 0x44 }, /* HDMI DC mode */ + { 0x8132, 0x4a }, + { 0x8133, 0x0b }, + { 0x8134, 0x00 }, + { 0x8135, 0x00 }, + { 0x8136, 0x00 }, + { 0x8137, 0x44 }, + { 0x813f, 0x0f }, + { 0x8140, 0xa0 }, + { 0x8141, 0xa0 }, + { 0x8142, 0xa0 }, + { 0x8143, 0xa0 }, + { 0x8144, 0x0a }, + }; + + /* HDMI AC mode */ + if (lt9611->ac_mode) + reg_cfg[2].def = 0x73; + + regmap_multi_reg_write(lt9611->regmap, reg_cfg, ARRAY_SIZE(reg_cfg)); +} + +static irqreturn_t lt9611_irq_thread_handler(int irq, void *dev_id) +{ + struct lt9611 *lt9611 = dev_id; + unsigned int irq_flag0 = 0; + unsigned int irq_flag3 = 0; + + regmap_read(lt9611->regmap, 0x820f, &irq_flag3); + regmap_read(lt9611->regmap, 0x820c, &irq_flag0); + + /* hpd changed low */ + if (irq_flag3 & 0x80) { + dev_info(lt9611->dev, "hdmi cable disconnected\n"); + + regmap_write(lt9611->regmap, 0x8207, 0xbf); + regmap_write(lt9611->regmap, 0x8207, 0x3f); + } + + /* hpd changed high */ + if (irq_flag3 & 0x40) { + dev_info(lt9611->dev, "hdmi cable connected\n"); + + regmap_write(lt9611->regmap, 0x8207, 0x7f); + regmap_write(lt9611->regmap, 0x8207, 0x3f); + } + + if (irq_flag3 & 0xc0 && lt9611->bridge.dev) + drm_kms_helper_hotplug_event(lt9611->bridge.dev); + + /* video input changed */ + if (irq_flag0 & 0x01) { + dev_info(lt9611->dev, "video input changed\n"); + regmap_write(lt9611->regmap, 0x829e, 0xff); + regmap_write(lt9611->regmap, 0x829e, 0xf7); + regmap_write(lt9611->regmap, 0x8204, 0xff); + regmap_write(lt9611->regmap, 0x8204, 0xfe); + } + + return IRQ_HANDLED; +} + +static void lt9611_enable_hpd_interrupts(struct lt9611 *lt9611) +{ + unsigned int val; + + regmap_read(lt9611->regmap, 0x8203, &val); + + val &= ~0xc0; + regmap_write(lt9611->regmap, 0x8203, val); + regmap_write(lt9611->regmap, 0x8207, 0xff); /* clear */ + regmap_write(lt9611->regmap, 0x8207, 0x3f); +} + +static void lt9611_sleep_setup(struct lt9611 *lt9611) +{ + const struct reg_sequence sleep_setup[] = { + { 0x8024, 0x76 }, + { 0x8023, 0x01 }, + { 0x8157, 0x03 }, /* set addr pin as output */ + { 0x8149, 0x0b }, + { 0x8151, 0x30 }, /* disable IRQ */ + { 0x8102, 0x48 }, /* MIPI Rx power down */ + { 0x8123, 0x80 }, + { 0x8130, 0x00 }, + { 0x8100, 0x01 }, /* bandgap power down */ + { 0x8101, 0x00 }, /* system clk power down */ + }; + + regmap_multi_reg_write(lt9611->regmap, + sleep_setup, ARRAY_SIZE(sleep_setup)); + lt9611->sleep = true; +} + +static int lt9611_power_on(struct lt9611 *lt9611) +{ + int ret; + const struct reg_sequence seq[] = { + /* LT9611_System_Init */ + { 0x8101, 0x18 }, /* sel xtal clock */ + + /* timer for frequency meter */ + { 0x821b, 0x69 }, /* timer 2 */ + { 0x821c, 0x78 }, + { 0x82cb, 0x69 }, /* timer 1 */ + { 0x82cc, 0x78 }, + + /* irq init */ + { 0x8251, 0x01 }, + { 0x8258, 0x0a }, /* hpd irq */ + { 0x8259, 0x80 }, /* hpd debounce width */ + { 0x829e, 0xf7 }, /* video check irq */ + + /* power consumption for work */ + { 0x8004, 0xf0 }, + { 0x8006, 0xf0 }, + { 0x800a, 0x80 }, + { 0x800b, 0x40 }, + { 0x800d, 0xef }, + { 0x8011, 0xfa }, + }; + + if (lt9611->power_on) + return 0; + + ret = regmap_multi_reg_write(lt9611->regmap, seq, ARRAY_SIZE(seq)); + if (!ret) + lt9611->power_on = true; + + return ret; +} + +static int lt9611_power_off(struct lt9611 *lt9611) +{ + int ret; + + ret = regmap_write(lt9611->regmap, 0x8130, 0x6a); + if (!ret) + lt9611->power_on = false; + + return ret; +} + +static void lt9611_reset(struct lt9611 *lt9611) +{ + gpiod_set_value_cansleep(lt9611->reset_gpio, 1); + msleep(20); + + gpiod_set_value_cansleep(lt9611->reset_gpio, 0); + msleep(20); + + gpiod_set_value_cansleep(lt9611->reset_gpio, 1); + msleep(100); +} + +static void lt9611_assert_5v(struct lt9611 *lt9611) +{ + if (!lt9611->enable_gpio) + return; + + gpiod_set_value_cansleep(lt9611->enable_gpio, 1); + msleep(20); +} + +static int lt9611_regulator_init(struct lt9611 *lt9611) +{ + int ret; + + lt9611->supplies[0].supply = "vdd"; + lt9611->supplies[1].supply = "vcc"; + + ret = devm_regulator_bulk_get(lt9611->dev, 2, lt9611->supplies); + if (ret < 0) + return ret; + + return regulator_set_load(lt9611->supplies[0].consumer, 300000); +} + +static int lt9611_regulator_enable(struct lt9611 *lt9611) +{ + int ret; + + ret = regulator_enable(lt9611->supplies[0].consumer); + if (ret < 0) + return ret; + + usleep_range(1000, 10000); + + ret = regulator_enable(lt9611->supplies[1].consumer); + if (ret < 0) { + regulator_disable(lt9611->supplies[0].consumer); + return ret; + } + + return 0; +} + +static struct lt9611_mode *lt9611_find_mode(const struct drm_display_mode *mode) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(lt9611_modes); i++) { + if (lt9611_modes[i].hdisplay == mode->hdisplay && + lt9611_modes[i].vdisplay == mode->vdisplay && + lt9611_modes[i].vrefresh == drm_mode_vrefresh(mode)) { + return <9611_modes[i]; + } + } + + return NULL; +} + +/* connector funcs */ +static enum drm_connector_status +lt9611_connector_detect(struct drm_connector *connector, bool force) +{ + struct lt9611 *lt9611 = connector_to_lt9611(connector); + unsigned int reg_val = 0; + int connected = 0; + + regmap_read(lt9611->regmap, 0x825e, ®_val); + connected = (reg_val & BIT(2)); + + lt9611->status = connected ? connector_status_connected : + connector_status_disconnected; + + return lt9611->status; +} + +static int lt9611_read_edid(struct lt9611 *lt9611) +{ + unsigned int temp; + int ret = 0; + int i, j; + + /* memset to clear old buffer, if any */ + memset(lt9611->edid_buf, 0, sizeof(lt9611->edid_buf)); + + regmap_write(lt9611->regmap, 0x8503, 0xc9); + + /* 0xA0 is EDID device address */ + regmap_write(lt9611->regmap, 0x8504, 0xa0); + /* 0x00 is EDID offset address */ + regmap_write(lt9611->regmap, 0x8505, 0x00); + + /* length for read */ + regmap_write(lt9611->regmap, 0x8506, EDID_LEN); + regmap_write(lt9611->regmap, 0x8514, 0x7f); + + for (i = 0; i < EDID_LOOP; i++) { + /* offset address */ + regmap_write(lt9611->regmap, 0x8505, i * EDID_LEN); + regmap_write(lt9611->regmap, 0x8507, 0x36); + regmap_write(lt9611->regmap, 0x8507, 0x31); + regmap_write(lt9611->regmap, 0x8507, 0x37); + usleep_range(5000, 10000); + + regmap_read(lt9611->regmap, 0x8540, &temp); + + if (temp & KEY_DDC_ACCS_DONE) { + for (j = 0; j < EDID_LEN; j++) { + regmap_read(lt9611->regmap, 0x8583, &temp); + lt9611->edid_buf[i * EDID_LEN + j] = temp; + } + + } else if (temp & DDC_NO_ACK) { /* DDC No Ack or Abitration lost */ + dev_err(lt9611->dev, "read edid failed: no ack\n"); + ret = -EIO; + goto end; + + } else { + dev_err(lt9611->dev, "read edid failed: access not done\n"); + ret = -EIO; + goto end; + } + } + +end: + regmap_write(lt9611->regmap, 0x8507, 0x1f); + return ret; +} + +static int +lt9611_get_edid_block(void *data, u8 *buf, unsigned int block, size_t len) +{ + struct lt9611 *lt9611 = data; + int ret; + + if (len > 128) + return -EINVAL; + + /* supports up to 1 extension block */ + /* TODO: add support for more extension blocks */ + if (block > 1) + return -EINVAL; + + if (block == 0) { + ret = lt9611_read_edid(lt9611); + if (ret) { + dev_err(lt9611->dev, "edid read failed\n"); + return ret; + } + } + + block %= 2; + memcpy(buf, lt9611->edid_buf + (block * 128), len); + + return 0; +} + +static int lt9611_connector_get_modes(struct drm_connector *connector) +{ + struct lt9611 *lt9611 = connector_to_lt9611(connector); + unsigned int count; + struct edid *edid; + + lt9611_power_on(lt9611); + edid = drm_do_get_edid(connector, lt9611_get_edid_block, lt9611); + drm_connector_update_edid_property(connector, edid); + count = drm_add_edid_modes(connector, edid); + kfree(edid); + + return count; +} + +static enum drm_mode_status +lt9611_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct lt9611_mode *lt9611_mode = lt9611_find_mode(mode); + + return lt9611_mode ? MODE_OK : MODE_BAD; +} + +/* bridge funcs */ +static void lt9611_bridge_enable(struct drm_bridge *bridge) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + + if (lt9611_power_on(lt9611)) { + dev_err(lt9611->dev, "power on failed\n"); + return; + } + + lt9611_mipi_input_analog(lt9611); + lt9611_hdmi_tx_digital(lt9611); + lt9611_hdmi_tx_phy(lt9611); + + msleep(500); + + lt9611_video_check(lt9611); + + /* Enable HDMI output */ + regmap_write(lt9611->regmap, 0x8130, 0xea); +} + +static void lt9611_bridge_disable(struct drm_bridge *bridge) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + int ret; + + /* Disable HDMI output */ + ret = regmap_write(lt9611->regmap, 0x8130, 0x6a); + if (ret) { + dev_err(lt9611->dev, "video on failed\n"); + return; + } + + if (lt9611_power_off(lt9611)) { + dev_err(lt9611->dev, "power on failed\n"); + return; + } +} + +static struct +drm_connector_helper_funcs lt9611_bridge_connector_helper_funcs = { + .get_modes = lt9611_connector_get_modes, + .mode_valid = lt9611_connector_mode_valid, +}; + +static const struct drm_connector_funcs lt9611_bridge_connector_funcs = { + .fill_modes = drm_helper_probe_single_connector_modes, + .detect = lt9611_connector_detect, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static struct mipi_dsi_device *lt9611_attach_dsi(struct lt9611 *lt9611, + struct device_node *dsi_node) +{ + const struct mipi_dsi_device_info info = { "lt9611", 0, NULL }; + struct mipi_dsi_device *dsi; + struct mipi_dsi_host *host; + int ret; + + host = of_find_mipi_dsi_host_by_node(dsi_node); + if (!host) { + dev_err(lt9611->dev, "failed to find dsi host\n"); + return ERR_PTR(-EPROBE_DEFER); + } + + dsi = mipi_dsi_device_register_full(host, &info); + if (IS_ERR(dsi)) { + dev_err(lt9611->dev, "failed to create dsi device\n"); + return dsi; + } + + dsi->lanes = 4; + dsi->format = MIPI_DSI_FMT_RGB888; + dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE | + MIPI_DSI_MODE_VIDEO_HSE; + + ret = mipi_dsi_attach(dsi); + if (ret < 0) { + dev_err(lt9611->dev, "failed to attach dsi to host\n"); + mipi_dsi_device_unregister(dsi); + return ERR_PTR(ret); + } + + return dsi; +} + +static void lt9611_bridge_detach(struct drm_bridge *bridge) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + + if (lt9611->dsi1) { + mipi_dsi_detach(lt9611->dsi1); + mipi_dsi_device_unregister(lt9611->dsi1); + } + + mipi_dsi_detach(lt9611->dsi0); + mipi_dsi_device_unregister(lt9611->dsi0); +} + +static int lt9611_connector_init(struct drm_bridge *bridge, struct lt9611 *lt9611) +{ + int ret; + + ret = drm_connector_init(bridge->dev, <9611->connector, + <9611_bridge_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret) { + DRM_ERROR("Failed to initialize connector with drm\n"); + return ret; + } + + drm_connector_helper_add(<9611->connector, + <9611_bridge_connector_helper_funcs); + drm_connector_attach_encoder(<9611->connector, bridge->encoder); + + if (!bridge->encoder) { + DRM_ERROR("Parent encoder object not found"); + return -ENODEV; + } + + return 0; +} + +static int lt9611_bridge_attach(struct drm_bridge *bridge, + enum drm_bridge_attach_flags flags) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + int ret; + + if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) { + ret = lt9611_connector_init(bridge, lt9611); + if (ret < 0) + return ret; + } + + /* Attach primary DSI */ + lt9611->dsi0 = lt9611_attach_dsi(lt9611, lt9611->dsi0_node); + if (IS_ERR(lt9611->dsi0)) + return PTR_ERR(lt9611->dsi0); + + /* Attach secondary DSI, if specified */ + if (lt9611->dsi1_node) { + lt9611->dsi1 = lt9611_attach_dsi(lt9611, lt9611->dsi1_node); + if (IS_ERR(lt9611->dsi1)) { + ret = PTR_ERR(lt9611->dsi1); + goto err_unregister_dsi0; + } + } + + return 0; + +err_unregister_dsi0: + lt9611_bridge_detach(bridge); + drm_connector_cleanup(<9611->connector); + mipi_dsi_device_unregister(lt9611->dsi0); + + return ret; +} + +static enum drm_mode_status lt9611_bridge_mode_valid(struct drm_bridge *bridge, + const struct drm_display_info *info, + const struct drm_display_mode *mode) +{ + struct lt9611_mode *lt9611_mode = lt9611_find_mode(mode); + + return lt9611_mode ? MODE_OK : MODE_BAD; +} + +static void lt9611_bridge_pre_enable(struct drm_bridge *bridge) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + + if (!lt9611->sleep) + return; + + lt9611_reset(lt9611); + regmap_write(lt9611->regmap, 0x80ee, 0x01); + + lt9611->sleep = false; +} + +static void lt9611_bridge_post_disable(struct drm_bridge *bridge) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + + lt9611_sleep_setup(lt9611); +} + +static void lt9611_bridge_mode_set(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + const struct drm_display_mode *adj_mode) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + struct hdmi_avi_infoframe avi_frame; + int ret; + + lt9611_bridge_pre_enable(bridge); + + lt9611_mipi_input_digital(lt9611, mode); + lt9611_pll_setup(lt9611, mode); + lt9611_mipi_video_setup(lt9611, mode); + lt9611_pcr_setup(lt9611, mode); + + ret = drm_hdmi_avi_infoframe_from_display_mode(&avi_frame, + <9611->connector, + mode); + if (!ret) + lt9611->vic = avi_frame.video_code; +} + +static enum drm_connector_status lt9611_bridge_detect(struct drm_bridge *bridge) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + unsigned int reg_val = 0; + int connected; + + regmap_read(lt9611->regmap, 0x825e, ®_val); + connected = reg_val & BIT(2); + + lt9611->status = connected ? connector_status_connected : + connector_status_disconnected; + + return lt9611->status; +} + +static struct edid *lt9611_bridge_get_edid(struct drm_bridge *bridge, + struct drm_connector *connector) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + + lt9611_power_on(lt9611); + return drm_do_get_edid(connector, lt9611_get_edid_block, lt9611); +} + +static void lt9611_bridge_hpd_enable(struct drm_bridge *bridge) +{ + struct lt9611 *lt9611 = bridge_to_lt9611(bridge); + + lt9611_enable_hpd_interrupts(lt9611); +} + +static const struct drm_bridge_funcs lt9611_bridge_funcs = { + .attach = lt9611_bridge_attach, + .detach = lt9611_bridge_detach, + .mode_valid = lt9611_bridge_mode_valid, + .enable = lt9611_bridge_enable, + .disable = lt9611_bridge_disable, + .post_disable = lt9611_bridge_post_disable, + .mode_set = lt9611_bridge_mode_set, + .detect = lt9611_bridge_detect, + .get_edid = lt9611_bridge_get_edid, + .hpd_enable = lt9611_bridge_hpd_enable, +}; + +static int lt9611_parse_dt(struct device *dev, + struct lt9611 *lt9611) +{ + lt9611->dsi0_node = of_graph_get_remote_node(dev->of_node, 1, -1); + if (!lt9611->dsi0_node) { + dev_err(lt9611->dev, "failed to get remote node for primary dsi\n"); + return -ENODEV; + } + + lt9611->dsi1_node = of_graph_get_remote_node(dev->of_node, 2, -1); + + lt9611->ac_mode = of_property_read_bool(dev->of_node, "lt,ac-mode"); + + return 0; +} + +static int lt9611_gpio_init(struct lt9611 *lt9611) +{ + struct device *dev = lt9611->dev; + + lt9611->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(lt9611->reset_gpio)) { + dev_err(dev, "failed to acquire reset gpio\n"); + return PTR_ERR(lt9611->reset_gpio); + } + + lt9611->enable_gpio = devm_gpiod_get_optional(dev, "enable", + GPIOD_OUT_LOW); + if (IS_ERR(lt9611->enable_gpio)) { + dev_err(dev, "failed to acquire enable gpio\n"); + return PTR_ERR(lt9611->enable_gpio); + } + + return 0; +} + +static int lt9611_read_device_rev(struct lt9611 *lt9611) +{ + unsigned int rev; + int ret; + + regmap_write(lt9611->regmap, 0x80ee, 0x01); + ret = regmap_read(lt9611->regmap, 0x8002, &rev); + if (ret) + dev_err(lt9611->dev, "failed to read revision: %d\n", ret); + else + dev_info(lt9611->dev, "LT9611 revision: 0x%x\n", rev); + + return ret; +} + +static int lt9611_hdmi_hw_params(struct device *dev, void *data, + struct hdmi_codec_daifmt *fmt, + struct hdmi_codec_params *hparms) +{ + struct lt9611 *lt9611 = data; + + if (hparms->sample_rate == 48000) + regmap_write(lt9611->regmap, 0x840f, 0x2b); + else if (hparms->sample_rate == 96000) + regmap_write(lt9611->regmap, 0x840f, 0xab); + else + return -EINVAL; + + regmap_write(lt9611->regmap, 0x8435, 0x00); + regmap_write(lt9611->regmap, 0x8436, 0x18); + regmap_write(lt9611->regmap, 0x8437, 0x00); + + return 0; +} + +static int lt9611_audio_startup(struct device *dev, void *data) +{ + struct lt9611 *lt9611 = data; + + regmap_write(lt9611->regmap, 0x82d6, 0x8c); + regmap_write(lt9611->regmap, 0x82d7, 0x04); + + regmap_write(lt9611->regmap, 0x8406, 0x08); + regmap_write(lt9611->regmap, 0x8407, 0x10); + + regmap_write(lt9611->regmap, 0x8434, 0xd5); + + return 0; +} + +static void lt9611_audio_shutdown(struct device *dev, void *data) +{ + struct lt9611 *lt9611 = data; + + regmap_write(lt9611->regmap, 0x8406, 0x00); + regmap_write(lt9611->regmap, 0x8407, 0x00); +} + +static int lt9611_hdmi_i2s_get_dai_id(struct snd_soc_component *component, + struct device_node *endpoint) +{ + struct of_endpoint of_ep; + int ret; + + ret = of_graph_parse_endpoint(endpoint, &of_ep); + if (ret < 0) + return ret; + + /* + * HDMI sound should be located as reg = <2> + * Then, it is sound port 0 + */ + if (of_ep.port == 2) + return 0; + + return -EINVAL; +} + +static const struct hdmi_codec_ops lt9611_codec_ops = { + .hw_params = lt9611_hdmi_hw_params, + .audio_shutdown = lt9611_audio_shutdown, + .audio_startup = lt9611_audio_startup, + .get_dai_id = lt9611_hdmi_i2s_get_dai_id, +}; + +static struct hdmi_codec_pdata codec_data = { + .ops = <9611_codec_ops, + .max_i2s_channels = 8, + .i2s = 1, +}; + +static int lt9611_audio_init(struct device *dev, struct lt9611 *lt9611) +{ + codec_data.data = lt9611; + lt9611->audio_pdev = + platform_device_register_data(dev, HDMI_CODEC_DRV_NAME, + PLATFORM_DEVID_AUTO, + &codec_data, sizeof(codec_data)); + + return PTR_ERR_OR_ZERO(lt9611->audio_pdev); +} + +static void lt9611_audio_exit(struct lt9611 *lt9611) +{ + if (lt9611->audio_pdev) { + platform_device_unregister(lt9611->audio_pdev); + lt9611->audio_pdev = NULL; + } +} + +static int lt9611_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lt9611 *lt9611; + struct device *dev = &client->dev; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(dev, "device doesn't support I2C\n"); + return -ENODEV; + } + + lt9611 = devm_kzalloc(dev, sizeof(*lt9611), GFP_KERNEL); + if (!lt9611) + return -ENOMEM; + + lt9611->dev = &client->dev; + lt9611->client = client; + lt9611->sleep = false; + + lt9611->regmap = devm_regmap_init_i2c(client, <9611_regmap_config); + if (IS_ERR(lt9611->regmap)) { + dev_err(lt9611->dev, "regmap i2c init failed\n"); + return PTR_ERR(lt9611->regmap); + } + + ret = lt9611_parse_dt(&client->dev, lt9611); + if (ret) { + dev_err(dev, "failed to parse device tree\n"); + return ret; + } + + ret = lt9611_gpio_init(lt9611); + if (ret < 0) + goto err_of_put; + + ret = lt9611_regulator_init(lt9611); + if (ret < 0) + goto err_of_put; + + lt9611_assert_5v(lt9611); + + ret = lt9611_regulator_enable(lt9611); + if (ret) + goto err_of_put; + + lt9611_reset(lt9611); + + ret = lt9611_read_device_rev(lt9611); + if (ret) { + dev_err(dev, "failed to read chip rev\n"); + goto err_disable_regulators; + } + + ret = devm_request_threaded_irq(dev, client->irq, NULL, + lt9611_irq_thread_handler, + IRQF_ONESHOT, "lt9611", lt9611); + if (ret) { + dev_err(dev, "failed to request irq\n"); + goto err_disable_regulators; + } + + i2c_set_clientdata(client, lt9611); + + lt9611->bridge.funcs = <9611_bridge_funcs; + lt9611->bridge.of_node = client->dev.of_node; + lt9611->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID | + DRM_BRIDGE_OP_HPD | DRM_BRIDGE_OP_MODES; + lt9611->bridge.type = DRM_MODE_CONNECTOR_HDMIA; + + drm_bridge_add(<9611->bridge); + + lt9611_enable_hpd_interrupts(lt9611); + + return lt9611_audio_init(dev, lt9611); + +err_disable_regulators: + regulator_bulk_disable(ARRAY_SIZE(lt9611->supplies), lt9611->supplies); + +err_of_put: + of_node_put(lt9611->dsi1_node); + of_node_put(lt9611->dsi0_node); + + return ret; +} + +static int lt9611_remove(struct i2c_client *client) +{ + struct lt9611 *lt9611 = i2c_get_clientdata(client); + + disable_irq(client->irq); + lt9611_audio_exit(lt9611); + drm_bridge_remove(<9611->bridge); + + regulator_bulk_disable(ARRAY_SIZE(lt9611->supplies), lt9611->supplies); + + of_node_put(lt9611->dsi1_node); + of_node_put(lt9611->dsi0_node); + + return 0; +} + +static struct i2c_device_id lt9611_id[] = { + { "lontium,lt9611", 0 }, + {} +}; + +static const struct of_device_id lt9611_match_table[] = { + { .compatible = "lontium,lt9611" }, + { } +}; +MODULE_DEVICE_TABLE(of, lt9611_match_table); + +static struct i2c_driver lt9611_driver = { + .driver = { + .name = "lt9611", + .of_match_table = lt9611_match_table, + }, + .probe = lt9611_probe, + .remove = lt9611_remove, + .id_table = lt9611_id, +}; +module_i2c_driver(lt9611_driver); + +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3 From 2a5c2ff5848efc77a115c53d59655aa6016237e8 Mon Sep 17 00:00:00 2001 From: Sam Ravnborg Date: Mon, 13 Apr 2020 20:30:05 +0200 Subject: drm/panel: add connector type to boe,hv070wsa-100 panel The boe,hv070wsa-100 panel is a LVDS panel. Fix connector type to reflect this. With this change users of this panel do not have to specify the connector type. v4: - Add .bpc = 4 (Laurent) v3: - Drop PIXDATA bus_flag, not relevant v2: - Add .bus_format (Laurent) - Add .bus_flags Signed-off-by: Sam Ravnborg Reviewed-by: Laurent Pinchart Cc: Laurent Pinchart Cc: Thierry Reding Cc: Sam Ravnborg Link: https://patchwork.freedesktop.org/patch/msgid/20200703192417.372164-2-sam@ravnborg.org --- drivers/gpu/drm/panel/panel-simple.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 88493538a147..54323515ca2c 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -1191,10 +1191,14 @@ static const struct drm_display_mode boe_hv070wsa_mode = { static const struct panel_desc boe_hv070wsa = { .modes = &boe_hv070wsa_mode, .num_modes = 1, + .bpc = 8, .size = { .width = 154, .height = 90, }, + .bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG, + .bus_flags = DRM_BUS_FLAG_DE_HIGH, + .connector_type = DRM_MODE_CONNECTOR_LVDS, }; static const struct drm_display_mode boe_nv101wxmn51_modes[] = { -- cgit v1.2.3 From ddb8e853dc8507ecf9c7b4630857771da1f89752 Mon Sep 17 00:00:00 2001 From: Sam Ravnborg Date: Sun, 26 Jul 2020 22:33:10 +0200 Subject: drm/panel: panel-simple: validate panel description Warn if we detect a panel with incomplete/wrong description. This is inspired by a similar patch by Laurent that introduced checks for LVDS panels - this extends the checks to the remaining type of connectors. This is known to warn for some of the existing panels but added despite this as we need help from people using the panels to add the missing info. The checks are not complete but will catch the most common mistakes. The checks at the same time serve as documentation for the minimum required description for a panel. The checks uses dev_warn() as we know this will hit. WARN() was too noisy at the moment for anything else than LVDS. v3: - %d => %u for bpc (Laurent) v2: - Use dev_warn (Laurent) - Check for empty bus_flags Signed-off-by: Sam Ravnborg Reviewed-by: Laurent Pinchart Cc: Laurent Pinchart Cc: Thierry Reding Cc: Sam Ravnborg Link: https://patchwork.freedesktop.org/patch/msgid/20200726203324.3722593-2-sam@ravnborg.org --- drivers/gpu/drm/panel/panel-simple.c | 41 ++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 54323515ca2c..2c972cc8d197 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -500,6 +500,7 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) struct panel_simple *panel; struct display_timing dt; struct device_node *ddc; + u32 bus_flags; int err; panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL); @@ -549,8 +550,12 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) panel_simple_parse_panel_timing_node(dev, panel, &dt); } - if (desc->connector_type == DRM_MODE_CONNECTOR_LVDS) { - /* Catch common mistakes for LVDS panels. */ + /* Catch common mistakes for panels. */ + switch (desc->connector_type) { + case 0: + dev_warn(dev, "Specify missing connector_type\n"); + break; + case DRM_MODE_CONNECTOR_LVDS: WARN_ON(desc->bus_flags & ~(DRM_BUS_FLAG_DE_LOW | DRM_BUS_FLAG_DE_HIGH | @@ -564,6 +569,38 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) WARN_ON((desc->bus_format == MEDIA_BUS_FMT_RGB888_1X7X4_SPWG || desc->bus_format == MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA) && desc->bpc != 8); + break; + case DRM_MODE_CONNECTOR_eDP: + if (desc->bus_format == 0) + dev_warn(dev, "Specify missing bus_format\n"); + if (desc->bpc != 6 && desc->bpc != 8) + dev_warn(dev, "Expected bpc in {6,8} but got: %u\n", desc->bpc); + break; + case DRM_MODE_CONNECTOR_DSI: + if (desc->bpc != 6 && desc->bpc != 8) + dev_warn(dev, "Expected bpc in {6,8} but got: %u\n", desc->bpc); + break; + case DRM_MODE_CONNECTOR_DPI: + bus_flags = DRM_BUS_FLAG_DE_LOW | + DRM_BUS_FLAG_DE_HIGH | + DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE | + DRM_BUS_FLAG_PIXDATA_SAMPLE_NEGEDGE | + DRM_BUS_FLAG_DATA_MSB_TO_LSB | + DRM_BUS_FLAG_DATA_LSB_TO_MSB | + DRM_BUS_FLAG_SYNC_SAMPLE_POSEDGE | + DRM_BUS_FLAG_SYNC_SAMPLE_NEGEDGE; + if (desc->bus_flags & ~bus_flags) + dev_warn(dev, "Unexpected bus_flags(%d)\n", desc->bus_flags & ~bus_flags); + if (!(desc->bus_flags & bus_flags)) + dev_warn(dev, "Specify missing bus_flags\n"); + if (desc->bus_format == 0) + dev_warn(dev, "Specify missing bus_format\n"); + if (desc->bpc != 6 && desc->bpc != 8) + dev_warn(dev, "Expected bpc in {6,8} but got: %u\n", desc->bpc); + break; + default: + dev_warn(dev, "Specify a valid connector_type: %d\n", desc->connector_type); + break; } drm_panel_init(&panel->base, dev, &panel_simple_funcs, -- cgit v1.2.3 From 9f069c6fbc72669311723262766047f577dba496 Mon Sep 17 00:00:00 2001 From: Sam Ravnborg Date: Sun, 26 Jul 2020 22:33:11 +0200 Subject: drm/panel: panel-simple: add default connector_type All panels shall report a connector type. panel-simple has a lot of panels with no connector_type, and for these fall back to DPI as the default. v2: - Rebased on top of validation of panel description Signed-off-by: Sam Ravnborg Reviewed-by: Laurent Pinchart Cc: Laurent Pinchart Cc: Thierry Reding Cc: Sam Ravnborg Link: https://patchwork.freedesktop.org/patch/msgid/20200726203324.3722593-3-sam@ravnborg.org --- drivers/gpu/drm/panel/panel-simple.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c index 2c972cc8d197..5cba33394083 100644 --- a/drivers/gpu/drm/panel/panel-simple.c +++ b/drivers/gpu/drm/panel/panel-simple.c @@ -500,6 +500,7 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) struct panel_simple *panel; struct display_timing dt; struct device_node *ddc; + int connector_type; u32 bus_flags; int err; @@ -550,10 +551,12 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) panel_simple_parse_panel_timing_node(dev, panel, &dt); } + connector_type = desc->connector_type; /* Catch common mistakes for panels. */ - switch (desc->connector_type) { + switch (connector_type) { case 0: dev_warn(dev, "Specify missing connector_type\n"); + connector_type = DRM_MODE_CONNECTOR_DPI; break; case DRM_MODE_CONNECTOR_LVDS: WARN_ON(desc->bus_flags & @@ -600,11 +603,11 @@ static int panel_simple_probe(struct device *dev, const struct panel_desc *desc) break; default: dev_warn(dev, "Specify a valid connector_type: %d\n", desc->connector_type); + connector_type = DRM_MODE_CONNECTOR_DPI; break; } - drm_panel_init(&panel->base, dev, &panel_simple_funcs, - desc->connector_type); + drm_panel_init(&panel->base, dev, &panel_simple_funcs, connector_type); err = drm_panel_of_backlight(&panel->base); if (err) -- cgit v1.2.3 From 385ca38da29c214e03f526d28346176e1b500db8 Mon Sep 17 00:00:00 2001 From: Sam Ravnborg Date: Sun, 26 Jul 2020 22:33:12 +0200 Subject: drm/bridge: tc358764: drop drm_connector_(un)register Drop drm_connector handling that is not needed: - drm_dev_register() in the display controller driver takes care of registering connectors. So the call to drm_connector_register() call is not needed in the bridge driver. - Use of drm_connector_unregister() is only required for drivers that explicit have called drm_dev_register. - The reference counting using drm_connector_put() is likewise not needed. Signed-off-by: Sam Ravnborg Reviewed-by: Laurent Pinchart Cc: Andrzej Hajda Cc: Neil Armstrong Cc: Laurent Pinchart Cc: Jonas Karlman Cc: Jernej Skrabec Link: https://patchwork.freedesktop.org/patch/msgid/20200726203324.3722593-4-sam@ravnborg.org --- drivers/gpu/drm/bridge/tc358764.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/gpu/drm/bridge/tc358764.c b/drivers/gpu/drm/bridge/tc358764.c index 5ac1430fab04..a277739fab58 100644 --- a/drivers/gpu/drm/bridge/tc358764.c +++ b/drivers/gpu/drm/bridge/tc358764.c @@ -375,7 +375,6 @@ static int tc358764_attach(struct drm_bridge *bridge, drm_connector_attach_encoder(&ctx->connector, bridge->encoder); drm_panel_attach(ctx->panel, &ctx->connector); ctx->connector.funcs->reset(&ctx->connector); - drm_connector_register(&ctx->connector); return 0; } @@ -384,10 +383,8 @@ static void tc358764_detach(struct drm_bridge *bridge) { struct tc358764 *ctx = bridge_to_tc358764(bridge); - drm_connector_unregister(&ctx->connector); drm_panel_detach(ctx->panel); ctx->panel = NULL; - drm_connector_put(&ctx->connector); } static const struct drm_bridge_funcs tc358764_bridge_funcs = { -- cgit v1.2.3 From 1644127f83bc72fcf2564dfbcab5802b587d964e Mon Sep 17 00:00:00 2001 From: Sam Ravnborg Date: Sun, 26 Jul 2020 22:33:13 +0200 Subject: drm/bridge: tc358764: add drm_panel_bridge support Prepare the tc358764 bridge driver for use in a chained setup by replacing direct use of drm_panel with drm_panel_bridge support. The bridge panel will use the connector type reported by the panel, where the connector for this driver hardcodes DRM_MODE_CONNECTOR_LVDS. The tc358764 did not add any additional info the the connector so the connector creation is passed to the bridge panel driver. v3: - Merge with patch to make connector creation optional to avoid creating two connectors (Laurent) - Pass connector creation to bridge panel, as this bridge driver did not add any extra info to the connector. - Set bridge.type to DRM_MODE_CONNECTOR_LVDS. v2: - Use PTR_ERR_OR_ZERO() (kbuild test robot) Signed-off-by: Sam Ravnborg Reviewed-by: Laurent Pinchart Cc: Laurent Pinchart Cc: kbuild test robot Cc: Andrzej Hajda Cc: Neil Armstrong Cc: Jonas Karlman Cc: Jernej Skrabec Link: https://patchwork.freedesktop.org/patch/msgid/20200726203324.3722593-5-sam@ravnborg.org --- drivers/gpu/drm/bridge/tc358764.c | 106 ++++++-------------------------------- 1 file changed, 15 insertions(+), 91 deletions(-) diff --git a/drivers/gpu/drm/bridge/tc358764.c b/drivers/gpu/drm/bridge/tc358764.c index a277739fab58..d89394bc5aa4 100644 --- a/drivers/gpu/drm/bridge/tc358764.c +++ b/drivers/gpu/drm/bridge/tc358764.c @@ -153,10 +153,9 @@ static const char * const tc358764_supplies[] = { struct tc358764 { struct device *dev; struct drm_bridge bridge; - struct drm_connector connector; struct regulator_bulk_data supplies[ARRAY_SIZE(tc358764_supplies)]; struct gpio_desc *gpio_reset; - struct drm_panel *panel; + struct drm_bridge *panel_bridge; int error; }; @@ -210,12 +209,6 @@ static inline struct tc358764 *bridge_to_tc358764(struct drm_bridge *bridge) return container_of(bridge, struct tc358764, bridge); } -static inline -struct tc358764 *connector_to_tc358764(struct drm_connector *connector) -{ - return container_of(connector, struct tc358764, connector); -} - static int tc358764_init(struct tc358764 *ctx) { u32 v = 0; @@ -278,43 +271,11 @@ stat