summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/gpu/drm/drm_connector.c79
-rw-r--r--drivers/gpu/drm/drm_crtc_internal.h2
-rw-r--r--drivers/gpu/drm/drm_sysfs.c87
-rw-r--r--drivers/usb/typec/altmodes/Kconfig1
-rw-r--r--drivers/usb/typec/altmodes/displayport.c58
-rw-r--r--include/drm/drm_connector.h25
6 files changed, 217 insertions, 35 deletions
diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c
index 2ba257b1ae20..e0a30e0ee86a 100644
--- a/drivers/gpu/drm/drm_connector.c
+++ b/drivers/gpu/drm/drm_connector.c
@@ -65,6 +65,14 @@
* support can instead use e.g. drm_helper_hpd_irq_event().
*/
+/*
+ * Global connector list for drm_connector_find_by_fwnode().
+ * Note drm_connector_[un]register() first take connector->lock and then
+ * take the connector_list_lock.
+ */
+static DEFINE_MUTEX(connector_list_lock);
+static LIST_HEAD(connector_list);
+
struct drm_conn_prop_enum_list {
int type;
const char *name;
@@ -267,6 +275,7 @@ int drm_connector_init(struct drm_device *dev,
goto out_put_type_id;
}
+ INIT_LIST_HEAD(&connector->global_connector_list_entry);
INIT_LIST_HEAD(&connector->probed_modes);
INIT_LIST_HEAD(&connector->modes);
mutex_init(&connector->mutex);
@@ -474,6 +483,8 @@ void drm_connector_cleanup(struct drm_connector *connector)
drm_mode_object_unregister(dev, &connector->base);
kfree(connector->name);
connector->name = NULL;
+ fwnode_handle_put(connector->fwnode);
+ connector->fwnode = NULL;
spin_lock_irq(&dev->mode_config.connector_list_lock);
list_del(&connector->head);
dev->mode_config.num_connector--;
@@ -532,6 +543,9 @@ int drm_connector_register(struct drm_connector *connector)
/* Let userspace know we have a new connector */
drm_sysfs_hotplug_event(connector->dev);
+ mutex_lock(&connector_list_lock);
+ list_add_tail(&connector->global_connector_list_entry, &connector_list);
+ mutex_unlock(&connector_list_lock);
goto unlock;
err_debugfs:
@@ -560,6 +574,10 @@ void drm_connector_unregister(struct drm_connector *connector)
return;
}
+ mutex_lock(&connector_list_lock);
+ list_del_init(&connector->global_connector_list_entry);
+ mutex_unlock(&connector_list_lock);
+
if (connector->funcs->early_unregister)
connector->funcs->early_unregister(connector);
@@ -2543,6 +2561,67 @@ out:
return ret;
}
+/**
+ * drm_connector_find_by_fwnode - Find a connector based on the associated fwnode
+ * @fwnode: fwnode for which to find the matching drm_connector
+ *
+ * This functions looks up a drm_connector based on its associated fwnode. When
+ * a connector is found a reference to the connector is returned. The caller must
+ * call drm_connector_put() to release this reference when it is done with the
+ * connector.
+ *
+ * Returns: A reference to the found connector or an ERR_PTR().
+ */
+struct drm_connector *drm_connector_find_by_fwnode(struct fwnode_handle *fwnode)
+{
+ struct drm_connector *connector, *found = ERR_PTR(-ENODEV);
+
+ if (!fwnode)
+ return ERR_PTR(-ENODEV);
+
+ mutex_lock(&connector_list_lock);
+
+ list_for_each_entry(connector, &connector_list, global_connector_list_entry) {
+ if (connector->fwnode == fwnode ||
+ (connector->fwnode && connector->fwnode->secondary == fwnode)) {
+ drm_connector_get(connector);
+ found = connector;
+ break;
+ }
+ }
+
+ mutex_unlock(&connector_list_lock);
+
+ return found;
+}
+
+/**
+ * drm_connector_oob_hotplug_event - Report out-of-band hotplug event to connector
+ * @connector: connector to report the event on
+ *
+ * On some hardware a hotplug event notification may come from outside the display
+ * driver / device. An example of this is some USB Type-C setups where the hardware
+ * muxes the DisplayPort data and aux-lines but does not pass the altmode HPD
+ * status bit to the GPU's DP HPD pin.
+ *
+ * This function can be used to report these out-of-band events after obtaining
+ * a drm_connector reference through calling drm_connector_find_by_fwnode().
+ */
+void drm_connector_oob_hotplug_event(struct fwnode_handle *connector_fwnode)
+{
+ struct drm_connector *connector;
+
+ connector = drm_connector_find_by_fwnode(connector_fwnode);
+ if (IS_ERR(connector))
+ return;
+
+ if (connector->funcs->oob_hotplug_event)
+ connector->funcs->oob_hotplug_event(connector);
+
+ drm_connector_put(connector);
+}
+EXPORT_SYMBOL(drm_connector_oob_hotplug_event);
+
/**
* DOC: Tile group
diff --git a/drivers/gpu/drm/drm_crtc_internal.h b/drivers/gpu/drm/drm_crtc_internal.h
index edb772947cb4..63279e984342 100644
--- a/drivers/gpu/drm/drm_crtc_internal.h
+++ b/drivers/gpu/drm/drm_crtc_internal.h
@@ -58,6 +58,7 @@ struct drm_property;
struct edid;
struct kref;
struct work_struct;
+struct fwnode_handle;
/* drm_crtc.c */
int drm_mode_crtc_set_obj_prop(struct drm_mode_object *obj,
@@ -186,6 +187,7 @@ int drm_connector_set_obj_prop(struct drm_mode_object *obj,
int drm_connector_create_standard_properties(struct drm_device *dev);
const char *drm_get_connector_force_name(enum drm_connector_force force);
void drm_connector_free_work_fn(struct work_struct *work);
+struct drm_connector *drm_connector_find_by_fwnode(struct fwnode_handle *fwnode);
/* IOCTL */
int drm_connector_property_set_ioctl(struct drm_device *dev,
diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c
index 968a9560b4aa..76ff6ec3421b 100644
--- a/drivers/gpu/drm/drm_sysfs.c
+++ b/drivers/gpu/drm/drm_sysfs.c
@@ -10,6 +10,7 @@
* Copyright (c) 2003-2004 IBM Corp.
*/
+#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/export.h>
@@ -50,8 +51,45 @@ static struct device_type drm_sysfs_device_minor = {
.name = "drm_minor"
};
+static struct device_type drm_sysfs_device_connector = {
+ .name = "drm_connector",
+};
+
struct class *drm_class;
+#ifdef CONFIG_ACPI
+static bool drm_connector_acpi_bus_match(struct device *dev)
+{
+ return dev->type == &drm_sysfs_device_connector;
+}
+
+static struct acpi_device *drm_connector_acpi_find_companion(struct device *dev)
+{
+ struct drm_connector *connector = to_drm_connector(dev);
+
+ return to_acpi_device_node(connector->fwnode);
+}
+
+static struct acpi_bus_type drm_connector_acpi_bus = {
+ .name = "drm_connector",
+ .match = drm_connector_acpi_bus_match,
+ .find_companion = drm_connector_acpi_find_companion,
+};
+
+static void drm_sysfs_acpi_register(void)
+{
+ register_acpi_bus_type(&drm_connector_acpi_bus);
+}
+
+static void drm_sysfs_acpi_unregister(void)
+{
+ unregister_acpi_bus_type(&drm_connector_acpi_bus);
+}
+#else
+static void drm_sysfs_acpi_register(void) { }
+static void drm_sysfs_acpi_unregister(void) { }
+#endif
+
static char *drm_devnode(struct device *dev, umode_t *mode)
{
return kasprintf(GFP_KERNEL, "dri/%s", dev_name(dev));
@@ -85,6 +123,8 @@ int drm_sysfs_init(void)
}
drm_class->devnode = drm_devnode;
+
+ drm_sysfs_acpi_register();
return 0;
}
@@ -97,11 +137,17 @@ void drm_sysfs_destroy(void)
{
if (IS_ERR_OR_NULL(drm_class))
return;
+ drm_sysfs_acpi_unregister();
class_remove_file(drm_class, &class_attr_version.attr);
class_destroy(drm_class);
drm_class = NULL;
}
+static void drm_sysfs_release(struct device *dev)
+{
+ kfree(dev);
+}
+
/*
* Connector properties
*/
@@ -273,27 +319,47 @@ static const struct attribute_group *connector_dev_groups[] = {
int drm_sysfs_connector_add(struct drm_connector *connector)
{
struct drm_device *dev = connector->dev;
+ struct device *kdev;
+ int r;
if (connector->kdev)
return 0;
- connector->kdev =
- device_create_with_groups(drm_class, dev->primary->kdev, 0,
- connector, connector_dev_groups,
- "card%d-%s", dev->primary->index,
- connector->name);
+ kdev = kzalloc(sizeof(*kdev), GFP_KERNEL);
+ if (!kdev)
+ return -ENOMEM;
+
+ device_initialize(kdev);
+ kdev->class = drm_class;
+ kdev->type = &drm_sysfs_device_connector;
+ kdev->parent = dev->primary->kdev;
+ kdev->groups = connector_dev_groups;
+ kdev->release = drm_sysfs_release;
+ dev_set_drvdata(kdev, connector);
+
+ r = dev_set_name(kdev, "card%d-%s", dev->primary->index, connector->name);
+ if (r)
+ goto err_free;
+
DRM_DEBUG("adding \"%s\" to sysfs\n",
connector->name);
- if (IS_ERR(connector->kdev)) {
- DRM_ERROR("failed to register connector device: %ld\n", PTR_ERR(connector->kdev));
- return PTR_ERR(connector->kdev);
+ r = device_add(kdev);
+ if (r) {
+ drm_err(dev, "failed to register connector device: %d\n", r);
+ goto err_free;
}
+ connector->kdev = kdev;
+
if (connector->ddc)
return sysfs_create_link(&connector->kdev->kobj,
&connector->ddc->dev.kobj, "ddc");
return 0;
+
+err_free:
+ put_device(kdev);
+ return r;
}
void drm_sysfs_connector_remove(struct drm_connector *connector)
@@ -374,11 +440,6 @@ void drm_sysfs_connector_status_event(struct drm_connector *connector,
}
EXPORT_SYMBOL(drm_sysfs_connector_status_event);
-static void drm_sysfs_release(struct device *dev)
-{
- kfree(dev);
-}
-
struct device *drm_sysfs_minor_alloc(struct drm_minor *minor)
{
const char *minor_str;
diff --git a/drivers/usb/typec/altmodes/Kconfig b/drivers/usb/typec/altmodes/Kconfig
index 60d375e9c3c7..1a6b5e872b0d 100644
--- a/drivers/usb/typec/altmodes/Kconfig
+++ b/drivers/usb/typec/altmodes/Kconfig
@@ -4,6 +4,7 @@ menu "USB Type-C Alternate Mode drivers"
config TYPEC_DP_ALTMODE
tristate "DisplayPort Alternate Mode driver"
+ depends on DRM
help
DisplayPort USB Type-C Alternate Mode allows DisplayPort
displays and adapters to be attached to the USB Type-C
diff --git a/drivers/usb/typec/altmodes/displayport.c b/drivers/usb/typec/altmodes/displayport.c
index b7f094435b00..c1d8c23baa39 100644
--- a/drivers/usb/typec/altmodes/displayport.c
+++ b/drivers/usb/typec/altmodes/displayport.c
@@ -11,8 +11,10 @@
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/module.h>
+#include <linux/property.h>
#include <linux/usb/pd_vdo.h>
#include <linux/usb/typec_dp.h>
+#include <drm/drm_connector.h>
#include "displayport.h"
#define DP_HEADER(_dp, ver, cmd) (VDO((_dp)->alt->svid, 1, ver, cmd) \
@@ -57,19 +59,28 @@ struct dp_altmode {
struct typec_displayport_data data;
enum dp_state state;
+ bool hpd;
struct mutex lock; /* device lock */
struct work_struct work;
struct typec_altmode *alt;
const struct typec_altmode *port;
+ struct fwnode_handle *connector_fwnode;
};
static int dp_altmode_notify(struct dp_altmode *dp)
{
- u8 state = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf));
+ unsigned long conf;
+ u8 state;
+
+ if (dp->data.conf) {
+ state = get_count_order(DP_CONF_GET_PIN_ASSIGN(dp->data.conf));
+ conf = TYPEC_MODAL_STATE(state);
+ } else {
+ conf = TYPEC_STATE_USB;
+ }
- return typec_altmode_notify(dp->alt, TYPEC_MODAL_STATE(state),
- &dp->data);
+ return typec_altmode_notify(dp->alt, conf, &dp->data);
}
static int dp_altmode_configure(struct dp_altmode *dp, u8 con)
@@ -118,6 +129,7 @@ static int dp_altmode_configure(struct dp_altmode *dp, u8 con)
static int dp_altmode_status_update(struct dp_altmode *dp)
{
bool configured = !!DP_CONF_GET_PIN_ASSIGN(dp->data.conf);
+ bool hpd = !!(dp->data.status & DP_STATUS_HPD_STATE);
u8 con = DP_STATUS_CONNECTION(dp->data.status);
int ret = 0;
@@ -130,6 +142,11 @@ static int dp_altmode_status_update(struct dp_altmode *dp)
ret = dp_altmode_configure(dp, con);
if (!ret)
dp->state = DP_STATE_CONFIGURE;
+ } else {
+ if (dp->hpd != hpd) {
+ drm_connector_oob_hotplug_event(dp->connector_fwnode);
+ dp->hpd = hpd;
+ }
}
return ret;
@@ -137,21 +154,10 @@ static int dp_altmode_status_update(struct dp_altmode *dp)
static int dp_altmode_configured(struct dp_altmode *dp)
{
- int ret;
-
sysfs_notify(&dp->alt->dev.kobj, "displayport", "configuration");
-
- if (!dp->data.conf)
- return typec_altmode_notify(dp->alt, TYPEC_STATE_USB,
- &dp->data);
-
- ret = dp_altmode_notify(dp);
- if (ret)
- return ret;
-
sysfs_notify(&dp->alt->dev.kobj, "displayport", "pin_assignment");
- return 0;
+ return dp_altmode_notify(dp);
}
static int dp_altmode_configure_vdm(struct dp_altmode *dp, u32 conf)
@@ -172,13 +178,8 @@ static int dp_altmode_configure_vdm(struct dp_altmode *dp, u32 conf)
}
ret = typec_altmode_vdm(dp->alt, header, &conf, 2);
- if (ret) {
- if (DP_CONF_GET_PIN_ASSIGN(dp->data.conf))
- dp_altmode_notify(dp);
- else
- typec_altmode_notify(dp->alt, TYPEC_STATE_USB,
- &dp->data);
- }
+ if (ret)
+ dp_altmode_notify(dp);
return ret;
}
@@ -521,6 +522,7 @@ static const struct attribute_group dp_altmode_group = {
int dp_altmode_probe(struct typec_altmode *alt)
{
const struct typec_altmode *port = typec_altmode_get_partner(alt);
+ struct fwnode_handle *fwnode;
struct dp_altmode *dp;
int ret;
@@ -549,6 +551,11 @@ int dp_altmode_probe(struct typec_altmode *alt)
alt->desc = "DisplayPort";
alt->ops = &dp_altmode_ops;
+ fwnode = dev_fwnode(alt->dev.parent->parent); /* typec_port fwnode */
+ dp->connector_fwnode = fwnode_find_reference(fwnode, "displayport", 0);
+ if (IS_ERR(dp->connector_fwnode))
+ dp->connector_fwnode = NULL;
+
typec_altmode_set_drvdata(alt, dp);
dp->state = DP_STATE_ENTER;
@@ -564,6 +571,13 @@ void dp_altmode_remove(struct typec_altmode *alt)
sysfs_remove_group(&alt->dev.kobj, &dp_altmode_group);
cancel_work_sync(&dp->work);
+
+ if (dp->connector_fwnode) {
+ if (dp->hpd)
+ drm_connector_oob_hotplug_event(dp->connector_fwnode);
+
+ fwnode_handle_put(dp->connector_fwnode);
+ }
}
EXPORT_SYMBOL_GPL(dp_altmode_remove);
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index 1647960c9e50..79fa34e5ccdb 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -1084,6 +1084,14 @@ struct drm_connector_funcs {
*/
void (*atomic_print_state)(struct drm_printer *p,
const struct drm_connector_state *state);
+
+ /**
+ * @oob_hotplug_event:
+ *
+ * This will get called when a hotplug-event for a drm-connector
+ * has been received from a source outside the display driver / device.
+ */
+ void (*oob_hotplug_event)(struct drm_connector *connector);
};
/**
@@ -1228,6 +1236,14 @@ struct drm_connector {
struct device *kdev;
/** @attr: sysfs attributes */
struct device_attribute *attr;
+ /**
+ * @fwnode: associated fwnode supplied by platform firmware
+ *
+ * Drivers can set this to associate a fwnode with a connector, drivers
+ * are expected to get a reference on the fwnode when setting this.
+ * drm_connector_cleanup() will call fwnode_handle_put() on this.
+ */
+ struct fwnode_handle *fwnode;
/**
* @head:
@@ -1239,6 +1255,14 @@ struct drm_connector {
*/
struct list_head head;
+ /**
+ * @global_connector_list_entry:
+ *
+ * Connector entry in the global connector-list, used by
+ * drm_connector_find_by_fwnode().
+ */
+ struct list_head global_connector_list_entry;
+
/** @base: base KMS object */
struct drm_mode_object base;
@@ -1650,6 +1674,7 @@ drm_connector_is_unregistered(struct drm_connector *connector)
DRM_CONNECTOR_UNREGISTERED;
}
+void drm_connector_oob_hotplug_event(struct fwnode_handle *connector_fwnode);
const char *drm_get_connector_type_name(unsigned int connector_type);
const char *drm_get_connector_status_name(enum drm_connector_status status);
const char *drm_get_subpixel_order_name(enum subpixel_order order);