diff mbox series

drm/bridge: sii902x: add support for DRM_BRIDGE_ATTACH_NO_CONNECTOR

Message ID 20220113144305.1074389-1-narmstrong@baylibre.com (mailing list archive)
State New, archived
Headers show
Series drm/bridge: sii902x: add support for DRM_BRIDGE_ATTACH_NO_CONNECTOR | expand

Commit Message

Neil Armstrong Jan. 13, 2022, 2:43 p.m. UTC
This adds support for DRM_BRIDGE_ATTACH_NO_CONNECTOR by adding the
bridge get_edid() and detect() callbacks after refactoring the connector
get_modes() and connector_detect() callbacks.

In order to keep the bridge working, extra code in get_modes() has been
moved to more logical places.

Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
---
 drivers/gpu/drm/bridge/sii902x.c | 129 ++++++++++++++++++++++++-------
 1 file changed, 99 insertions(+), 30 deletions(-)

Comments

Robert Foss Jan. 17, 2022, 9:53 a.m. UTC | #1
Hey Neil,

On Thu, 13 Jan 2022 at 15:43, Neil Armstrong <narmstrong@baylibre.com> wrote:
>
> This adds support for DRM_BRIDGE_ATTACH_NO_CONNECTOR by adding the
> bridge get_edid() and detect() callbacks after refactoring the connector
> get_modes() and connector_detect() callbacks.
>
> In order to keep the bridge working, extra code in get_modes() has been
> moved to more logical places.
>
> Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
> ---
>  drivers/gpu/drm/bridge/sii902x.c | 129 ++++++++++++++++++++++++-------
>  1 file changed, 99 insertions(+), 30 deletions(-)
>
> diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c
> index 89558e581530..65549fbfdc87 100644
> --- a/drivers/gpu/drm/bridge/sii902x.c
> +++ b/drivers/gpu/drm/bridge/sii902x.c
> @@ -166,10 +166,12 @@ struct sii902x {
>         struct i2c_client *i2c;
>         struct regmap *regmap;
>         struct drm_bridge bridge;
> +       struct drm_bridge *next_bridge;
>         struct drm_connector connector;
>         struct gpio_desc *reset_gpio;
>         struct i2c_mux_core *i2cmux;
>         struct regulator_bulk_data supplies[2];
> +       bool sink_is_hdmi;
>         /*
>          * Mutex protects audio and video functions from interfering
>          * each other, by keeping their i2c command sequences atomic.
> @@ -245,10 +247,8 @@ static void sii902x_reset(struct sii902x *sii902x)
>         gpiod_set_value(sii902x->reset_gpio, 0);
>  }
>
> -static enum drm_connector_status
> -sii902x_connector_detect(struct drm_connector *connector, bool force)
> +static enum drm_connector_status sii902x_detect(struct sii902x *sii902x)
>  {
> -       struct sii902x *sii902x = connector_to_sii902x(connector);
>         unsigned int status;
>
>         mutex_lock(&sii902x->mutex);
> @@ -261,6 +261,14 @@ sii902x_connector_detect(struct drm_connector *connector, bool force)
>                connector_status_connected : connector_status_disconnected;
>  }
>
> +static enum drm_connector_status
> +sii902x_connector_detect(struct drm_connector *connector, bool force)
> +{
> +       struct sii902x *sii902x = connector_to_sii902x(connector);
> +
> +       return sii902x_detect(sii902x);
> +}
> +
>  static const struct drm_connector_funcs sii902x_connector_funcs = {
>         .detect = sii902x_connector_detect,
>         .fill_modes = drm_helper_probe_single_connector_modes,
> @@ -270,42 +278,40 @@ static const struct drm_connector_funcs sii902x_connector_funcs = {
>         .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
>  };
>
> -static int sii902x_get_modes(struct drm_connector *connector)
> +static struct edid *sii902x_get_edid(struct sii902x *sii902x,
> +                                    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);
> +                       sii902x->sink_is_hdmi = true;
> +               else
> +                       sii902x->sink_is_hdmi = false;
>         }
>
> -       ret = drm_display_info_set_bus_formats(&connector->display_info,
> -                                              &bus_format, 1);
> -       if (ret)
> -               goto error_out;
> +       mutex_unlock(&sii902x->mutex);
>
> -       ret = regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA,
> -                                SII902X_SYS_CTRL_OUTPUT_MODE, output_mode);
> -       if (ret)
> -               goto error_out;
> +       return edid;
> +}
>
> -       ret = num;
> +static int sii902x_get_modes(struct drm_connector *connector)
> +{
> +       struct sii902x *sii902x = connector_to_sii902x(connector);
> +       struct edid *edid;
> +       int num = 0;
>
> -error_out:
> -       mutex_unlock(&sii902x->mutex);
> +       edid = sii902x_get_edid(sii902x, connector);
> +       drm_connector_update_edid_property(connector, edid);
> +       if (edid) {
> +               num = drm_add_edid_modes(connector, edid);
> +               kfree(edid);
> +       }
>
> -       return ret;
> +       return num;
>  }
>
>  static enum drm_mode_status sii902x_mode_valid(struct drm_connector *connector,
> @@ -354,12 +360,16 @@ static void sii902x_bridge_mode_set(struct drm_bridge *bridge,
>                                     const struct drm_display_mode *adj)
>  {
>         struct sii902x *sii902x = bridge_to_sii902x(bridge);
> +       u8 output_mode = SII902X_SYS_CTRL_OUTPUT_DVI;
>         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;
>
> +       if (sii902x->sink_is_hdmi)
> +               output_mode = SII902X_SYS_CTRL_OUTPUT_HDMI;
> +
>         buf[0] = pixel_clock_10kHz & 0xff;
>         buf[1] = pixel_clock_10kHz >> 8;
>         buf[2] = drm_mode_vrefresh(adj);
> @@ -375,6 +385,11 @@ static void sii902x_bridge_mode_set(struct drm_bridge *bridge,
>
>         mutex_lock(&sii902x->mutex);
>
> +       ret = regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA,
> +                                SII902X_SYS_CTRL_OUTPUT_MODE, output_mode);
> +       if (ret)
> +               goto out;
> +
>         ret = regmap_bulk_write(regmap, SII902X_TPI_VIDEO_DATA, buf, 10);
>         if (ret)
>                 goto out;
> @@ -405,13 +420,13 @@ static int sii902x_bridge_attach(struct drm_bridge *bridge,
>                                  enum drm_bridge_attach_flags flags)
>  {
>         struct sii902x *sii902x = bridge_to_sii902x(bridge);
> +       u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
>         struct drm_device *drm = bridge->dev;
>         int ret;
>
> -       if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
> -               DRM_ERROR("Fix bridge driver to make connector optional!");
> -               return -EINVAL;
> -       }
> +       if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)
> +               return drm_bridge_attach(bridge->encoder, sii902x->next_bridge,
> +                                        bridge, flags);
>
>         drm_connector_helper_add(&sii902x->connector,
>                                  &sii902x_connector_helper_funcs);
> @@ -433,16 +448,38 @@ static int sii902x_bridge_attach(struct drm_bridge *bridge,
>         else
>                 sii902x->connector.polled = DRM_CONNECTOR_POLL_CONNECT;
>
> +       ret = drm_display_info_set_bus_formats(&sii902x->connector.display_info,
> +                                              &bus_format, 1);
> +       if (ret)
> +               return ret;
> +
>         drm_connector_attach_encoder(&sii902x->connector, bridge->encoder);
>
>         return 0;
>  }
>
> +static enum drm_connector_status sii902x_bridge_detect(struct drm_bridge *bridge)
> +{
> +       struct sii902x *sii902x = bridge_to_sii902x(bridge);
> +
> +       return sii902x_detect(sii902x);
> +}
> +
> +static struct edid *sii902x_bridge_get_edid(struct drm_bridge *bridge,
> +                                           struct drm_connector *connector)
> +{
> +       struct sii902x *sii902x = bridge_to_sii902x(bridge);
> +
> +       return sii902x_get_edid(sii902x, connector);
> +}
> +
>  static const struct drm_bridge_funcs sii902x_bridge_funcs = {
>         .attach = sii902x_bridge_attach,
>         .mode_set = sii902x_bridge_mode_set,
>         .disable = sii902x_bridge_disable,
>         .enable = sii902x_bridge_enable,
> +       .detect = sii902x_bridge_detect,
> +       .get_edid = sii902x_bridge_get_edid,
>  };
>
>  static int sii902x_mute(struct sii902x *sii902x, bool mute)
> @@ -829,8 +866,12 @@ static irqreturn_t sii902x_interrupt(int irq, void *data)
>
>         mutex_unlock(&sii902x->mutex);
>
> -       if ((status & SII902X_HOTPLUG_EVENT) && sii902x->bridge.dev)
> +       if ((status & SII902X_HOTPLUG_EVENT) && sii902x->bridge.dev) {
>                 drm_helper_hpd_irq_event(sii902x->bridge.dev);
> +               drm_bridge_hpd_notify(&sii902x->bridge, (status & SII902X_PLUGGED_STATUS)
> +                                                               ? connector_status_connected
> +                                                               : connector_status_disconnected);
> +       }
>
>         return IRQ_HANDLED;
>  }
> @@ -1001,6 +1042,11 @@ static int sii902x_init(struct sii902x *sii902x)
>         sii902x->bridge.funcs = &sii902x_bridge_funcs;
>         sii902x->bridge.of_node = dev->of_node;
>         sii902x->bridge.timings = &default_sii902x_timings;
> +       sii902x->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID;
> +
> +       if (sii902x->i2c->irq > 0)
> +               sii902x->bridge.ops |= DRM_BRIDGE_OP_HPD;
> +
>         drm_bridge_add(&sii902x->bridge);
>
>         sii902x_audio_codec_init(sii902x, dev);
> @@ -1022,6 +1068,7 @@ static int sii902x_probe(struct i2c_client *client,
>                          const struct i2c_device_id *id)
>  {
>         struct device *dev = &client->dev;
> +       struct device_node *endpoint;
>         struct sii902x *sii902x;
>         int ret;
>
> @@ -1049,6 +1096,28 @@ static int sii902x_probe(struct i2c_client *client,
>                 return PTR_ERR(sii902x->reset_gpio);
>         }
>
> +       endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 1, -1);
> +       if (endpoint) {
> +               struct device_node *remote = of_graph_get_remote_port_parent(endpoint);
> +
> +               of_node_put(endpoint);
> +               if (!remote) {
> +                       dev_err(dev, "Endpoint in port@1 unconnected\n");
> +                       return -ENODEV;
> +               }
> +
> +               if (!of_device_is_available(remote)) {
> +                       dev_err(dev, "port@1 remote device is disabled\n");
> +                       of_node_put(remote);
> +                       return -ENODEV;
> +               }
> +
> +               sii902x->next_bridge = of_drm_find_bridge(remote);
> +               of_node_put(remote);
> +               if (!sii902x->next_bridge)
> +                       return -EPROBE_DEFER;
> +       }
> +
>         mutex_init(&sii902x->mutex);
>
>         sii902x->supplies[0].supply = "iovcc";

Reviewed-by: Robert Foss <robert.foss@linaro.org>

Applied to drm-misc-next.
Neil Armstrong Aug. 8, 2022, 9:15 a.m. UTC | #2
Hi Dmitry,

On 31/07/2022 22:07, Dmitry Osipenko wrote:
> 13.01.2022 17:43, Neil Armstrong пишет:
>> This adds support for DRM_BRIDGE_ATTACH_NO_CONNECTOR by adding the
>> bridge get_edid() and detect() callbacks after refactoring the connector
>> get_modes() and connector_detect() callbacks.
>>
>> In order to keep the bridge working, extra code in get_modes() has been
>> moved to more logical places.
>>
>> Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
>> ---
>>   drivers/gpu/drm/bridge/sii902x.c | 129 ++++++++++++++++++++++++-------
>>   

1 file changed, 99 insertions(+), 30 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c
>> index 89558e581530..65549fbfdc87 100644
>> --- a/drivers/gpu/drm/bridge/sii902x.c
>> +++ b/drivers/gpu/drm/bridge/sii902x.c
>> @@ -166,10 +166,12 @@ struct sii902x {
>>   	struct i2c_client *i2c;
>>   	struct regmap *regmap;
>>   	struct drm_bridge bridge;
>> +	struct drm_bridge *next_bridge;
>>   	struct drm_connector connector;
>>   	struct gpio_desc *reset_gpio;
>>   	struct i2c_mux_core *i2cmux;
>>   	struct regulator_bulk_data supplies[2];
>> +	bool sink_is_hdmi;
>>   	/*
>>   	 * Mutex protects audio and video functions from interfering
>>   	 * each other, by keeping their i2c command sequences atomic.
>> @@ -245,10 +247,8 @@ static void sii902x_reset(struct sii902x *sii902x)
>>   	gpiod_set_value(sii902x->reset_gpio, 0);
>>   }
>>   
>> -static enum drm_connector_status
>> -sii902x_connector_detect(struct drm_connector *connector, bool force)
>> +static enum drm_connector_status sii902x_detect(struct sii902x *sii902x)
>>   {
>> -	struct sii902x *sii902x = connector_to_sii902x(connector);
>>   	unsigned int status;
>>   
>>   	mutex_lock(&sii902x->mutex);
>> @@ -261,6 +261,14 @@ sii902x_connector_detect(struct drm_connector *connector, bool force)
>>   	       connector_status_connected : connector_status_disconnected;
>>   }
>>   
>> +static enum drm_connector_status
>> +sii902x_connector_detect(struct drm_connector *connector, bool force)
>> +{
>> +	struct sii902x *sii902x = connector_to_sii902x(connector);
>> +
>> +	return sii902x_detect(sii902x);
>> +}
>> +
>>   static const struct drm_connector_funcs sii902x_connector_funcs = {
>>   	.detect = sii902x_connector_detect,
>>   	.fill_modes = drm_helper_probe_single_connector_modes,
>> @@ -270,42 +278,40 @@ static const struct drm_connector_funcs sii902x_connector_funcs = {
>>   	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
>>   };
>>   
>> -static int sii902x_get_modes(struct drm_connector *connector)
>> +static struct edid *sii902x_get_edid(struct sii902x *sii902x,
>> +				     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);
>> +			sii902x->sink_is_hdmi = true;
>> +		else
>> +			sii902x->sink_is_hdmi = false;
>>   	}
>>   
>> -	ret = drm_display_info_set_bus_formats(&connector->display_info,
>> -					       &bus_format, 1);
>> -	if (ret)
>> -		goto error_out;
>> +	mutex_unlock(&sii902x->mutex);
>>   
>> -	ret = regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA,
>> -				 SII902X_SYS_CTRL_OUTPUT_MODE, output_mode);
>> -	if (ret)
>> -		goto error_out;
>> +	return edid;
>> +}
>>   
>> -	ret = num;
>> +static int sii902x_get_modes(struct drm_connector *connector)
>> +{
>> +	struct sii902x *sii902x = connector_to_sii902x(connector);
>> +	struct edid *edid;
>> +	int num = 0;
>>   
>> -error_out:
>> -	mutex_unlock(&sii902x->mutex);
>> +	edid = sii902x_get_edid(sii902x, connector);
>> +	drm_connector_update_edid_property(connector, edid);
>> +	if (edid) {
>> +		num = drm_add_edid_modes(connector, edid);
>> +		kfree(edid);
>> +	}
>>   
>> -	return ret;
>> +	return num;
>>   }
>>   
>>   static enum drm_mode_status sii902x_mode_valid(struct drm_connector *connector,
>> @@ -354,12 +360,16 @@ static void sii902x_bridge_mode_set(struct drm_bridge *bridge,
>>   				    const struct drm_display_mode *adj)
>>   {
>>   	struct sii902x *sii902x = bridge_to_sii902x(bridge);
>> +	u8 output_mode = SII902X_SYS_CTRL_OUTPUT_DVI;
>>   	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;
>>   
>> +	if (sii902x->sink_is_hdmi)
>> +		output_mode = SII902X_SYS_CTRL_OUTPUT_HDMI;
>> +
>>   	buf[0] = pixel_clock_10kHz & 0xff;
>>   	buf[1] = pixel_clock_10kHz >> 8;
>>   	buf[2] = drm_mode_vrefresh(adj);
>> @@ -375,6 +385,11 @@ static void sii902x_bridge_mode_set(struct drm_bridge *bridge,
>>   
>>   	mutex_lock(&sii902x->mutex);
>>   
>> +	ret = regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA,
>> +				 SII902X_SYS_CTRL_OUTPUT_MODE, output_mode);
>> +	if (ret)
>> +		goto out;
>> +
>>   	ret = regmap_bulk_write(regmap, SII902X_TPI_VIDEO_DATA, buf, 10);
>>   	if (ret)
>>   		goto out;
>> @@ -405,13 +420,13 @@ static int sii902x_bridge_attach(struct drm_bridge *bridge,
>>   				 enum drm_bridge_attach_flags flags)
>>   {
>>   	struct sii902x *sii902x = bridge_to_sii902x(bridge);
>> +	u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
>>   	struct drm_device *drm = bridge->dev;
>>   	int ret;
>>   
>> -	if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
>> -		DRM_ERROR("Fix bridge driver to make connector optional!");
>> -		return -EINVAL;
>> -	}
>> +	if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)
>> +		return drm_bridge_attach(bridge->encoder, sii902x->next_bridge,
>> +					 bridge, flags);
>>   
>>   	drm_connector_helper_add(&sii902x->connector,
>>   				 &sii902x_connector_helper_funcs);
>> @@ -433,16 +448,38 @@ static int sii902x_bridge_attach(struct drm_bridge *bridge,
>>   	else
>>   		sii902x->connector.polled = DRM_CONNECTOR_POLL_CONNECT;
>>   
>> +	ret = drm_display_info_set_bus_formats(&sii902x->connector.display_info,
>> +					       &bus_format, 1);
>> +	if (ret)
>> +		return ret;
>> +
>>   	drm_connector_attach_encoder(&sii902x->connector, bridge->encoder);
>>   
>>   	return 0;
>>   }
>>   
>> +static enum drm_connector_status sii902x_bridge_detect(struct drm_bridge *bridge)
>> +{
>> +	struct sii902x *sii902x = bridge_to_sii902x(bridge);
>> +
>> +	return sii902x_detect(sii902x);
>> +}
>> +
>> +static struct edid *sii902x_bridge_get_edid(struct drm_bridge *bridge,
>> +					    struct drm_connector *connector)
>> +{
>> +	struct sii902x *sii902x = bridge_to_sii902x(bridge);
>> +
>> +	return sii902x_get_edid(sii902x, connector);
>> +}
>> +
>>   static const struct drm_bridge_funcs sii902x_bridge_funcs = {
>>   	.attach = sii902x_bridge_attach,
>>   	.mode_set = sii902x_bridge_mode_set,
>>   	.disable = sii902x_bridge_disable,
>>   	.enable = sii902x_bridge_enable,
>> +	.detect = sii902x_bridge_detect,
>> +	.get_edid = sii902x_bridge_get_edid,
>>   };
>>   
>>   static int sii902x_mute(struct sii902x *sii902x, bool mute)
>> @@ -829,8 +866,12 @@ static irqreturn_t sii902x_interrupt(int irq, void *data)
>>   
>>   	mutex_unlock(&sii902x->mutex);
>>   
>> -	if ((status & SII902X_HOTPLUG_EVENT) && sii902x->bridge.dev)
>> +	if ((status & SII902X_HOTPLUG_EVENT) && sii902x->bridge.dev) {
>>   		drm_helper_hpd_irq_event(sii902x->bridge.dev);
>> +		drm_bridge_hpd_notify(&sii902x->bridge, (status & SII902X_PLUGGED_STATUS)
>> +								? connector_status_connected
>> +								: connector_status_disconnected);
>> +	}
>>   
>>   	return IRQ_HANDLED;
>>   }
>> @@ -1001,6 +1042,11 @@ static int sii902x_init(struct sii902x *sii902x)
>>   	sii902x->bridge.funcs = &sii902x_bridge_funcs;
>>   	sii902x->bridge.of_node = dev->of_node;
>>   	sii902x->bridge.timings = &default_sii902x_timings;
>> +	sii902x->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID;
>> +
>> +	if (sii902x->i2c->irq > 0)
>> +		sii902x->bridge.ops |= DRM_BRIDGE_OP_HPD;
>> +
>>   	drm_bridge_add(&sii902x->bridge);
>>   
>>   	sii902x_audio_codec_init(sii902x, dev);
>> @@ -1022,6 +1068,7 @@ static int sii902x_probe(struct i2c_client *client,
>>   			 const struct i2c_device_id *id)
>>   {
>>   	struct device *dev = &client->dev;
>> +	struct device_node *endpoint;
>>   	struct sii902x *sii902x;
>>   	int ret;
>>   
>> @@ -1049,6 +1096,28 @@ static int sii902x_probe(struct i2c_client *client,
>>   		return PTR_ERR(sii902x->reset_gpio);
>>   	}
>>   
>> +	endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 1, -1);
>> +	if (endpoint) {
>> +		struct device_node *remote = of_graph_get_remote_port_parent(endpoint);
>> +
>> +		of_node_put(endpoint);
>> +		if (!remote) {
>> +			dev_err(dev, "Endpoint in port@1 unconnected\n");
>> +			return -ENODEV;
>> +		}
>> +
>> +		if (!of_device_is_available(remote)) {
>> +			dev_err(dev, "port@1 remote device is disabled\n");
>> +			of_node_put(remote);
>> +			return -ENODEV;
>> +		}
>> +
>> +		sii902x->next_bridge = of_drm_find_bridge(remote);
>> +		of_node_put(remote);
>> +		if (!sii902x->next_bridge)
>> +			return -EPROBE_DEFER;
> 
> Hi,
> 
> This patch broke ARM/QEMU vexpress display because of_drm_find_bridge()
> always fail with -EPROBE_DEFER. Reverting this patch returns display
> back. Please fix or revert, thanks in advance.

Can you share a QEMU cmdline to reproduce ?

Neil
Neil Armstrong Aug. 8, 2022, 9:51 a.m. UTC | #3
On 08/08/2022 11:15, Neil Armstrong wrote:
> Hi Dmitry,
> 
> On 31/07/2022 22:07, Dmitry Osipenko wrote:
>> 13.01.2022 17:43, Neil Armstrong пишет:
>>> This adds support for DRM_BRIDGE_ATTACH_NO_CONNECTOR by adding the
>>> bridge get_edid() and detect() callbacks after refactoring the connector
>>> get_modes() and connector_detect() callbacks.
>>>
>>> In order to keep the bridge working, extra code in get_modes() has been
>>> moved to more logical places.
>>>
>>> Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
>>> ---
>>>   drivers/gpu/drm/bridge/sii902x.c | 129 ++++++++++++++++++++++++-------
> 
> 1 file changed, 99 insertions(+), 30 deletions(-)
>>>
>>> diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c
>>> index 89558e581530..65549fbfdc87 100644
>>> --- a/drivers/gpu/drm/bridge/sii902x.c
>>> +++ b/drivers/gpu/drm/bridge/sii902x.c

[...]

>>>       }
>>> +    endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 1, -1);
>>> +    if (endpoint) {
>>> +        struct device_node *remote = of_graph_get_remote_port_parent(endpoint);
>>> +
>>> +        of_node_put(endpoint);
>>> +        if (!remote) {
>>> +            dev_err(dev, "Endpoint in port@1 unconnected\n");
>>> +            return -ENODEV;
>>> +        }
>>> +
>>> +        if (!of_device_is_available(remote)) {
>>> +            dev_err(dev, "port@1 remote device is disabled\n");
>>> +            of_node_put(remote);
>>> +            return -ENODEV;
>>> +        }
>>> +
>>> +        sii902x->next_bridge = of_drm_find_bridge(remote);
>>> +        of_node_put(remote);
>>> +        if (!sii902x->next_bridge)
>>> +            return -EPROBE_DEFER;
>>
>> Hi,
>>
>> This patch broke ARM/QEMU vexpress display because of_drm_find_bridge()
>> always fail with -EPROBE_DEFER. Reverting this patch returns display
>> back. Please fix or revert, thanks in advance.
> 
> Can you share a QEMU cmdline to reproduce ?

Actually the vexpress DT has multiple input ports instead of a single input port at @0
and an output port at @1 like documented in the bindings:

vexpress-v2m.dtsi#L303-L307:
ports {
	#address-cells = <1>;
	#size-cells = <0>;

	/*
	 * Both the core tile and the motherboard routes their output
	 * pads to this transmitter. The motherboard system controller
	 * can select one of them as input using a mux register in
	 * "arm,vexpress-muxfpga". The Vexpress with the CA9 core tile is
	 * the only platform with this specific set-up.
	 */
	port@0 {
		reg = <0>;
		dvi_bridge_in_ct: endpoint {
			remote-endpoint = <&clcd_pads_ct>;
		};
	};
	port@1 {
		reg = <1>;
		dvi_bridge_in_mb: endpoint {
			remote-endpoint = <&clcd_pads_mb>;
		};
	};
};

bindings:
   ports:
     $ref: /schemas/graph.yaml#/properties/ports

     properties:
       port@0:
         $ref: /schemas/graph.yaml#/properties/port
         description: Parallel RGB input port

       port@1:
         $ref: /schemas/graph.yaml#/properties/port
         description: HDMI output port

       port@3:
         $ref: /schemas/graph.yaml#/properties/port
         description: Sound input port

The patch is conform to the bindings, the DT was working but is actually not valid.

Neil

> 
> Neil
Dmitry Osipenko Aug. 15, 2022, 12:15 a.m. UTC | #4
08.08.2022 12:51, Neil Armstrong пишет:
> On 08/08/2022 11:15, Neil Armstrong wrote:
>> Hi Dmitry,
>>
>> On 31/07/2022 22:07, Dmitry Osipenko wrote:
>>> 13.01.2022 17:43, Neil Armstrong пишет:
>>>> This adds support for DRM_BRIDGE_ATTACH_NO_CONNECTOR by adding the
>>>> bridge get_edid() and detect() callbacks after refactoring the
>>>> connector
>>>> get_modes() and connector_detect() callbacks.
>>>>
>>>> In order to keep the bridge working, extra code in get_modes() has been
>>>> moved to more logical places.
>>>>
>>>> Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
>>>> ---
>>>>   drivers/gpu/drm/bridge/sii902x.c | 129
>>>> ++++++++++++++++++++++++-------
>>
>> 1 file changed, 99 insertions(+), 30 deletions(-)
>>>>
>>>> diff --git a/drivers/gpu/drm/bridge/sii902x.c
>>>> b/drivers/gpu/drm/bridge/sii902x.c
>>>> index 89558e581530..65549fbfdc87 100644
>>>> --- a/drivers/gpu/drm/bridge/sii902x.c
>>>> +++ b/drivers/gpu/drm/bridge/sii902x.c
> 
> [...]
> 
>>>>       }
>>>> +    endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 1, -1);
>>>> +    if (endpoint) {
>>>> +        struct device_node *remote =
>>>> of_graph_get_remote_port_parent(endpoint);
>>>> +
>>>> +        of_node_put(endpoint);
>>>> +        if (!remote) {
>>>> +            dev_err(dev, "Endpoint in port@1 unconnected\n");
>>>> +            return -ENODEV;
>>>> +        }
>>>> +
>>>> +        if (!of_device_is_available(remote)) {
>>>> +            dev_err(dev, "port@1 remote device is disabled\n");
>>>> +            of_node_put(remote);
>>>> +            return -ENODEV;
>>>> +        }
>>>> +
>>>> +        sii902x->next_bridge = of_drm_find_bridge(remote);
>>>> +        of_node_put(remote);
>>>> +        if (!sii902x->next_bridge)
>>>> +            return -EPROBE_DEFER;
>>>
>>> Hi,
>>>
>>> This patch broke ARM/QEMU vexpress display because of_drm_find_bridge()
>>> always fail with -EPROBE_DEFER. Reverting this patch returns display
>>> back. Please fix or revert, thanks in advance.
>>
>> Can you share a QEMU cmdline to reproduce ?
> 
> Actually the vexpress DT has multiple input ports instead of a single
> input port at @0
> and an output port at @1 like documented in the bindings:
> 
> vexpress-v2m.dtsi#L303-L307:
> ports {
>     #address-cells = <1>;
>     #size-cells = <0>;
> 
>     /*
>      * Both the core tile and the motherboard routes their output
>      * pads to this transmitter. The motherboard system controller
>      * can select one of them as input using a mux register in
>      * "arm,vexpress-muxfpga". The Vexpress with the CA9 core tile is
>      * the only platform with this specific set-up.
>      */
>     port@0 {
>         reg = <0>;
>         dvi_bridge_in_ct: endpoint {
>             remote-endpoint = <&clcd_pads_ct>;
>         };
>     };
>     port@1 {
>         reg = <1>;
>         dvi_bridge_in_mb: endpoint {
>             remote-endpoint = <&clcd_pads_mb>;
>         };
>     };
> };
> 
> bindings:
>   ports:
>     $ref: /schemas/graph.yaml#/properties/ports
> 
>     properties:
>       port@0:
>         $ref: /schemas/graph.yaml#/properties/port
>         description: Parallel RGB input port
> 
>       port@1:
>         $ref: /schemas/graph.yaml#/properties/port
>         description: HDMI output port
> 
>       port@3:
>         $ref: /schemas/graph.yaml#/properties/port
>         description: Sound input port
> 
> The patch is conform to the bindings, the DT was working but is actually
> not valid.

I haven't looked closely at how to fix this properly, but if we can fix
it using of_machine_is_compatible("arm,vexpress") workaround in the
driver, then it will be good enough at least as a temporal fix, IMO.
Dmitry Osipenko Aug. 15, 2022, 12:18 a.m. UTC | #5
08.08.2022 12:15, Neil Armstrong пишет:
> Hi Dmitry,
> 
> On 31/07/2022 22:07, Dmitry Osipenko wrote:
>> 13.01.2022 17:43, Neil Armstrong пишет:
>>> This adds support for DRM_BRIDGE_ATTACH_NO_CONNECTOR by adding the
>>> bridge get_edid() and detect() callbacks after refactoring the connector
>>> get_modes() and connector_detect() callbacks.
>>>
>>> In order to keep the bridge working, extra code in get_modes() has been
>>> moved to more logical places.
>>>
>>> Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
>>> ---
>>>   drivers/gpu/drm/bridge/sii902x.c | 129 ++++++++++++++++++++++++-------
>>>   
> 
> 1 file changed, 99 insertions(+), 30 deletions(-)
>>>
>>> diff --git a/drivers/gpu/drm/bridge/sii902x.c
>>> b/drivers/gpu/drm/bridge/sii902x.c
>>> index 89558e581530..65549fbfdc87 100644
>>> --- a/drivers/gpu/drm/bridge/sii902x.c
>>> +++ b/drivers/gpu/drm/bridge/sii902x.c
>>> @@ -166,10 +166,12 @@ struct sii902x {
>>>       struct i2c_client *i2c;
>>>       struct regmap *regmap;
>>>       struct drm_bridge bridge;
>>> +    struct drm_bridge *next_bridge;
>>>       struct drm_connector connector;
>>>       struct gpio_desc *reset_gpio;
>>>       struct i2c_mux_core *i2cmux;
>>>       struct regulator_bulk_data supplies[2];
>>> +    bool sink_is_hdmi;
>>>       /*
>>>        * Mutex protects audio and video functions from interfering
>>>        * each other, by keeping their i2c command sequences atomic.
>>> @@ -245,10 +247,8 @@ static void sii902x_reset(struct sii902x *sii902x)
>>>       gpiod_set_value(sii902x->reset_gpio, 0);
>>>   }
>>>   -static enum drm_connector_status
>>> -sii902x_connector_detect(struct drm_connector *connector, bool force)
>>> +static enum drm_connector_status sii902x_detect(struct sii902x
>>> *sii902x)
>>>   {
>>> -    struct sii902x *sii902x = connector_to_sii902x(connector);
>>>       unsigned int status;
>>>         mutex_lock(&sii902x->mutex);
>>> @@ -261,6 +261,14 @@ sii902x_connector_detect(struct drm_connector
>>> *connector, bool force)
>>>              connector_status_connected : connector_status_disconnected;
>>>   }
>>>   +static enum drm_connector_status
>>> +sii902x_connector_detect(struct drm_connector *connector, bool force)
>>> +{
>>> +    struct sii902x *sii902x = connector_to_sii902x(connector);
>>> +
>>> +    return sii902x_detect(sii902x);
>>> +}
>>> +
>>>   static const struct drm_connector_funcs sii902x_connector_funcs = {
>>>       .detect = sii902x_connector_detect,
>>>       .fill_modes = drm_helper_probe_single_connector_modes,
>>> @@ -270,42 +278,40 @@ static const struct drm_connector_funcs
>>> sii902x_connector_funcs = {
>>>       .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
>>>   };
>>>   -static int sii902x_get_modes(struct drm_connector *connector)
>>> +static struct edid *sii902x_get_edid(struct sii902x *sii902x,
>>> +                     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);
>>> +            sii902x->sink_is_hdmi = true;
>>> +        else
>>> +            sii902x->sink_is_hdmi = false;
>>>       }
>>>   -    ret = drm_display_info_set_bus_formats(&connector->display_info,
>>> -                           &bus_format, 1);
>>> -    if (ret)
>>> -        goto error_out;
>>> +    mutex_unlock(&sii902x->mutex);
>>>   -    ret = regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA,
>>> -                 SII902X_SYS_CTRL_OUTPUT_MODE, output_mode);
>>> -    if (ret)
>>> -        goto error_out;
>>> +    return edid;
>>> +}
>>>   -    ret = num;
>>> +static int sii902x_get_modes(struct drm_connector *connector)
>>> +{
>>> +    struct sii902x *sii902x = connector_to_sii902x(connector);
>>> +    struct edid *edid;
>>> +    int num = 0;
>>>   -error_out:
>>> -    mutex_unlock(&sii902x->mutex);
>>> +    edid = sii902x_get_edid(sii902x, connector);
>>> +    drm_connector_update_edid_property(connector, edid);
>>> +    if (edid) {
>>> +        num = drm_add_edid_modes(connector, edid);
>>> +        kfree(edid);
>>> +    }
>>>   -    return ret;
>>> +    return num;
>>>   }
>>>     static enum drm_mode_status sii902x_mode_valid(struct
>>> drm_connector *connector,
>>> @@ -354,12 +360,16 @@ static void sii902x_bridge_mode_set(struct
>>> drm_bridge *bridge,
>>>                       const struct drm_display_mode *adj)
>>>   {
>>>       struct sii902x *sii902x = bridge_to_sii902x(bridge);
>>> +    u8 output_mode = SII902X_SYS_CTRL_OUTPUT_DVI;
>>>       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;
>>>   +    if (sii902x->sink_is_hdmi)
>>> +        output_mode = SII902X_SYS_CTRL_OUTPUT_HDMI;
>>> +
>>>       buf[0] = pixel_clock_10kHz & 0xff;
>>>       buf[1] = pixel_clock_10kHz >> 8;
>>>       buf[2] = drm_mode_vrefresh(adj);
>>> @@ -375,6 +385,11 @@ static void sii902x_bridge_mode_set(struct
>>> drm_bridge *bridge,
>>>         mutex_lock(&sii902x->mutex);
>>>   +    ret = regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA,
>>> +                 SII902X_SYS_CTRL_OUTPUT_MODE, output_mode);
>>> +    if (ret)
>>> +        goto out;
>>> +
>>>       ret = regmap_bulk_write(regmap, SII902X_TPI_VIDEO_DATA, buf, 10);
>>>       if (ret)
>>>           goto out;
>>> @@ -405,13 +420,13 @@ static int sii902x_bridge_attach(struct
>>> drm_bridge *bridge,
>>>                    enum drm_bridge_attach_flags flags)
>>>   {
>>>       struct sii902x *sii902x = bridge_to_sii902x(bridge);
>>> +    u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
>>>       struct drm_device *drm = bridge->dev;
>>>       int ret;
>>>   -    if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
>>> -        DRM_ERROR("Fix bridge driver to make connector optional!");
>>> -        return -EINVAL;
>>> -    }
>>> +    if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)
>>> +        return drm_bridge_attach(bridge->encoder, sii902x->next_bridge,
>>> +                     bridge, flags);
>>>         drm_connector_helper_add(&sii902x->connector,
>>>                    &sii902x_connector_helper_funcs);
>>> @@ -433,16 +448,38 @@ static int sii902x_bridge_attach(struct
>>> drm_bridge *bridge,
>>>       else
>>>           sii902x->connector.polled = DRM_CONNECTOR_POLL_CONNECT;
>>>   +    ret =
>>> drm_display_info_set_bus_formats(&sii902x->connector.display_info,
>>> +                           &bus_format, 1);
>>> +    if (ret)
>>> +        return ret;
>>> +
>>>       drm_connector_attach_encoder(&sii902x->connector,
>>> bridge->encoder);
>>>         return 0;
>>>   }
>>>   +static enum drm_connector_status sii902x_bridge_detect(struct
>>> drm_bridge *bridge)
>>> +{
>>> +    struct sii902x *sii902x = bridge_to_sii902x(bridge);
>>> +
>>> +    return sii902x_detect(sii902x);
>>> +}
>>> +
>>> +static struct edid *sii902x_bridge_get_edid(struct drm_bridge *bridge,
>>> +                        struct drm_connector *connector)
>>> +{
>>> +    struct sii902x *sii902x = bridge_to_sii902x(bridge);
>>> +
>>> +    return sii902x_get_edid(sii902x, connector);
>>> +}
>>> +
>>>   static const struct drm_bridge_funcs sii902x_bridge_funcs = {
>>>       .attach = sii902x_bridge_attach,
>>>       .mode_set = sii902x_bridge_mode_set,
>>>       .disable = sii902x_bridge_disable,
>>>       .enable = sii902x_bridge_enable,
>>> +    .detect = sii902x_bridge_detect,
>>> +    .get_edid = sii902x_bridge_get_edid,
>>>   };
>>>     static int sii902x_mute(struct sii902x *sii902x, bool mute)
>>> @@ -829,8 +866,12 @@ static irqreturn_t sii902x_interrupt(int irq,
>>> void *data)
>>>         mutex_unlock(&sii902x->mutex);
>>>   -    if ((status & SII902X_HOTPLUG_EVENT) && sii902x->bridge.dev)
>>> +    if ((status & SII902X_HOTPLUG_EVENT) && sii902x->bridge.dev) {
>>>           drm_helper_hpd_irq_event(sii902x->bridge.dev);
>>> +        drm_bridge_hpd_notify(&sii902x->bridge, (status &
>>> SII902X_PLUGGED_STATUS)
>>> +                                ? connector_status_connected
>>> +                                : connector_status_disconnected);
>>> +    }
>>>         return IRQ_HANDLED;
>>>   }
>>> @@ -1001,6 +1042,11 @@ static int sii902x_init(struct sii902x *sii902x)
>>>       sii902x->bridge.funcs = &sii902x_bridge_funcs;
>>>       sii902x->bridge.of_node = dev->of_node;
>>>       sii902x->bridge.timings = &default_sii902x_timings;
>>> +    sii902x->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID;
>>> +
>>> +    if (sii902x->i2c->irq > 0)
>>> +        sii902x->bridge.ops |= DRM_BRIDGE_OP_HPD;
>>> +
>>>       drm_bridge_add(&sii902x->bridge);
>>>         sii902x_audio_codec_init(sii902x, dev);
>>> @@ -1022,6 +1068,7 @@ static int sii902x_probe(struct i2c_client
>>> *client,
>>>                const struct i2c_device_id *id)
>>>   {
>>>       struct device *dev = &client->dev;
>>> +    struct device_node *endpoint;
>>>       struct sii902x *sii902x;
>>>       int ret;
>>>   @@ -1049,6 +1096,28 @@ static int sii902x_probe(struct i2c_client
>>> *client,
>>>           return PTR_ERR(sii902x->reset_gpio);
>>>       }
>>>   +    endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 1, -1);
>>> +    if (endpoint) {
>>> +        struct device_node *remote =
>>> of_graph_get_remote_port_parent(endpoint);
>>> +
>>> +        of_node_put(endpoint);
>>> +        if (!remote) {
>>> +            dev_err(dev, "Endpoint in port@1 unconnected\n");
>>> +            return -ENODEV;
>>> +        }
>>> +
>>> +        if (!of_device_is_available(remote)) {
>>> +            dev_err(dev, "port@1 remote device is disabled\n");
>>> +            of_node_put(remote);
>>> +            return -ENODEV;
>>> +        }
>>> +
>>> +        sii902x->next_bridge = of_drm_find_bridge(remote);
>>> +        of_node_put(remote);
>>> +        if (!sii902x->next_bridge)
>>> +            return -EPROBE_DEFER;
>>
>> Hi,
>>
>> This patch broke ARM/QEMU vexpress display because of_drm_find_bridge()
>> always fail with -EPROBE_DEFER. Reverting this patch returns display
>> back. Please fix or revert, thanks in advance.
> 
> Can you share a QEMU cmdline to reproduce ?

qemu-system-arm -cpu cortex-a9 -M vexpress-a9 -smp 2 -m 1024 -kernel
arch/arm/boot/zImage -dtb arch/arm/boot/dts/vexpress-v2p-ca9.dtb -serial
stdio -net nic,model=lan9118 -net user
Neil Armstrong Aug. 17, 2022, 1:31 p.m. UTC | #6
On 15/08/2022 02:15, Dmitry Osipenko wrote:
> 08.08.2022 12:51, Neil Armstrong пишет:
>> On 08/08/2022 11:15, Neil Armstrong wrote:
>>> Hi Dmitry,
>>>
>>> On 31/07/2022 22:07, Dmitry Osipenko wrote:
>>>> 13.01.2022 17:43, Neil Armstrong пишет:
>>>>> This adds support for DRM_BRIDGE_ATTACH_NO_CONNECTOR by adding the
>>>>> bridge get_edid() and detect() callbacks after refactoring the
>>>>> connector
>>>>> get_modes() and connector_detect() callbacks.
>>>>>
>>>>> In order to keep the bridge working, extra code in get_modes() has been
>>>>> moved to more logical places.
>>>>>
>>>>> Signed-off-by: Neil Armstrong <narmstrong@baylibre.com>
>>>>> ---
>>>>>    drivers/gpu/drm/bridge/sii902x.c | 129
>>>>> ++++++++++++++++++++++++-------
>>>
>>> 1 file changed, 99 insertions(+), 30 deletions(-)
>>>>>
>>>>> diff --git a/drivers/gpu/drm/bridge/sii902x.c
>>>>> b/drivers/gpu/drm/bridge/sii902x.c
>>>>> index 89558e581530..65549fbfdc87 100644
>>>>> --- a/drivers/gpu/drm/bridge/sii902x.c
>>>>> +++ b/drivers/gpu/drm/bridge/sii902x.c
>>
>> [...]
>>
>>>>>        }
>>>>> +    endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 1, -1);
>>>>> +    if (endpoint) {
>>>>> +        struct device_node *remote =
>>>>> of_graph_get_remote_port_parent(endpoint);
>>>>> +
>>>>> +        of_node_put(endpoint);
>>>>> +        if (!remote) {
>>>>> +            dev_err(dev, "Endpoint in port@1 unconnected\n");
>>>>> +            return -ENODEV;
>>>>> +        }
>>>>> +
>>>>> +        if (!of_device_is_available(remote)) {
>>>>> +            dev_err(dev, "port@1 remote device is disabled\n");
>>>>> +            of_node_put(remote);
>>>>> +            return -ENODEV;
>>>>> +        }
>>>>> +
>>>>> +        sii902x->next_bridge = of_drm_find_bridge(remote);
>>>>> +        of_node_put(remote);
>>>>> +        if (!sii902x->next_bridge)
>>>>> +            return -EPROBE_DEFER;
>>>>
>>>> Hi,
>>>>
>>>> This patch broke ARM/QEMU vexpress display because of_drm_find_bridge()
>>>> always fail with -EPROBE_DEFER. Reverting this patch returns display
>>>> back. Please fix or revert, thanks in advance.
>>>
>>> Can you share a QEMU cmdline to reproduce ?
>>
>> Actually the vexpress DT has multiple input ports instead of a single
>> input port at @0
>> and an output port at @1 like documented in the bindings:
>>
>> vexpress-v2m.dtsi#L303-L307:
>> ports {
>>      #address-cells = <1>;
>>      #size-cells = <0>;
>>
>>      /*
>>       * Both the core tile and the motherboard routes their output
>>       * pads to this transmitter. The motherboard system controller
>>       * can select one of them as input using a mux register in
>>       * "arm,vexpress-muxfpga". The Vexpress with the CA9 core tile is
>>       * the only platform with this specific set-up.
>>       */
>>      port@0 {
>>          reg = <0>;
>>          dvi_bridge_in_ct: endpoint {
>>              remote-endpoint = <&clcd_pads_ct>;
>>          };
>>      };
>>      port@1 {
>>          reg = <1>;
>>          dvi_bridge_in_mb: endpoint {
>>              remote-endpoint = <&clcd_pads_mb>;
>>          };
>>      };
>> };
>>
>> bindings:
>>    ports:
>>      $ref: /schemas/graph.yaml#/properties/ports
>>
>>      properties:
>>        port@0:
>>          $ref: /schemas/graph.yaml#/properties/port
>>          description: Parallel RGB input port
>>
>>        port@1:
>>          $ref: /schemas/graph.yaml#/properties/port
>>          description: HDMI output port
>>
>>        port@3:
>>          $ref: /schemas/graph.yaml#/properties/port
>>          description: Sound input port
>>
>> The patch is conform to the bindings, the DT was working but is actually
>> not valid.
> 
> I haven't looked closely at how to fix this properly, but if we can fix
> it using of_machine_is_compatible("arm,vexpress") workaround in the
> driver, then it will be good enough at least as a temporal fix, IMO.

If other maintainers are ok with that, it can be temporary fix until the DT gets fixed.

Neil
Linus Walleij Aug. 25, 2022, 12:48 p.m. UTC | #7
On Wed, Aug 17, 2022 at 3:31 PM Neil Armstrong <narmstrong@baylibre.com> wrote:
> On 15/08/2022 02:15, Dmitry Osipenko wrote:
> > 08.08.2022 12:51, Neil Armstrong пишет:
> >> On 08/08/2022 11:15, Neil Armstrong wrote:

> >>>> This patch broke ARM/QEMU vexpress display because of_drm_find_bridge()
> >>>> always fail with -EPROBE_DEFER. Reverting this patch returns display
> >>>> back. Please fix or revert, thanks in advance.
> >>>
> >>> Can you share a QEMU cmdline to reproduce ?
> >>
> >> Actually the vexpress DT has multiple input ports instead of a single
> >> input port at @0
> >> and an output port at @1 like documented in the bindings:
> >>
> >> vexpress-v2m.dtsi#L303-L307:
> >> ports {
> >>      #address-cells = <1>;
> >>      #size-cells = <0>;
> >>
> >>      /*
> >>       * Both the core tile and the motherboard routes their output
> >>       * pads to this transmitter. The motherboard system controller
> >>       * can select one of them as input using a mux register in
> >>       * "arm,vexpress-muxfpga". The Vexpress with the CA9 core tile is
> >>       * the only platform with this specific set-up.
> >>       */
> >>      port@0 {
> >>          reg = <0>;
> >>          dvi_bridge_in_ct: endpoint {
> >>              remote-endpoint = <&clcd_pads_ct>;
> >>          };
> >>      };
> >>      port@1 {
> >>          reg = <1>;
> >>          dvi_bridge_in_mb: endpoint {
> >>              remote-endpoint = <&clcd_pads_mb>;
> >>          };
> >>      };
> >> };
> >>
> >> bindings:
> >>    ports:
> >>      $ref: /schemas/graph.yaml#/properties/ports
> >>
> >>      properties:
> >>        port@0:
> >>          $ref: /schemas/graph.yaml#/properties/port
> >>          description: Parallel RGB input port
> >>
> >>        port@1:
> >>          $ref: /schemas/graph.yaml#/properties/port
> >>          description: HDMI output port
> >>
> >>        port@3:
> >>          $ref: /schemas/graph.yaml#/properties/port
> >>          description: Sound input port
> >>
> >> The patch is conform to the bindings, the DT was working but is actually
> >> not valid.
> >
> > I haven't looked closely at how to fix this properly, but if we can fix
> > it using of_machine_is_compatible("arm,vexpress") workaround in the
> > driver, then it will be good enough at least as a temporal fix, IMO.
>
> If other maintainers are ok with that, it can be temporary fix until the DT gets fixed.

That's fine with me, will you send a patch?

I don't know how you expect the DT to get "fixed" though.

The hardware looks like this, it's maybe not the most elegant
electronics design but it exists, so... I wrote this DT with two
inputs, see commit f1fe12c8bf332, the code handling this
awkward mux is part of the DRM driver, see
drivers/gpu/drm/pl111/pl111_versatile.c function
pl111_vexpress_clcd_init() for an idea of how it works.

Yours,
Linus Walleij
Neil Armstrong Aug. 29, 2022, 1:36 p.m. UTC | #8
On 25/08/2022 14:48, Linus Walleij wrote:
> On Wed, Aug 17, 2022 at 3:31 PM Neil Armstrong <narmstrong@baylibre.com> wrote:
>> On 15/08/2022 02:15, Dmitry Osipenko wrote:
>>> 08.08.2022 12:51, Neil Armstrong пишет:
>>>> On 08/08/2022 11:15, Neil Armstrong wrote:
> 
>>>>>> This patch broke ARM/QEMU vexpress display because of_drm_find_bridge()
>>>>>> always fail with -EPROBE_DEFER. Reverting this patch returns display
>>>>>> back. Please fix or revert, thanks in advance.
>>>>>
>>>>> Can you share a QEMU cmdline to reproduce ?
>>>>
>>>> Actually the vexpress DT has multiple input ports instead of a single
>>>> input port at @0
>>>> and an output port at @1 like documented in the bindings:
>>>>
>>>> vexpress-v2m.dtsi#L303-L307:
>>>> ports {
>>>>       #address-cells = <1>;
>>>>       #size-cells = <0>;
>>>>
>>>>       /*
>>>>        * Both the core tile and the motherboard routes their output
>>>>        * pads to this transmitter. The motherboard system controller
>>>>        * can select one of them as input using a mux register in
>>>>        * "arm,vexpress-muxfpga". The Vexpress with the CA9 core tile is
>>>>        * the only platform with this specific set-up.
>>>>        */
>>>>       port@0 {
>>>>           reg = <0>;
>>>>           dvi_bridge_in_ct: endpoint {
>>>>               remote-endpoint = <&clcd_pads_ct>;
>>>>           };
>>>>       };
>>>>       port@1 {
>>>>           reg = <1>;
>>>>           dvi_bridge_in_mb: endpoint {
>>>>               remote-endpoint = <&clcd_pads_mb>;
>>>>           };
>>>>       };
>>>> };
>>>>
>>>> bindings:
>>>>     ports:
>>>>       $ref: /schemas/graph.yaml#/properties/ports
>>>>
>>>>       properties:
>>>>         port@0:
>>>>           $ref: /schemas/graph.yaml#/properties/port
>>>>           description: Parallel RGB input port
>>>>
>>>>         port@1:
>>>>           $ref: /schemas/graph.yaml#/properties/port
>>>>           description: HDMI output port
>>>>
>>>>         port@3:
>>>>           $ref: /schemas/graph.yaml#/properties/port
>>>>           description: Sound input port
>>>>
>>>> The patch is conform to the bindings, the DT was working but is actually
>>>> not valid.
>>>
>>> I haven't looked closely at how to fix this properly, but if we can fix
>>> it using of_machine_is_compatible("arm,vexpress") workaround in the
>>> driver, then it will be good enough at least as a temporal fix, IMO.
>>
>> If other maintainers are ok with that, it can be temporary fix until the DT gets fixed.
> 
> That's fine with me, will you send a patch?

Who, me ?

> 
> I don't know how you expect the DT to get "fixed" though.
> 
> The hardware looks like this, it's maybe not the most elegant
> electronics design but it exists, so... I wrote this DT with two
> inputs, see commit f1fe12c8bf332, the code handling this
> awkward mux is part of the DRM driver, see
> drivers/gpu/drm/pl111/pl111_versatile.c function
> pl111_vexpress_clcd_init() for an idea of how it works.

The proper fix would be the other way around, adding a mux bridge before the sii902x
returning the next bridge or nothing to the right controller.

> 
> Yours,
> Linus Walleij

Neil
Linus Walleij Aug. 31, 2022, 12:34 p.m. UTC | #9
On Mon, Aug 29, 2022 at 3:36 PM Neil Armstrong <narmstrong@baylibre.com> wrote:
> On 25/08/2022 14:48, Linus Walleij wrote:
> > On Wed, Aug 17, 2022 at 3:31 PM Neil Armstrong <narmstrong@baylibre.com> wrote:
> >> On 15/08/2022 02:15, Dmitry Osipenko wrote:
> >>> 08.08.2022 12:51, Neil Armstrong пишет:
> >>>> On 08/08/2022 11:15, Neil Armstrong wrote:
> >
> >>>>>> This patch broke ARM/QEMU vexpress display because of_drm_find_bridge()
> >>>>>> always fail with -EPROBE_DEFER. Reverting this patch returns display
> >>>>>> back. Please fix or revert, thanks in advance.
> >>>>>
> >>>>> Can you share a QEMU cmdline to reproduce ?
> >>>>
> >>>> Actually the vexpress DT has multiple input ports instead of a single
> >>>> input port at @0
> >>>> and an output port at @1 like documented in the bindings:
> >>>>
> >>>> vexpress-v2m.dtsi#L303-L307:
> >>>> ports {
> >>>>       #address-cells = <1>;
> >>>>       #size-cells = <0>;
> >>>>
> >>>>       /*
> >>>>        * Both the core tile and the motherboard routes their output
> >>>>        * pads to this transmitter. The motherboard system controller
> >>>>        * can select one of them as input using a mux register in
> >>>>        * "arm,vexpress-muxfpga". The Vexpress with the CA9 core tile is
> >>>>        * the only platform with this specific set-up.
> >>>>        */
> >>>>       port@0 {
> >>>>           reg = <0>;
> >>>>           dvi_bridge_in_ct: endpoint {
> >>>>               remote-endpoint = <&clcd_pads_ct>;
> >>>>           };
> >>>>       };
> >>>>       port@1 {
> >>>>           reg = <1>;
> >>>>           dvi_bridge_in_mb: endpoint {
> >>>>               remote-endpoint = <&clcd_pads_mb>;
> >>>>           };
> >>>>       };
> >>>> };
> >>>>
> >>>> bindings:
> >>>>     ports:
> >>>>       $ref: /schemas/graph.yaml#/properties/ports
> >>>>
> >>>>       properties:
> >>>>         port@0:
> >>>>           $ref: /schemas/graph.yaml#/properties/port
> >>>>           description: Parallel RGB input port
> >>>>
> >>>>         port@1:
> >>>>           $ref: /schemas/graph.yaml#/properties/port
> >>>>           description: HDMI output port
> >>>>
> >>>>         port@3:
> >>>>           $ref: /schemas/graph.yaml#/properties/port
> >>>>           description: Sound input port
> >>>>
> >>>> The patch is conform to the bindings, the DT was working but is actually
> >>>> not valid.
> >>>
> >>> I haven't looked closely at how to fix this properly, but if we can fix
> >>> it using of_machine_is_compatible("arm,vexpress") workaround in the
> >>> driver, then it will be good enough at least as a temporal fix, IMO.
> >>
> >> If other maintainers are ok with that, it can be temporary fix until the DT gets fixed.
> >
> > That's fine with me, will you send a patch?
>
> Who, me ?

Whoever you were referring to with the "temporary fix" :D

> > I don't know how you expect the DT to get "fixed" though.
> >
> > The hardware looks like this, it's maybe not the most elegant
> > electronics design but it exists, so... I wrote this DT with two
> > inputs, see commit f1fe12c8bf332, the code handling this
> > awkward mux is part of the DRM driver, see
> > drivers/gpu/drm/pl111/pl111_versatile.c function
> > pl111_vexpress_clcd_init() for an idea of how it works.
>
> The proper fix would be the other way around, adding a mux bridge before the sii902x
> returning the next bridge or nothing to the right controller.

Hm you mean the vexpress PLD should be a bridge and not just
some magic registers poked by the DRM driver?

OK fair point. But it will need proper representing in the DT then,
I guess that is what you mean by "DT gets fixed". It's not just
the DT that needs fixing then but also the driver(s).

Yours,
Linus Walleij
diff mbox series

Patch

diff --git a/drivers/gpu/drm/bridge/sii902x.c b/drivers/gpu/drm/bridge/sii902x.c
index 89558e581530..65549fbfdc87 100644
--- a/drivers/gpu/drm/bridge/sii902x.c
+++ b/drivers/gpu/drm/bridge/sii902x.c
@@ -166,10 +166,12 @@  struct sii902x {
 	struct i2c_client *i2c;
 	struct regmap *regmap;
 	struct drm_bridge bridge;
+	struct drm_bridge *next_bridge;
 	struct drm_connector connector;
 	struct gpio_desc *reset_gpio;
 	struct i2c_mux_core *i2cmux;
 	struct regulator_bulk_data supplies[2];
+	bool sink_is_hdmi;
 	/*
 	 * Mutex protects audio and video functions from interfering
 	 * each other, by keeping their i2c command sequences atomic.
@@ -245,10 +247,8 @@  static void sii902x_reset(struct sii902x *sii902x)
 	gpiod_set_value(sii902x->reset_gpio, 0);
 }
 
-static enum drm_connector_status
-sii902x_connector_detect(struct drm_connector *connector, bool force)
+static enum drm_connector_status sii902x_detect(struct sii902x *sii902x)
 {
-	struct sii902x *sii902x = connector_to_sii902x(connector);
 	unsigned int status;
 
 	mutex_lock(&sii902x->mutex);
@@ -261,6 +261,14 @@  sii902x_connector_detect(struct drm_connector *connector, bool force)
 	       connector_status_connected : connector_status_disconnected;
 }
 
+static enum drm_connector_status
+sii902x_connector_detect(struct drm_connector *connector, bool force)
+{
+	struct sii902x *sii902x = connector_to_sii902x(connector);
+
+	return sii902x_detect(sii902x);
+}
+
 static const struct drm_connector_funcs sii902x_connector_funcs = {
 	.detect = sii902x_connector_detect,
 	.fill_modes = drm_helper_probe_single_connector_modes,
@@ -270,42 +278,40 @@  static const struct drm_connector_funcs sii902x_connector_funcs = {
 	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
 };
 
-static int sii902x_get_modes(struct drm_connector *connector)
+static struct edid *sii902x_get_edid(struct sii902x *sii902x,
+				     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);
+			sii902x->sink_is_hdmi = true;
+		else
+			sii902x->sink_is_hdmi = false;
 	}
 
-	ret = drm_display_info_set_bus_formats(&connector->display_info,
-					       &bus_format, 1);
-	if (ret)
-		goto error_out;
+	mutex_unlock(&sii902x->mutex);
 
-	ret = regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA,
-				 SII902X_SYS_CTRL_OUTPUT_MODE, output_mode);
-	if (ret)
-		goto error_out;
+	return edid;
+}
 
-	ret = num;
+static int sii902x_get_modes(struct drm_connector *connector)
+{
+	struct sii902x *sii902x = connector_to_sii902x(connector);
+	struct edid *edid;
+	int num = 0;
 
-error_out:
-	mutex_unlock(&sii902x->mutex);
+	edid = sii902x_get_edid(sii902x, connector);
+	drm_connector_update_edid_property(connector, edid);
+	if (edid) {
+		num = drm_add_edid_modes(connector, edid);
+		kfree(edid);
+	}
 
-	return ret;
+	return num;
 }
 
 static enum drm_mode_status sii902x_mode_valid(struct drm_connector *connector,
@@ -354,12 +360,16 @@  static void sii902x_bridge_mode_set(struct drm_bridge *bridge,
 				    const struct drm_display_mode *adj)
 {
 	struct sii902x *sii902x = bridge_to_sii902x(bridge);
+	u8 output_mode = SII902X_SYS_CTRL_OUTPUT_DVI;
 	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;
 
+	if (sii902x->sink_is_hdmi)
+		output_mode = SII902X_SYS_CTRL_OUTPUT_HDMI;
+
 	buf[0] = pixel_clock_10kHz & 0xff;
 	buf[1] = pixel_clock_10kHz >> 8;
 	buf[2] = drm_mode_vrefresh(adj);
@@ -375,6 +385,11 @@  static void sii902x_bridge_mode_set(struct drm_bridge *bridge,
 
 	mutex_lock(&sii902x->mutex);
 
+	ret = regmap_update_bits(sii902x->regmap, SII902X_SYS_CTRL_DATA,
+				 SII902X_SYS_CTRL_OUTPUT_MODE, output_mode);
+	if (ret)
+		goto out;
+
 	ret = regmap_bulk_write(regmap, SII902X_TPI_VIDEO_DATA, buf, 10);
 	if (ret)
 		goto out;
@@ -405,13 +420,13 @@  static int sii902x_bridge_attach(struct drm_bridge *bridge,
 				 enum drm_bridge_attach_flags flags)
 {
 	struct sii902x *sii902x = bridge_to_sii902x(bridge);
+	u32 bus_format = MEDIA_BUS_FMT_RGB888_1X24;
 	struct drm_device *drm = bridge->dev;
 	int ret;
 
-	if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
-		DRM_ERROR("Fix bridge driver to make connector optional!");
-		return -EINVAL;
-	}
+	if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)
+		return drm_bridge_attach(bridge->encoder, sii902x->next_bridge,
+					 bridge, flags);
 
 	drm_connector_helper_add(&sii902x->connector,
 				 &sii902x_connector_helper_funcs);
@@ -433,16 +448,38 @@  static int sii902x_bridge_attach(struct drm_bridge *bridge,
 	else
 		sii902x->connector.polled = DRM_CONNECTOR_POLL_CONNECT;
 
+	ret = drm_display_info_set_bus_formats(&sii902x->connector.display_info,
+					       &bus_format, 1);
+	if (ret)
+		return ret;
+
 	drm_connector_attach_encoder(&sii902x->connector, bridge->encoder);
 
 	return 0;
 }
 
+static enum drm_connector_status sii902x_bridge_detect(struct drm_bridge *bridge)
+{
+	struct sii902x *sii902x = bridge_to_sii902x(bridge);
+
+	return sii902x_detect(sii902x);
+}
+
+static struct edid *sii902x_bridge_get_edid(struct drm_bridge *bridge,
+					    struct drm_connector *connector)
+{
+	struct sii902x *sii902x = bridge_to_sii902x(bridge);
+
+	return sii902x_get_edid(sii902x, connector);
+}
+
 static const struct drm_bridge_funcs sii902x_bridge_funcs = {
 	.attach = sii902x_bridge_attach,
 	.mode_set = sii902x_bridge_mode_set,
 	.disable = sii902x_bridge_disable,
 	.enable = sii902x_bridge_enable,
+	.detect = sii902x_bridge_detect,
+	.get_edid = sii902x_bridge_get_edid,
 };
 
 static int sii902x_mute(struct sii902x *sii902x, bool mute)
@@ -829,8 +866,12 @@  static irqreturn_t sii902x_interrupt(int irq, void *data)
 
 	mutex_unlock(&sii902x->mutex);
 
-	if ((status & SII902X_HOTPLUG_EVENT) && sii902x->bridge.dev)
+	if ((status & SII902X_HOTPLUG_EVENT) && sii902x->bridge.dev) {
 		drm_helper_hpd_irq_event(sii902x->bridge.dev);
+		drm_bridge_hpd_notify(&sii902x->bridge, (status & SII902X_PLUGGED_STATUS)
+								? connector_status_connected
+								: connector_status_disconnected);
+	}
 
 	return IRQ_HANDLED;
 }
@@ -1001,6 +1042,11 @@  static int sii902x_init(struct sii902x *sii902x)
 	sii902x->bridge.funcs = &sii902x_bridge_funcs;
 	sii902x->bridge.of_node = dev->of_node;
 	sii902x->bridge.timings = &default_sii902x_timings;
+	sii902x->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID;
+
+	if (sii902x->i2c->irq > 0)
+		sii902x->bridge.ops |= DRM_BRIDGE_OP_HPD;
+
 	drm_bridge_add(&sii902x->bridge);
 
 	sii902x_audio_codec_init(sii902x, dev);
@@ -1022,6 +1068,7 @@  static int sii902x_probe(struct i2c_client *client,
 			 const struct i2c_device_id *id)
 {
 	struct device *dev = &client->dev;
+	struct device_node *endpoint;
 	struct sii902x *sii902x;
 	int ret;
 
@@ -1049,6 +1096,28 @@  static int sii902x_probe(struct i2c_client *client,
 		return PTR_ERR(sii902x->reset_gpio);
 	}
 
+	endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 1, -1);
+	if (endpoint) {
+		struct device_node *remote = of_graph_get_remote_port_parent(endpoint);
+
+		of_node_put(endpoint);
+		if (!remote) {
+			dev_err(dev, "Endpoint in port@1 unconnected\n");
+			return -ENODEV;
+		}
+
+		if (!of_device_is_available(remote)) {
+			dev_err(dev, "port@1 remote device is disabled\n");
+			of_node_put(remote);
+			return -ENODEV;
+		}
+
+		sii902x->next_bridge = of_drm_find_bridge(remote);
+		of_node_put(remote);
+		if (!sii902x->next_bridge)
+			return -EPROBE_DEFER;
+	}
+
 	mutex_init(&sii902x->mutex);
 
 	sii902x->supplies[0].supply = "iovcc";