diff options
Diffstat (limited to 'drivers/gpu/drm/bridge/ti-tfp410.c')
| -rw-r--r-- | drivers/gpu/drm/bridge/ti-tfp410.c | 238 | 
1 files changed, 95 insertions, 143 deletions
| diff --git a/drivers/gpu/drm/bridge/ti-tfp410.c b/drivers/gpu/drm/bridge/ti-tfp410.c index 6f6d6d1e60ae..e3eb6364c0f7 100644 --- a/drivers/gpu/drm/bridge/ti-tfp410.c +++ b/drivers/gpu/drm/bridge/ti-tfp410.c @@ -4,14 +4,12 @@   * Author: Jyri Sarha <[email protected]>   */ -#include <linux/delay.h> -#include <linux/fwnode.h>  #include <linux/gpio/consumer.h>  #include <linux/i2c.h> -#include <linux/irq.h>  #include <linux/module.h>  #include <linux/of_graph.h>  #include <linux/platform_device.h> +#include <linux/workqueue.h>  #include <drm/drm_atomic_helper.h>  #include <drm/drm_bridge.h> @@ -24,16 +22,13 @@  struct tfp410 {  	struct drm_bridge	bridge;  	struct drm_connector	connector; -	unsigned int		connector_type;  	u32			bus_format; -	struct i2c_adapter	*ddc; -	struct gpio_desc	*hpd; -	int			hpd_irq;  	struct delayed_work	hpd_work;  	struct gpio_desc	*powerdown;  	struct drm_bridge_timings timings; +	struct drm_bridge	*next_bridge;  	struct device *dev;  }; @@ -56,13 +51,18 @@ static int tfp410_get_modes(struct drm_connector *connector)  	struct edid *edid;  	int ret; -	if (!dvi->ddc) -		goto fallback; +	edid = drm_bridge_get_edid(dvi->next_bridge, connector); +	if (IS_ERR_OR_NULL(edid)) { +		if (edid != ERR_PTR(-ENOTSUPP)) +			DRM_INFO("EDID read failed. Fallback to standard modes\n"); -	edid = drm_get_edid(connector, dvi->ddc); -	if (!edid) { -		DRM_INFO("EDID read failed. Fallback to standard modes\n"); -		goto fallback; +		/* +		 * No EDID, fallback on the XGA standard modes and prefer a mode +		 * pretty much anything can handle. +		 */ +		ret = drm_add_modes_noedid(connector, 1920, 1200); +		drm_set_preferred_mode(connector, 1024, 768); +		return ret;  	}  	drm_connector_update_edid_property(connector, edid); @@ -72,15 +72,6 @@ static int tfp410_get_modes(struct drm_connector *connector)  	kfree(edid);  	return ret; - -fallback: -	/* No EDID, fallback on the XGA standard modes */ -	ret = drm_add_modes_noedid(connector, 1920, 1200); - -	/* And prefer a mode pretty much anything can handle */ -	drm_set_preferred_mode(connector, 1024, 768); - -	return ret;  }  static const struct drm_connector_helper_funcs tfp410_con_helper_funcs = { @@ -92,21 +83,7 @@ tfp410_connector_detect(struct drm_connector *connector, bool force)  {  	struct tfp410 *dvi = drm_connector_to_tfp410(connector); -	if (dvi->hpd) { -		if (gpiod_get_value_cansleep(dvi->hpd)) -			return connector_status_connected; -		else -			return connector_status_disconnected; -	} - -	if (dvi->ddc) { -		if (drm_probe_ddc(dvi->ddc)) -			return connector_status_connected; -		else -			return connector_status_disconnected; -	} - -	return connector_status_unknown; +	return drm_bridge_detect(dvi->next_bridge);  }  static const struct drm_connector_funcs tfp410_con_funcs = { @@ -118,41 +95,84 @@ static const struct drm_connector_funcs tfp410_con_funcs = {  	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,  }; -static int tfp410_attach(struct drm_bridge *bridge) +static void tfp410_hpd_work_func(struct work_struct *work) +{ +	struct tfp410 *dvi; + +	dvi = container_of(work, struct tfp410, hpd_work.work); + +	if (dvi->bridge.dev) +		drm_helper_hpd_irq_event(dvi->bridge.dev); +} + +static void tfp410_hpd_callback(void *arg, enum drm_connector_status status) +{ +	struct tfp410 *dvi = arg; + +	mod_delayed_work(system_wq, &dvi->hpd_work, +			 msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS)); +} + +static int tfp410_attach(struct drm_bridge *bridge, +			 enum drm_bridge_attach_flags flags)  {  	struct tfp410 *dvi = drm_bridge_to_tfp410(bridge);  	int ret; +	ret = drm_bridge_attach(bridge->encoder, dvi->next_bridge, bridge, +				DRM_BRIDGE_ATTACH_NO_CONNECTOR); +	if (ret < 0) +		return ret; + +	if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) +		return 0; +  	if (!bridge->encoder) {  		dev_err(dvi->dev, "Missing encoder\n");  		return -ENODEV;  	} -	if (dvi->hpd_irq >= 0) +	if (dvi->next_bridge->ops & DRM_BRIDGE_OP_DETECT)  		dvi->connector.polled = DRM_CONNECTOR_POLL_HPD;  	else  		dvi->connector.polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT; +	if (dvi->next_bridge->ops & DRM_BRIDGE_OP_HPD) { +		INIT_DELAYED_WORK(&dvi->hpd_work, tfp410_hpd_work_func); +		drm_bridge_hpd_enable(dvi->next_bridge, tfp410_hpd_callback, +				      dvi); +	} +  	drm_connector_helper_add(&dvi->connector,  				 &tfp410_con_helper_funcs);  	ret = drm_connector_init_with_ddc(bridge->dev, &dvi->connector,  					  &tfp410_con_funcs, -					  dvi->connector_type, -					  dvi->ddc); +					  dvi->next_bridge->type, +					  dvi->next_bridge->ddc);  	if (ret) { -		dev_err(dvi->dev, "drm_connector_init() failed: %d\n", ret); +		dev_err(dvi->dev, "drm_connector_init_with_ddc() failed: %d\n", +			ret);  		return ret;  	}  	drm_display_info_set_bus_formats(&dvi->connector.display_info,  					 &dvi->bus_format, 1); -	drm_connector_attach_encoder(&dvi->connector, -					  bridge->encoder); +	drm_connector_attach_encoder(&dvi->connector, bridge->encoder);  	return 0;  } +static void tfp410_detach(struct drm_bridge *bridge) +{ +	struct tfp410 *dvi = drm_bridge_to_tfp410(bridge); + +	if (dvi->connector.dev && dvi->next_bridge->ops & DRM_BRIDGE_OP_HPD) { +		drm_bridge_hpd_disable(dvi->next_bridge); +		cancel_delayed_work_sync(&dvi->hpd_work); +	} +} +  static void tfp410_enable(struct drm_bridge *bridge)  {  	struct tfp410 *dvi = drm_bridge_to_tfp410(bridge); @@ -167,31 +187,25 @@ static void tfp410_disable(struct drm_bridge *bridge)  	gpiod_set_value_cansleep(dvi->powerdown, 1);  } -static const struct drm_bridge_funcs tfp410_bridge_funcs = { -	.attach		= tfp410_attach, -	.enable		= tfp410_enable, -	.disable	= tfp410_disable, -}; - -static void tfp410_hpd_work_func(struct work_struct *work) +static enum drm_mode_status tfp410_mode_valid(struct drm_bridge *bridge, +					      const struct drm_display_mode *mode)  { -	struct tfp410 *dvi; +	if (mode->clock < 25000) +		return MODE_CLOCK_LOW; -	dvi = container_of(work, struct tfp410, hpd_work.work); +	if (mode->clock > 165000) +		return MODE_CLOCK_HIGH; -	if (dvi->bridge.dev) -		drm_helper_hpd_irq_event(dvi->bridge.dev); +	return MODE_OK;  } -static irqreturn_t tfp410_hpd_irq_thread(int irq, void *arg) -{ -	struct tfp410 *dvi = arg; - -	mod_delayed_work(system_wq, &dvi->hpd_work, -			msecs_to_jiffies(HOTPLUG_DEBOUNCE_MS)); - -	return IRQ_HANDLED; -} +static const struct drm_bridge_funcs tfp410_bridge_funcs = { +	.attach		= tfp410_attach, +	.detach		= tfp410_detach, +	.enable		= tfp410_enable, +	.disable	= tfp410_disable, +	.mode_valid	= tfp410_mode_valid, +};  static const struct drm_bridge_timings tfp410_default_timings = {  	.input_bus_flags = DRM_BUS_FLAG_PIXDATA_SAMPLE_POSEDGE @@ -270,51 +284,9 @@ static int tfp410_parse_timings(struct tfp410 *dvi, bool i2c)  	return 0;  } -static int tfp410_get_connector_properties(struct tfp410 *dvi) -{ -	struct device_node *connector_node, *ddc_phandle; -	int ret = 0; - -	/* port@1 is the connector node */ -	connector_node = of_graph_get_remote_node(dvi->dev->of_node, 1, -1); -	if (!connector_node) -		return -ENODEV; - -	if (of_device_is_compatible(connector_node, "hdmi-connector")) -		dvi->connector_type = DRM_MODE_CONNECTOR_HDMIA; -	else -		dvi->connector_type = DRM_MODE_CONNECTOR_DVID; - -	dvi->hpd = fwnode_gpiod_get_index(&connector_node->fwnode, -					  "hpd", 0, GPIOD_IN, "hpd"); -	if (IS_ERR(dvi->hpd)) { -		ret = PTR_ERR(dvi->hpd); -		dvi->hpd = NULL; -		if (ret == -ENOENT) -			ret = 0; -		else -			goto fail; -	} - -	ddc_phandle = of_parse_phandle(connector_node, "ddc-i2c-bus", 0); -	if (!ddc_phandle) -		goto fail; - -	dvi->ddc = of_get_i2c_adapter_by_node(ddc_phandle); -	if (dvi->ddc) -		dev_info(dvi->dev, "Connector's ddc i2c bus found\n"); -	else -		ret = -EPROBE_DEFER; - -	of_node_put(ddc_phandle); - -fail: -	of_node_put(connector_node); -	return ret; -} -  static int tfp410_init(struct device *dev, bool i2c)  { +	struct device_node *node;  	struct tfp410 *dvi;  	int ret; @@ -326,21 +298,31 @@ static int tfp410_init(struct device *dev, bool i2c)  	dvi = devm_kzalloc(dev, sizeof(*dvi), GFP_KERNEL);  	if (!dvi)  		return -ENOMEM; + +	dvi->dev = dev;  	dev_set_drvdata(dev, dvi);  	dvi->bridge.funcs = &tfp410_bridge_funcs;  	dvi->bridge.of_node = dev->of_node;  	dvi->bridge.timings = &dvi->timings; -	dvi->dev = dev; +	dvi->bridge.type = DRM_MODE_CONNECTOR_DVID;  	ret = tfp410_parse_timings(dvi, i2c);  	if (ret) -		goto fail; +		return ret; -	ret = tfp410_get_connector_properties(dvi); -	if (ret) -		goto fail; +	/* Get the next bridge, connected to port@1. */ +	node = of_graph_get_remote_node(dev->of_node, 1, -1); +	if (!node) +		return -ENODEV; + +	dvi->next_bridge = of_drm_find_bridge(node); +	of_node_put(node); +	if (!dvi->next_bridge) +		return -EPROBE_DEFER; + +	/* Get the powerdown GPIO. */  	dvi->powerdown = devm_gpiod_get_optional(dev, "powerdown",  						 GPIOD_OUT_HIGH);  	if (IS_ERR(dvi->powerdown)) { @@ -348,48 +330,18 @@ static int tfp410_init(struct device *dev, bool i2c)  		return PTR_ERR(dvi->powerdown);  	} -	if (dvi->hpd) -		dvi->hpd_irq = gpiod_to_irq(dvi->hpd); -	else -		dvi->hpd_irq = -ENXIO; - -	if (dvi->hpd_irq >= 0) { -		INIT_DELAYED_WORK(&dvi->hpd_work, tfp410_hpd_work_func); - -		ret = devm_request_threaded_irq(dev, dvi->hpd_irq, -			NULL, tfp410_hpd_irq_thread, IRQF_TRIGGER_RISING | -			IRQF_TRIGGER_FALLING | IRQF_ONESHOT, -			"hdmi-hpd", dvi); -		if (ret) { -			DRM_ERROR("failed to register hpd interrupt\n"); -			goto fail; -		} -	} - +	/*  Register the DRM bridge. */  	drm_bridge_add(&dvi->bridge);  	return 0; -fail: -	i2c_put_adapter(dvi->ddc); -	if (dvi->hpd) -		gpiod_put(dvi->hpd); -	return ret;  }  static int tfp410_fini(struct device *dev)  {  	struct tfp410 *dvi = dev_get_drvdata(dev); -	if (dvi->hpd_irq >= 0) -		cancel_delayed_work_sync(&dvi->hpd_work); -  	drm_bridge_remove(&dvi->bridge); -	if (dvi->ddc) -		i2c_put_adapter(dvi->ddc); -	if (dvi->hpd) -		gpiod_put(dvi->hpd); -  	return 0;  } |