Re: [PATCH 2/3] usb: chipidea: Hook into mux framework to toggle usb switch

From: Peter Rosin
Date: Wed Jul 12 2017 - 02:46:00 EST


On 2017-07-12 03:02, Stephen Boyd wrote:
> On the db410c 96boards platform we have a TC7USB40MU on the board
> to mux the D+/D- lines coming from the controller between a micro
> usb "device" port and a USB hub for "host" roles[1]. During a
> role switch, we need to toggle this mux to forward the D+/D-
> lines to either the port or the hub. Add the necessary code to do
> the role switch in chipidea core via the generic mux framework.
> Board configurations like on db410c are expected to change roles
> via the sysfs API described in
> Documentation/ABI/testing/sysfs-platform-chipidea-usb2.
>
> [1] https://github.com/96boards/documentation/raw/master/ConsumerEdition/DragonBoard-410c/HardwareDocs/Schematics_DragonBoard.pdf
>
> Cc: Peter Rosin <peda@xxxxxxxxxx>
> Cc: Peter Chen <peter.chen@xxxxxxx>
> Cc: Greg Kroah-Hartman <gregkh@xxxxxxxxxxxxxxxxxxx>
> Cc: <devicetree@xxxxxxxxxxxxxxx>
> Signed-off-by: Stephen Boyd <stephen.boyd@xxxxxxxxxx>
> ---
> Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt | 8 ++++++++
> drivers/usb/chipidea/core.c | 17 +++++++++++++++++
> drivers/usb/chipidea/host.c | 10 ++++++++++
> drivers/usb/chipidea/udc.c | 11 +++++++++++
> include/linux/usb/chipidea.h | 14 ++++++++++++++
> 5 files changed, 60 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt b/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt
> index 0e03344e2e8b..96ce81d975d5 100644
> --- a/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt
> +++ b/Documentation/devicetree/bindings/usb/ci-hdrc-usb2.txt
> @@ -76,6 +76,11 @@ Optional properties:
> needs to make sure it does not send more than 90%
> maximum_periodic_data_per_frame. The use case is multiple transactions, but
> less frame rate.
> +- mux-controls: The mux control for toggling host/device output of this
> + controller.
> +- mux-control-names: Shall be "usb_switch" if mux-controls is specified.
> +- usb-switch-states: Two u32's defining the state to set on the mux for the
> + host mode and device modes respectively.
>
> i.mx specific properties
> - fsl,usbmisc: phandler of non-core register device, with one
> @@ -102,4 +107,7 @@ Example:
> rx-burst-size-dword = <0x10>;
> extcon = <0>, <&usb_id>;
> phy-clkgate-delay-us = <400>;
> + mux-controls = <&usb_switch>;
> + mux-control-names = "usb_switch";
> + usb-switch-states = <0>, <1>;

I don't see the need for usb-switch-states? Just assume states 0/1 and
if someone later needs some other states, make them add a property that
overrides the defaults. Just document that 0 is host and 1 is device.

> };
> diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c
> index b17ed3a9a304..6531d771f296 100644
> --- a/drivers/usb/chipidea/core.c
> +++ b/drivers/usb/chipidea/core.c
> @@ -64,6 +64,7 @@
> #include <linux/of.h>
> #include <linux/regulator/consumer.h>
> #include <linux/usb/ehci_def.h>
> +#include <linux/mux/consumer.h>
>
> #include "ci.h"
> #include "udc.h"
> @@ -606,6 +607,7 @@ static int ci_get_platdata(struct device *dev,
> {
> struct extcon_dev *ext_vbus, *ext_id;
> struct ci_hdrc_cable *cable;
> + struct ci_hdrc_switch *usb_switch;
> int ret;
>
> if (!platdata->phy_mode)
> @@ -690,6 +692,21 @@ static int ci_get_platdata(struct device *dev,
> if (of_find_property(dev->of_node, "non-zero-ttctrl-ttha", NULL))
> platdata->flags |= CI_HDRC_SET_NON_ZERO_TTHA;
>
> + if (IS_ENABLED(CONFIG_MULTIPLEXER)) {
> + usb_switch = &platdata->usb_switch;
> + usb_switch->mux = devm_mux_control_get(dev, "usb_switch");
> + if (!IS_ERR(usb_switch->mux)) {
> + if (of_property_read_u32_index(dev->of_node,
> + "usb-switch-states",
> + 0, &usb_switch->device))
> + return -EINVAL;
> + if (of_property_read_u32_index(dev->of_node,
> + "usb-switch-states",
> + 1, &usb_switch->host))
> + return -EINVAL;
> + }
> + }
> +
> ext_id = ERR_PTR(-ENODEV);
> ext_vbus = ERR_PTR(-ENODEV);
> if (of_property_read_bool(dev->of_node, "extcon")) {
> diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c
> index 18cb8e46262d..9fd23ecc2da3 100644
> --- a/drivers/usb/chipidea/host.c
> +++ b/drivers/usb/chipidea/host.c
> @@ -25,6 +25,7 @@
> #include <linux/usb/hcd.h>
> #include <linux/usb/chipidea.h>
> #include <linux/regulator/consumer.h>
> +#include <linux/mux/consumer.h>
>
> #include "../host/ehci.h"
>
> @@ -123,6 +124,13 @@ static int host_start(struct ci_hdrc *ci)
> if (usb_disabled())
> return -ENODEV;
>
> + if (!IS_ERR(ci->platdata->usb_switch.mux)) {
> + ret = mux_control_select(ci->platdata->usb_switch.mux,
> + ci->platdata->usb_switch.host);
> + if (ret)
> + return ret;
> + }
> +

You *must* call mux_control_deselect to clean up if there is a failure
later in host_start. Is that handled in some non-obvious way?

> hcd = __usb_create_hcd(&ci_ehci_hc_driver, ci->dev->parent,
> ci->dev, dev_name(ci->dev), NULL);
> if (!hcd)
> @@ -205,6 +213,8 @@ static void host_stop(struct ci_hdrc *ci)
> if (ci->platdata->reg_vbus && !ci_otg_is_fsm_mode(ci) &&
> (ci->platdata->flags & CI_HDRC_TURN_VBUS_EARLY_ON))
> regulator_disable(ci->platdata->reg_vbus);
> + if (!IS_ERR(ci->platdata->usb_switch.mux))
> + mux_control_deselect(ci->platdata->usb_switch.mux);
> }
> ci->hcd = NULL;
> ci->otg.host = NULL;
> diff --git a/drivers/usb/chipidea/udc.c b/drivers/usb/chipidea/udc.c
> index d68b125796f9..ab3355905740 100644
> --- a/drivers/usb/chipidea/udc.c
> +++ b/drivers/usb/chipidea/udc.c
> @@ -22,6 +22,7 @@
> #include <linux/usb/gadget.h>
> #include <linux/usb/otg-fsm.h>
> #include <linux/usb/chipidea.h>
> +#include <linux/mux/consumer.h>
>
> #include "ci.h"
> #include "udc.h"
> @@ -1899,6 +1900,13 @@ static int udc_start(struct ci_hdrc *ci)
> ci->gadget.name = ci->platdata->name;
> ci->gadget.otg_caps = otg_caps;
>
> + if (!IS_ERR(ci->platdata->usb_switch.mux)) {
> + retval = mux_control_select(ci->platdata->usb_switch.mux,
> + ci->platdata->usb_switch.device);
> + if (retval)
> + return retval;
> + }
> +

Dito.

Cheers,
peda

> if (ci->is_otg && (otg_caps->hnp_support || otg_caps->srp_support ||
> otg_caps->adp_support))
> ci->gadget.is_otg = 1;
> @@ -1982,6 +1990,9 @@ static void udc_id_switch_for_host(struct ci_hdrc *ci)
> hw_write_otgsc(ci, OTGSC_BSVIE | OTGSC_BSVIS, OTGSC_BSVIS);
>
> ci->vbus_active = 0;
> +
> + if (!IS_ERR(ci->platdata->usb_switch.mux))
> + mux_control_deselect(ci->platdata->usb_switch.mux);
> }
>
> /**
> diff --git a/include/linux/usb/chipidea.h b/include/linux/usb/chipidea.h
> index c5fdfcf99828..559bd470b8c0 100644
> --- a/include/linux/usb/chipidea.h
> +++ b/include/linux/usb/chipidea.h
> @@ -9,6 +9,7 @@
> #include <linux/usb/otg.h>
>
> struct ci_hdrc;
> +struct mux_control;
>
> /**
> * struct ci_hdrc_cable - structure for external connector cable state tracking
> @@ -29,6 +30,18 @@ struct ci_hdrc_cable {
> struct notifier_block nb;
> };
>
> +/**
> + * struct ci_hdrc_switch - structure for usb mux control
> + * @mux: mux to set @host state or @device state on during role switch
> + * @host: Value to set for mux to connect D+/D- to host D+/D- lines
> + * @device: Value to set for mux to connect D+/D- to device D+/D- lines
> + */
> +struct ci_hdrc_switch {
> + struct mux_control *mux;
> + int host;
> + int device;
> +};
> +
> struct ci_hdrc_platform_data {
> const char *name;
> /* offset of the capability registers */
> @@ -74,6 +87,7 @@ struct ci_hdrc_platform_data {
> /* VBUS and ID signal state tracking, using extcon framework */
> struct ci_hdrc_cable vbus_extcon;
> struct ci_hdrc_cable id_extcon;
> + struct ci_hdrc_switch usb_switch;
> u32 phy_clkgate_delay_us;
> };
>
>