aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/typec/mux.c
diff options
context:
space:
mode:
authorHeikki Krogerus <heikki.krogerus@linux.intel.com>2019-05-31 17:15:41 +0300
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>2019-06-03 10:55:38 +0200
commit3370db35193b241ba5836a66df6ec1a559108389 (patch)
tree2b71ccf9d778bb28aa1930a1a00fd05307754760 /drivers/usb/typec/mux.c
parentdevice connection: Find connections also by checking the references (diff)
downloadlinux-dev-3370db35193b241ba5836a66df6ec1a559108389.tar.xz
linux-dev-3370db35193b241ba5836a66df6ec1a559108389.zip
usb: typec: Registering real device entries for the muxes
Registering real device entries (struct device) for the mode muxes as well as for the orientation switches. The Type-C mux code was deliberately attempting to avoid creation of separate device entries for the orientation switch and the mode switch (alternate modes) because they are not physical devices. They are functions of a single physical multiplexer/demultiplexer switch device. Unfortunately because of the dependency we still have on the underlying mux device driver, we had to put in hacks like the one in the commit 3e3b81965cbf ("usb: typec: mux: Take care of driver module reference counting") to make sure the driver does not disappear from underneath us. Even with those hacks we were still left with a potential NUll pointer dereference scenario, so just creating the device entries, and letting the core take care of the dependencies. No more hacks needed. Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com> Tested-by: Hans de Goede <hdegoede@redhat.com> Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
Diffstat (limited to 'drivers/usb/typec/mux.c')
-rw-r--r--drivers/usb/typec/mux.c238
1 files changed, 172 insertions, 66 deletions
diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c
index 2ce54f3fc79c..61b7bc58dd81 100644
--- a/drivers/usb/typec/mux.c
+++ b/drivers/usb/typec/mux.c
@@ -15,35 +15,47 @@
#include <linux/slab.h>
#include <linux/usb/typec_mux.h>
-static DEFINE_MUTEX(switch_lock);
-static DEFINE_MUTEX(mux_lock);
-static LIST_HEAD(switch_list);
-static LIST_HEAD(mux_list);
+#include "bus.h"
+
+static int name_match(struct device *dev, const void *name)
+{
+ return !strcmp((const char *)name, dev_name(dev));
+}
+
+static bool dev_name_ends_with(struct device *dev, const char *suffix)
+{
+ const char *name = dev_name(dev);
+ const int name_len = strlen(name);
+ const int suffix_len = strlen(suffix);
+
+ if (suffix_len > name_len)
+ return false;
+
+ return strcmp(name + (name_len - suffix_len), suffix) == 0;
+}
+
+static int switch_fwnode_match(struct device *dev, const void *fwnode)
+{
+ return dev_fwnode(dev) == fwnode && dev_name_ends_with(dev, "-switch");
+}
static void *typec_switch_match(struct device_connection *con, int ep,
void *data)
{
- struct typec_switch *sw;
-
- if (!con->fwnode) {
- list_for_each_entry(sw, &switch_list, entry)
- if (!strcmp(con->endpoint[ep], dev_name(sw->dev)))
- return sw;
- return ERR_PTR(-EPROBE_DEFER);
- }
+ struct device *dev;
- /*
- * With OF graph the mux node must have a boolean device property named
- * "orientation-switch".
- */
- if (con->id && !fwnode_property_present(con->fwnode, con->id))
- return NULL;
+ if (con->fwnode) {
+ if (con->id && !fwnode_property_present(con->fwnode, con->id))
+ return NULL;
- list_for_each_entry(sw, &switch_list, entry)
- if (dev_fwnode(sw->dev) == con->fwnode)
- return sw;
+ dev = class_find_device(&typec_mux_class, NULL, con->fwnode,
+ switch_fwnode_match);
+ } else {
+ dev = class_find_device(&typec_mux_class, NULL,
+ con->endpoint[ep], name_match);
+ }
- return con->id ? ERR_PTR(-EPROBE_DEFER) : NULL;
+ return dev ? to_typec_switch(dev) : ERR_PTR(-EPROBE_DEFER);
}
/**
@@ -59,14 +71,10 @@ struct typec_switch *typec_switch_get(struct device *dev)
{
struct typec_switch *sw;
- mutex_lock(&switch_lock);
sw = device_connection_find_match(dev, "orientation-switch", NULL,
typec_switch_match);
- if (!IS_ERR_OR_NULL(sw)) {
- WARN_ON(!try_module_get(sw->dev->driver->owner));
- get_device(sw->dev);
- }
- mutex_unlock(&switch_lock);
+ if (!IS_ERR_OR_NULL(sw))
+ WARN_ON(!try_module_get(sw->dev.parent->driver->owner));
return sw;
}
@@ -81,28 +89,64 @@ EXPORT_SYMBOL_GPL(typec_switch_get);
void typec_switch_put(struct typec_switch *sw)
{
if (!IS_ERR_OR_NULL(sw)) {
- module_put(sw->dev->driver->owner);
- put_device(sw->dev);
+ module_put(sw->dev.parent->driver->owner);
+ put_device(&sw->dev);
}
}
EXPORT_SYMBOL_GPL(typec_switch_put);
+static void typec_switch_release(struct device *dev)
+{
+ kfree(to_typec_switch(dev));
+}
+
+static const struct device_type typec_switch_dev_type = {
+ .name = "orientation_switch",
+ .release = typec_switch_release,
+};
+
/**
* typec_switch_register - Register USB Type-C orientation switch
- * @sw: USB Type-C orientation switch
+ * @parent: Parent device
+ * @desc: Orientation switch description
*
* This function registers a switch that can be used for routing the correct
* data pairs depending on the cable plug orientation from the USB Type-C
* connector to the USB controllers. USB Type-C plugs can be inserted
* right-side-up or upside-down.
*/
-int typec_switch_register(struct typec_switch *sw)
+struct typec_switch *
+typec_switch_register(struct device *parent,
+ const struct typec_switch_desc *desc)
{
- mutex_lock(&switch_lock);
- list_add_tail(&sw->entry, &switch_list);
- mutex_unlock(&switch_lock);
+ struct typec_switch *sw;
+ int ret;
+
+ if (!desc || !desc->set)
+ return ERR_PTR(-EINVAL);
+
+ sw = kzalloc(sizeof(*sw), GFP_KERNEL);
+ if (!sw)
+ return ERR_PTR(-ENOMEM);
- return 0;
+ sw->set = desc->set;
+
+ device_initialize(&sw->dev);
+ sw->dev.parent = parent;
+ sw->dev.fwnode = desc->fwnode;
+ sw->dev.class = &typec_mux_class;
+ sw->dev.type = &typec_switch_dev_type;
+ sw->dev.driver_data = desc->drvdata;
+ dev_set_name(&sw->dev, "%s-switch", dev_name(parent));
+
+ ret = device_add(&sw->dev);
+ if (ret) {
+ dev_err(parent, "failed to register switch (%d)\n", ret);
+ put_device(&sw->dev);
+ return ERR_PTR(ret);
+ }
+
+ return sw;
}
EXPORT_SYMBOL_GPL(typec_switch_register);
@@ -114,28 +158,44 @@ EXPORT_SYMBOL_GPL(typec_switch_register);
*/
void typec_switch_unregister(struct typec_switch *sw)
{
- mutex_lock(&switch_lock);
- list_del(&sw->entry);
- mutex_unlock(&switch_lock);
+ if (!IS_ERR_OR_NULL(sw))
+ device_unregister(&sw->dev);
}
EXPORT_SYMBOL_GPL(typec_switch_unregister);
+void typec_switch_set_drvdata(struct typec_switch *sw, void *data)
+{
+ dev_set_drvdata(&sw->dev, data);
+}
+EXPORT_SYMBOL_GPL(typec_switch_set_drvdata);
+
+void *typec_switch_get_drvdata(struct typec_switch *sw)
+{
+ return dev_get_drvdata(&sw->dev);
+}
+EXPORT_SYMBOL_GPL(typec_switch_get_drvdata);
+
/* ------------------------------------------------------------------------- */
+static int mux_fwnode_match(struct device *dev, const void *fwnode)
+{
+ return dev_fwnode(dev) == fwnode && dev_name_ends_with(dev, "-mux");
+}
+
static void *typec_mux_match(struct device_connection *con, int ep, void *data)
{
const struct typec_altmode_desc *desc = data;
- struct typec_mux *mux;
- int nval;
+ struct device *dev;
bool match;
+ int nval;
u16 *val;
int i;
if (!con->fwnode) {
- list_for_each_entry(mux, &mux_list, entry)
- if (!strcmp(con->endpoint[ep], dev_name(mux->dev)))
- return mux;
- return ERR_PTR(-EPROBE_DEFER);
+ dev = class_find_device(&typec_mux_class, NULL,
+ con->endpoint[ep], name_match);
+
+ return dev ? to_typec_switch(dev) : ERR_PTR(-EPROBE_DEFER);
}
/*
@@ -180,11 +240,10 @@ static void *typec_mux_match(struct device_connection *con, int ep, void *data)
return NULL;
find_mux:
- list_for_each_entry(mux, &mux_list, entry)
- if (dev_fwnode(mux->dev) == con->fwnode)
- return mux;
+ dev = class_find_device(&typec_mux_class, NULL, con->fwnode,
+ mux_fwnode_match);
- return ERR_PTR(-EPROBE_DEFER);
+ return dev ? to_typec_switch(dev) : ERR_PTR(-EPROBE_DEFER);
}
/**
@@ -202,14 +261,10 @@ struct typec_mux *typec_mux_get(struct device *dev,
{
struct typec_mux *mux;
- mutex_lock(&mux_lock);
mux = device_connection_find_match(dev, "mode-switch", (void *)desc,
typec_mux_match);
- if (!IS_ERR_OR_NULL(mux)) {
- WARN_ON(!try_module_get(mux->dev->driver->owner));
- get_device(mux->dev);
- }
- mutex_unlock(&mux_lock);
+ if (!IS_ERR_OR_NULL(mux))
+ WARN_ON(!try_module_get(mux->dev.parent->driver->owner));
return mux;
}
@@ -224,28 +279,63 @@ EXPORT_SYMBOL_GPL(typec_mux_get);
void typec_mux_put(struct typec_mux *mux)
{
if (!IS_ERR_OR_NULL(mux)) {
- module_put(mux->dev->driver->owner);
- put_device(mux->dev);
+ module_put(mux->dev.parent->driver->owner);
+ put_device(&mux->dev);
}
}
EXPORT_SYMBOL_GPL(typec_mux_put);
+static void typec_mux_release(struct device *dev)
+{
+ kfree(to_typec_mux(dev));
+}
+
+static const struct device_type typec_mux_dev_type = {
+ .name = "mode_switch",
+ .release = typec_mux_release,
+};
+
/**
* typec_mux_register - Register Multiplexer routing USB Type-C pins
- * @mux: USB Type-C Connector Multiplexer/DeMultiplexer
+ * @parent: Parent device
+ * @desc: Multiplexer description
*
* USB Type-C connectors can be used for alternate modes of operation besides
* USB when Accessory/Alternate Modes are supported. With some of those modes,
* the pins on the connector need to be reconfigured. This function registers
* multiplexer switches routing the pins on the connector.
*/
-int typec_mux_register(struct typec_mux *mux)
+struct typec_mux *
+typec_mux_register(struct device *parent, const struct typec_mux_desc *desc)
{
- mutex_lock(&mux_lock);
- list_add_tail(&mux->entry, &mux_list);
- mutex_unlock(&mux_lock);
+ struct typec_mux *mux;
+ int ret;
+
+ if (!desc || !desc->set)
+ return ERR_PTR(-EINVAL);
+
+ mux = kzalloc(sizeof(*mux), GFP_KERNEL);
+ if (!mux)
+ return ERR_PTR(-ENOMEM);
+
+ mux->set = desc->set;
+
+ device_initialize(&mux->dev);
+ mux->dev.parent = parent;
+ mux->dev.fwnode = desc->fwnode;
+ mux->dev.class = &typec_mux_class;
+ mux->dev.type = &typec_mux_dev_type;
+ mux->dev.driver_data = desc->drvdata;
+ dev_set_name(&mux->dev, "%s-mux", dev_name(parent));
+
+ ret = device_add(&mux->dev);
+ if (ret) {
+ dev_err(parent, "failed to register mux (%d)\n", ret);
+ put_device(&mux->dev);
+ return ERR_PTR(ret);
+ }
- return 0;
+ return mux;
}
EXPORT_SYMBOL_GPL(typec_mux_register);
@@ -257,8 +347,24 @@ EXPORT_SYMBOL_GPL(typec_mux_register);
*/
void typec_mux_unregister(struct typec_mux *mux)
{
- mutex_lock(&mux_lock);
- list_del(&mux->entry);
- mutex_unlock(&mux_lock);
+ if (!IS_ERR_OR_NULL(mux))
+ device_unregister(&mux->dev);
}
EXPORT_SYMBOL_GPL(typec_mux_unregister);
+
+void typec_mux_set_drvdata(struct typec_mux *mux, void *data)
+{
+ dev_set_drvdata(&mux->dev, data);
+}
+EXPORT_SYMBOL_GPL(typec_mux_set_drvdata);
+
+void *typec_mux_get_drvdata(struct typec_mux *mux)
+{
+ return dev_get_drvdata(&mux->dev);
+}
+EXPORT_SYMBOL_GPL(typec_mux_get_drvdata);
+
+struct class typec_mux_class = {
+ .name = "typec_mux",
+ .owner = THIS_MODULE,
+};