[PATCH 3/4] usb: typec: mux: add typec orientation switch support via mux controller
Peter Rosin
peda at axentia.se
Mon Aug 22 23:13:06 PDT 2022
Hi!
2022-08-22 at 17:35, Xu Yang wrote:
> Some dedicated mux block can use existing mux controller as a mux
> provider, typec port as a consumer to select channel for orientation
> switch, this can be an alternate way to control typec orientation switch.
> Also, one mux controller could cover highspeed, superspeed and sideband
> use case one time in this implementation.
>
> Signed-off-by: Xu Yang <xu.yang_2 at nxp.com>
> ---
> drivers/usb/typec/mux.c | 74 +++++++++++++++++++++++++++++++++++
> include/linux/usb/typec_mux.h | 7 +---
> 2 files changed, 76 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/usb/typec/mux.c b/drivers/usb/typec/mux.c
> index 464330776cd6..5ee960fb668d 100644
> --- a/drivers/usb/typec/mux.c
> +++ b/drivers/usb/typec/mux.c
> @@ -13,6 +13,7 @@
> #include <linux/mutex.h>
> #include <linux/property.h>
> #include <linux/slab.h>
> +#include <linux/mux/consumer.h>
>
> #include "class.h"
> #include "mux.h"
> @@ -22,6 +23,11 @@
> struct typec_switch {
> struct typec_switch_dev *sw_devs[TYPEC_MUX_MAX_DEVS];
> unsigned int num_sw_devs;
> +
> + /* Could handle HighSpeed, SuperSpeed, Sideband switch one time */
> + struct mux_control *mux_switch;
> + /* 3 state correspond to NONE, NORMAL, REVERSE for all switches */
> + int mux_states[3];
> };
>
> static int switch_fwnode_match(struct device *dev, const void *fwnode)
> @@ -117,6 +123,58 @@ struct typec_switch *fwnode_typec_switch_get(struct fwnode_handle *fwnode)
> }
> EXPORT_SYMBOL_GPL(fwnode_typec_switch_get);
>
> +static struct typec_switch *mux_control_typec_switch_get(struct device *dev)
> +{
> + struct typec_switch *sw;
> + struct mux_control *mux;
> + int ret;
> +
> + if (!device_property_present(dev, "mux-controls"))
> + return NULL;
> +
> + sw = kzalloc(sizeof(*sw), GFP_KERNEL);
> + if (!sw)
> + return ERR_PTR(-ENOMEM);
> +
> + mux = mux_control_get(dev, NULL);
> + if (!IS_ERR(mux)) {
> + sw->mux_switch = mux;
> + ret = device_property_read_u32_array(dev,
> + "typec-switch-states", sw->mux_states, 3);
> + if (ret) {
> + kfree(sw);
> + return ERR_PTR(ret);
> + }
> + } else {
> + kfree(sw);
> + return ERR_CAST(mux);
> + }
> +
> + return sw;
> +}
> +
> +/**
> + * typec_switch_get - Find USB Type-C orientation switch
> + * @dev: The device using switch
> + *
> + * Finds a switch used by @dev. Returns a reference to the switch on
> + * success, NULL if no matching connection was found, or
> + * ERR_PTR(-EPROBE_DEFER) when a connection was found but the switch
> + * has not been enumerated yet, or ERR_PTR with a negative errno.
> + */
> +struct typec_switch *typec_switch_get(struct device *dev)
> +{
> + struct typec_switch *sw;
> +
> + sw = fwnode_typec_switch_get(dev_fwnode(dev));
> + if (!sw)
> + /* Try get switch based on mux control */
> + sw = mux_control_typec_switch_get(dev);
> +
> + return sw;
> +}
> +EXPORT_SYMBOL_GPL(typec_switch_get);
> +
> /**
> * typec_switch_put - Release USB Type-C orientation switch
> * @sw: USB Type-C orientation switch
> @@ -137,6 +195,10 @@ void typec_switch_put(struct typec_switch *sw)
> module_put(sw_dev->dev.parent->driver->owner);
> put_device(&sw_dev->dev);
> }
> +
> + if (sw->mux_switch)
> + mux_control_put(sw->mux_switch);
> +
> kfree(sw);
> }
> EXPORT_SYMBOL_GPL(typec_switch_put);
> @@ -204,6 +266,7 @@ int typec_switch_set(struct typec_switch *sw,
> enum typec_orientation orientation)
> {
> struct typec_switch_dev *sw_dev;
> + struct mux_control *mux;
> unsigned int i;
> int ret;
>
> @@ -218,6 +281,17 @@ int typec_switch_set(struct typec_switch *sw,
> return ret;
> }
>
> + mux = sw->mux_switch;
> + if (mux) {
> + ret = mux_control_deselect(mux);
This is broken. Please read the documentation for mux_control_select and
mux_control_deselect. Every call to mux_control_deselect *must* be paired
with a *successful* call to mux_control_select. Here, mux_control_deselect
is called unconditionally (as long as a mux is configured).
Yes, agreed, that is indeed awkward (and fragile). But those are the rules.
(the mux interface was not designed for long-time selects like this)
Cheers,
Peter
> + if (ret)
> + return ret;
> +
> + ret = mux_control_select(mux, sw->mux_states[orientation]);
> + if (ret)
> + return ret;
> + }
> +
> return 0;
> }
> EXPORT_SYMBOL_GPL(typec_switch_set);
> diff --git a/include/linux/usb/typec_mux.h b/include/linux/usb/typec_mux.h
> index 9292f0e07846..2287e5a5f591 100644
> --- a/include/linux/usb/typec_mux.h
> +++ b/include/linux/usb/typec_mux.h
> @@ -24,16 +24,13 @@ struct typec_switch_desc {
> void *drvdata;
> };
>
> +
> +struct typec_switch *typec_switch_get(struct device *dev);
> struct typec_switch *fwnode_typec_switch_get(struct fwnode_handle *fwnode);
> void typec_switch_put(struct typec_switch *sw);
> int typec_switch_set(struct typec_switch *sw,
> enum typec_orientation orientation);
>
> -static inline struct typec_switch *typec_switch_get(struct device *dev)
> -{
> - return fwnode_typec_switch_get(dev_fwnode(dev));
> -}
> -
> struct typec_switch_dev *
> typec_switch_register(struct device *parent,
> const struct typec_switch_desc *desc);
More information about the linux-arm-kernel
mailing list