[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <20250825-red-bear-of-infinity-054eb7@houat>
Date: Mon, 25 Aug 2025 16:14:24 +0200
From: Maxime Ripard <mripard@...nel.org>
To: Mike Looijmans <mike.looijmans@...ic.nl>
Cc: dri-devel@...ts.freedesktop.org,
Andrzej Hajda <andrzej.hajda@...el.com>, David Airlie <airlied@...il.com>,
Jernej Skrabec <jernej.skrabec@...il.com>, Jonas Karlman <jonas@...boo.se>,
Laurent Pinchart <Laurent.pinchart@...asonboard.com>, Maarten Lankhorst <maarten.lankhorst@...ux.intel.com>,
Neil Armstrong <neil.armstrong@...aro.org>, Robert Foss <rfoss@...nel.org>, Simona Vetter <simona@...ll.ch>,
Thomas Zimmermann <tzimmermann@...e.de>, linux-kernel@...r.kernel.org
Subject: Re: [PATCH v2 2/2] drm: bridge: Add TI tmds181 and sn65dp159 driver
On Tue, Aug 19, 2025 at 04:25:29PM +0200, Mike Looijmans wrote:
> On 19-08-2025 15:22, Maxime Ripard wrote:
> > On Tue, Aug 19, 2025 at 07:31:15AM +0200, Mike Looijmans wrote:
> > > The tmds181 and sn65dp159 are "retimers" and hence can be considered
> > > HDMI-to-HDMI bridges. Typical usage is to convert the output of an
> > > FPGA into a valid HDMI signal, and it will typically be inserted
> > > between an encoder and hdmi-connector.
> > >
> > > Signed-off-by: Mike Looijmans <mike.looijmans@...ic.nl>
> > > ---
> > >
> > > Changes in v2:
> > > Use atomic_enable/disable
> > > Use #defines for bit fields in registers
> > > Allow HDMI 2 compliance
> > > Filter modes on clock range
> > > Use cross-over pixel frequency instead of manual overides
> > > Devicetree bindings according to standards
> > >
> > > drivers/gpu/drm/bridge/Kconfig | 11 +
> > > drivers/gpu/drm/bridge/Makefile | 1 +
> > > drivers/gpu/drm/bridge/ti-tmds181.c | 561 ++++++++++++++++++++++++++++
> > > 3 files changed, 573 insertions(+)
> > > create mode 100644 drivers/gpu/drm/bridge/ti-tmds181.c
> > >
> > > diff --git a/drivers/gpu/drm/bridge/Kconfig b/drivers/gpu/drm/bridge/Kconfig
> > > index b9e0ca85226a..753177fc9b50 100644
> > > --- a/drivers/gpu/drm/bridge/Kconfig
> > > +++ b/drivers/gpu/drm/bridge/Kconfig
> > > @@ -430,6 +430,17 @@ config DRM_TI_SN65DSI86
> > > help
> > > Texas Instruments SN65DSI86 DSI to eDP Bridge driver
> > > +config DRM_TI_TMDS181
> > > + tristate "TI TMDS181 and SN65DP159 HDMI retimer bridge driver"
> > > + depends on OF
> > > + select DRM_KMS_HELPER
> > > + select REGMAP_I2C
> > > + help
> > > + Enable this to support the TI TMDS181 and SN65DP159 HDMI retimers.
> > > + The SN65DP159 provides output into a cable (source) whereas the
> > > + TMDS181 is meant to forward a cable signal into a PCB (sink). Either
> > > + can be set up as source or sink though.
> > > +
> > > config DRM_TI_TPD12S015
> > > tristate "TI TPD12S015 HDMI level shifter and ESD protection"
> > > depends on OF
> > > diff --git a/drivers/gpu/drm/bridge/Makefile b/drivers/gpu/drm/bridge/Makefile
> > > index 245e8a27e3fc..f4b5089e903c 100644
> > > --- a/drivers/gpu/drm/bridge/Makefile
> > > +++ b/drivers/gpu/drm/bridge/Makefile
> > > @@ -39,6 +39,7 @@ obj-$(CONFIG_DRM_TI_SN65DSI83) += ti-sn65dsi83.o
> > > obj-$(CONFIG_DRM_TI_SN65DSI86) += ti-sn65dsi86.o
> > > obj-$(CONFIG_DRM_TI_TDP158) += ti-tdp158.o
> > > obj-$(CONFIG_DRM_TI_TFP410) += ti-tfp410.o
> > > +obj-$(CONFIG_DRM_TI_TMDS181) += ti-tmds181.o
> > > obj-$(CONFIG_DRM_TI_TPD12S015) += ti-tpd12s015.o
> > > obj-$(CONFIG_DRM_NWL_MIPI_DSI) += nwl-dsi.o
> > > obj-$(CONFIG_DRM_ITE_IT66121) += ite-it66121.o
> > > diff --git a/drivers/gpu/drm/bridge/ti-tmds181.c b/drivers/gpu/drm/bridge/ti-tmds181.c
> > > new file mode 100644
> > > index 000000000000..34cbdb066820
> > > --- /dev/null
> > > +++ b/drivers/gpu/drm/bridge/ti-tmds181.c
> > > @@ -0,0 +1,561 @@
> > > +// SPDX-License-Identifier: GPL-2.0
> > > +/*
> > > + * TI tmds181 and sn65dp159 HDMI redriver and retimer chips
> > > + *
> > > + * Copyright (C) 2018 - 2025 Topic Embedded Products <www.topic.nl>
> > > + *
> > > + * based on code
> > > + * Copyright (C) 2007 Hans Verkuil
> > > + * Copyright (C) 2016, 2017 Leon Woestenberg <leon@...ebranch.com>
> > > + */
> > > +
> > > +#include <linux/module.h>
> > > +#include <linux/kernel.h>
> > > +#include <linux/i2c.h>
> > > +#include <linux/slab.h>
> > > +#include <linux/of.h>
> > > +#include <linux/regmap.h>
> > > +#include <linux/gpio/consumer.h>
> > > +#include <linux/delay.h>
> > > +
> > > +#include <drm/drm_atomic_helper.h>
> > > +#include <drm/drm_bridge.h>
> > > +#include <drm/drm_crtc.h>
> > > +#include <drm/drm_print.h>
> > > +#include <drm/drm_probe_helper.h>
> > > +
> > > +MODULE_DESCRIPTION("I2C device driver for DP159 and TMDS181 redriver/retimer");
> > > +MODULE_AUTHOR("Mike Looijmans");
> > > +MODULE_LICENSE("GPL");
> > > +
> > > +#define TMDS181_REG_ID 0
> > > +#define TMDS181_REG_REV 0x8
> > > +#define TMDS181_REG_CTRL9 0x9
> > > +/* Registers A and B have a volatile bit, but we don't use it, so cache is ok */
> > > +#define TMDS181_REG_CTRLA 0xA
> > > +#define TMDS181_REG_CTRLB 0xB
> > > +#define TMDS181_REG_CTRLC 0xC
> > > +#define TMDS181_REG_EQUALIZER 0xD
> > > +#define TMDS181_REG_EYESCAN 0xE
> > > +
> > > +#define TMDS181_CTRL9_SIG_EN BIT(4)
> > > +#define TMDS181_CTRL9_PD_EN BIT(3)
> > > +#define TMDS181_CTRL9_HPD_AUTO_PWRDWN_DISABLE BIT(2)
> > > +#define TMDS181_CTRL9_I2C_DR_CTL GENMASK(1, 0)
> > > +
> > > +#define TMDS181_CTRLA_MODE_SINK BIT(7)
> > > +#define TMDS181_CTRLA_HPDSNK_GATE_EN BIT(6)
> > > +#define TMDS181_CTRLA_EQ_ADA_EN BIT(5)
> > > +#define TMDS181_CTRLA_EQ_EN BIT(4)
> > > +#define TMDS181_CTRLA_AUX_BRG_EN BIT(3)
> > > +#define TMDS181_CTRLA_APPLY BIT(2)
> > > +#define TMDS181_CTRLA_DEV_FUNC_MODE GENMASK(1, 0)
> > > +
> > > +#define TMDS181_CTRLB_SLEW_CTL GENMASK(7, 6)
> > > +#define TMDS181_CTRLB_HDMI_SEL_DVI BIT(5)
> > > +#define TMDS181_CTRLB_TX_TERM_CTL GENMASK(4, 3)
> > > +#define TMDS181_CTRLB_DDC_DR_SEL BIT(2)
> > > +#define TMDS181_CTRLB_TMDS_CLOCK_RATIO_STATUS BIT(1)
> > > +#define TMDS181_CTRLB_DDC_TRAIN_SET BIT(0)
> > > +
> > > +#define TMDS181_CTRLB_TX_TERM_150_300_OHMS 1
> > > +#define TMDS181_CTRLB_TX_TERM_75_150_OHMS 3
> > > +
> > > +#define TMDS181_CTRLC_VSWING_DATA GENMASK(7, 5)
> > > +#define TMDS181_CTRLC_VSWING_CLK GENMASK(4, 2)
> > > +#define TMDS181_CTRLC_HDMI_TWPST1 GENMASK(1, 0)
> > > +
> > > +#define TMDS181_EQ_DATA_LANE GENMASK(5, 3)
> > > +#define TMDS181_EQ_CLOCK_LANE GENMASK(2, 1)
> > > +#define TMDS181_EQ_DIS_HDMI2_SWG BIT(0)
> > > +
> > > +/* Above this data rate HDMI2 standards apply (TX termination) */
> > > +#define HDMI2_PIXEL_RATE_KHZ 340000
> > > +
> > > +enum tmds181_chip {
> > > + tmds181,
> > > + dp159,
> > > +};
> > > +
> > > +struct tmds181_data {
> > > + struct i2c_client *client;
> > > + struct regmap *regmap;
> > > + struct drm_bridge *next_bridge;
> > > + struct drm_bridge bridge;
> > > + u32 retimer_threshold_khz;
> > > + enum tmds181_chip chip;
> > > +};
> > > +
> > > +static inline struct tmds181_data *
> > > +drm_bridge_to_tmds181_data(struct drm_bridge *bridge)
> > > +{
> > > + return container_of(bridge, struct tmds181_data, bridge);
> > > +}
> > > +
> > > +/* Set "apply" bit in control register after making changes */
> > > +static int tmds181_apply_changes(struct tmds181_data *data)
> > > +{
> > > + return regmap_write_bits(data->regmap, TMDS181_REG_CTRLA,
> > > + TMDS181_CTRLA_APPLY, TMDS181_CTRLA_APPLY);
> > > +}
> > > +
> > > +static int tmds181_attach(struct drm_bridge *bridge,
> > > + enum drm_bridge_attach_flags flags)
> > > +{
> > > + struct tmds181_data *data = drm_bridge_to_tmds181_data(bridge);
> > > +
> > > + return drm_bridge_attach(bridge->encoder, data->next_bridge, bridge,
> > > + flags);
> > > +}
> > > +
> > > +static enum drm_mode_status
> > > +tmds181_mode_valid(struct drm_bridge *bridge, const struct drm_display_info *info,
> > > + const struct drm_display_mode *mode)
> > > +{
> > > + /* Clock limits: clk between 25 and 350 MHz, clk is 1/10 of bit clock */
> > > + if (mode->clock < 25000)
> > > + return MODE_CLOCK_LOW;
> > I'm a bit confused by your comment here. Why do we care about the bit clock here?
>
> Purpose here is to filter out modes that certainly won't work. For example,
> a standard 640x480 mode would have a 24MHz clock, which this chip doesn't
> support according to the datasheet.
Sure, but you're talking about the pixel clock here, not the bit clock.
So I'm still not sure why you need to mention it in the comment.
> > > + /* The actual HDMI clock (if provided) cannot exceed 350MHz */
> > > + if (mode->crtc_clock > 350000)
> > > + return MODE_CLOCK_HIGH;
> > And what do you call the "actual HDMI clock" and why wouldn't it be provided?
>
> The clock signal on the HDMI cable.
That's not mode->clock or mode->crtc_clock then. The datasheets mention
that it can go from 0.25 to 6 Gbps. That's the TMDS char rate, which is
calculated using drm_hdmi_compute_mode_clock().
> Again, aim is to block modes that certainly won't work.
>
> While developing the driver (and logging lots) the crtc_clock was
> often just "0".
>
> This is still tricky though. What if we'd have a DSI-to-HDMI bridge in
> between this and the crtc, and outputting a 4k display mode? The
> DSI-to-HDMI bridge output clock (to the retimer) won't match the CRTC
> clock, nor would it match the pixel clock (using 1/40 clock rate).
It's not tricky, really. DRM internals only ever talks in pixel clock
rates, ie assuming a pixel gets sent out for each clock cycle.
It will not be the case if the buses are somewhat-serial for example,
but it allows each bus driver to derive its own internal clock from the
pixel clock rate and its parameters.
So your example works just fine, because on the DSI bus and on the HDMI
bus, you'd still get the same pixel clock rate.
Maxime
Download attachment "signature.asc" of type "application/pgp-signature" (274 bytes)
Powered by blists - more mailing lists