From patchwork Tue Jun 2 13:14:33 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Ivan T. Ivanov" X-Patchwork-Id: 6528751 X-Patchwork-Delegate: agross@codeaurora.org Return-Path: X-Original-To: patchwork-linux-arm-msm@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id F3145C0020 for ; Tue, 2 Jun 2015 13:14:44 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id BD68020570 for ; Tue, 2 Jun 2015 13:14:43 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 3A00A20544 for ; Tue, 2 Jun 2015 13:14:42 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1758926AbbFBNOk (ORCPT ); Tue, 2 Jun 2015 09:14:40 -0400 Received: from mail-wg0-f46.google.com ([74.125.82.46]:36428 "EHLO mail-wg0-f46.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1758948AbbFBNOi (ORCPT ); Tue, 2 Jun 2015 09:14:38 -0400 Received: by wgbgq6 with SMTP id gq6so139905943wgb.3 for ; Tue, 02 Jun 2015 06:14:36 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:from:to:cc:subject:date:message-id; bh=TDW23ocT0LjySyUiPw83HggoYNeG5BY7B+vMf2GaytM=; b=KFDR7WFPymh82BgdhfJ6NMw8xjsu6WcscnyPNbfYMgQ7WttSH+meNKp9ywY/zaLcBy k8FXKQ7tsOZ077jKe0jFurcgyhyHY26W9rrmY//N2AaO7H9cfywlVQTyI8kCreuHDgYv yb9IDnOwo6x8kzp1vaJvBuAmOlH9ZRi5LPAcOTP48jGrrGVJVWzk1fGY5h9apeVCi56d BmbRPGOAnLYEM636w8EzmEUuWHYRQ7oV8dp7ycmwRi/l4MmahZYH0Xtj9iyN/Eibz1Bj gumNC4m/zNOtNG3CWF4JnSadsuVCDq5vbTodNKUb5YqIz0XtMjwpOCA4Adwpb4hceLCT hJUg== X-Gm-Message-State: ALoCoQkNXSyH+ga24q/eN0G4IzBgc5XeeSX4Z4Gjc4wUa/WhOuyVsxQSdQ+dBcYmIdUKeOu/GK2O X-Received: by 10.194.90.100 with SMTP id bv4mr49233625wjb.143.1433250876543; Tue, 02 Jun 2015 06:14:36 -0700 (PDT) Received: from mms-0439.wifi.mm-sol.com ([37.157.136.206]) by mx.google.com with ESMTPSA id ul1sm17745279wjc.30.2015.06.02.06.14.34 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Tue, 02 Jun 2015 06:14:35 -0700 (PDT) From: "Ivan T. Ivanov" To: Peter Chen Cc: Kumar Gala , Rob Herring , Pawel Moll , Mark Rutland , Ian Campbell , Greg Kroah-Hartman , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-usb@vger.kernel.org, linux-arm-msm@vger.kernel.org Subject: [PATCH v3] usb: chipidea: Use extcon framework for VBUS and ID detect Date: Tue, 2 Jun 2015 16:14:33 +0300 Message-Id: <1433250873-20780-1-git-send-email-ivan.ivanov@linaro.org> X-Mailer: git-send-email 1.9.1 Sender: linux-arm-msm-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-arm-msm@vger.kernel.org X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, T_RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP On recent Qualcomm platforms VBUS and ID lines are not routed to USB PHY LINK controller. Use extcon framework to receive connect and disconnect ID and VBUS notification. Signed-off-by: Ivan T. Ivanov --- Changes sice v2 [1]. * Simulate IRQ on extcon event - used to trigger OTG state machine. I have to admit that I couldn't test complete Chipidea OTG state machine, because my setup is little weird. I am using "qcom,usb-otg-ci" as PHY/OTG provider, "qcom,ehci-host" as host controller driver and "qcom,ci-hdrc" for device role. There could be patch conflict regarding ci-hdrc-qcom.txt, because Rob Herring is consolidating DT binding for all vendors which integrate this device [2] [1] https://lkml.org/lkml/2015/4/15/283 [2] http://www.spinics.net/lists/linux-usb/msg125453.html .../devicetree/bindings/usb/ci-hdrc-qcom.txt | 9 ++ drivers/usb/chipidea/Kconfig | 1 + drivers/usb/chipidea/core.c | 125 +++++++++++++++++++++ drivers/usb/chipidea/otg.c | 55 ++++++++- include/linux/usb/chipidea.h | 24 ++++ 5 files changed, 212 insertions(+), 2 deletions(-) -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-arm-msm" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html diff --git a/Documentation/devicetree/bindings/usb/ci-hdrc-qcom.txt b/Documentation/devicetree/bindings/usb/ci-hdrc-qcom.txt index f2899b5..c635aca 100644 --- a/Documentation/devicetree/bindings/usb/ci-hdrc-qcom.txt +++ b/Documentation/devicetree/bindings/usb/ci-hdrc-qcom.txt @@ -7,6 +7,14 @@ Required properties: - usb-phy: phandle for the PHY device - dr_mode: Should be "peripheral" +Optional properties: +- extcon: phandles to external connector devices. First phandle + should point to external connector, which provide "USB" + cable events, the second should point to external connector + device, which provide "USB-HOST" cable events. If one of + the external connector devices is not required, empty <0> + phandle should be specified. + Examples: gadget@f9a55000 { compatible = "qcom,ci-hdrc"; @@ -14,4 +22,5 @@ Examples: dr_mode = "peripheral"; interrupts = <0 134 0>; usb-phy = <&usbphy0>; + extcon = <0>, <&usb_id>; }; diff --git a/drivers/usb/chipidea/Kconfig b/drivers/usb/chipidea/Kconfig index 5ce3f1d..5619b8c 100644 --- a/drivers/usb/chipidea/Kconfig +++ b/drivers/usb/chipidea/Kconfig @@ -1,6 +1,7 @@ config USB_CHIPIDEA tristate "ChipIdea Highspeed Dual Role Controller" depends on ((USB_EHCI_HCD && USB_GADGET) || (USB_EHCI_HCD && !USB_GADGET) || (!USB_EHCI_HCD && USB_GADGET)) && HAS_DMA + select EXTCON help Say Y here if your system has a dual role high speed USB controller based on ChipIdea silicon IP. Currently, only the diff --git a/drivers/usb/chipidea/core.c b/drivers/usb/chipidea/core.c index 74fea4f..2ae2c09 100644 --- a/drivers/usb/chipidea/core.c +++ b/drivers/usb/chipidea/core.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -557,9 +558,47 @@ static irqreturn_t ci_irq(int irq, void *data) return ret; } +static int ci_vbus_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + struct ci_hdrc_cable *vbus = container_of(nb, struct ci_hdrc_cable, nb); + struct ci_hdrc *ci = vbus->ci; + + if (event) + vbus->state = true; + else + vbus->state = false; + + vbus->changed = true; + + ci_irq(ci->irq, ci); + return NOTIFY_DONE; +} + +static int ci_id_notifier(struct notifier_block *nb, unsigned long event, + void *ptr) +{ + struct ci_hdrc_cable *id = container_of(nb, struct ci_hdrc_cable, nb); + struct ci_hdrc *ci = id->ci; + + if (event) + id->state = false; + else + id->state = true; + + id->changed = true; + + ci_irq(ci->irq, ci); + return NOTIFY_DONE; +} + static int ci_get_platdata(struct device *dev, struct ci_hdrc_platform_data *platdata) { + struct extcon_dev *ext_vbus, *ext_id; + struct ci_hdrc_cable *cable; + int ret; + if (!platdata->phy_mode) platdata->phy_mode = of_usb_get_phy_mode(dev->of_node); @@ -591,9 +630,89 @@ static int ci_get_platdata(struct device *dev, if (of_usb_get_maximum_speed(dev->of_node) == USB_SPEED_FULL) platdata->flags |= CI_HDRC_FORCE_FULLSPEED; + ext_id = ERR_PTR(-ENODEV); + ext_vbus = ERR_PTR(-ENODEV); + if (of_property_read_bool(dev->of_node, "extcon")) { + /* Each one of them is not mandatory */ + ext_vbus = extcon_get_edev_by_phandle(dev, 0); + if (IS_ERR(ext_vbus) && PTR_ERR(ext_vbus) != -ENODEV) + return PTR_ERR(ext_vbus); + + ext_id = extcon_get_edev_by_phandle(dev, 1); + if (IS_ERR(ext_id) && PTR_ERR(ext_id) != -ENODEV) + return PTR_ERR(ext_id); + } + + cable = &platdata->vbus_extcon; + cable->nb.notifier_call = ci_vbus_notifier; + cable->edev = ext_vbus; + + if (!IS_ERR(ext_vbus)) { + ret = extcon_get_cable_state(cable->edev, "USB"); + if (ret) + cable->state = true; + else + cable->state = false; + } + + cable = &platdata->id_extcon; + cable->nb.notifier_call = ci_id_notifier; + cable->edev = ext_id; + + if (!IS_ERR(ext_id)) { + ret = extcon_get_cable_state(cable->edev, "USB-HOST"); + if (ret) + cable->state = false; + else + cable->state = true; + } + return 0; +} + +static int ci_extcon_register(struct ci_hdrc *ci) +{ + struct ci_hdrc_cable *id, *vbus; + int ret; + + id = &ci->platdata->id_extcon; + id->ci = ci; + if (!IS_ERR(id->edev)) { + ret = extcon_register_interest(&id->conn, id->edev->name, + "USB-HOST", &id->nb); + if (ret < 0) { + dev_err(ci->dev, "register ID failed\n"); + return ret; + } + } + + vbus = &ci->platdata->vbus_extcon; + vbus->ci = ci; + if (!IS_ERR(vbus->edev)) { + ret = extcon_register_interest(&vbus->conn, vbus->edev->name, + "USB", &vbus->nb); + if (ret < 0) { + extcon_unregister_interest(&id->conn); + dev_err(ci->dev, "register VBUS failed\n"); + return ret; + } + } + return 0; } +static void ci_extcon_unregister(struct ci_hdrc *ci) +{ + struct ci_hdrc_cable *cable; + + cable = &ci->platdata->id_extcon; + if (!IS_ERR(cable->edev)) + extcon_unregister_interest(&cable->conn); + + cable = &ci->platdata->vbus_extcon; + if (!IS_ERR(cable->edev)) + extcon_unregister_interest(&cable->conn); +} + static DEFINE_IDA(ci_ida); struct platform_device *ci_hdrc_add_device(struct device *dev, @@ -817,6 +936,10 @@ static int ci_hdrc_probe(struct platform_device *pdev) if (ret) goto stop; + ret = ci_extcon_register(ci); + if (ret) + goto stop; + if (ci->supports_runtime_pm) { pm_runtime_set_active(&pdev->dev); pm_runtime_enable(&pdev->dev); @@ -834,6 +957,7 @@ static int ci_hdrc_probe(struct platform_device *pdev) if (!ret) return 0; + ci_extcon_unregister(ci); stop: ci_role_destroy(ci); deinit_phy: @@ -853,6 +977,7 @@ static int ci_hdrc_remove(struct platform_device *pdev) } dbg_remove_files(ci); + ci_extcon_unregister(ci); ci_role_destroy(ci); ci_hdrc_enter_lpm(ci, true); ci_usb_phy_exit(ci); diff --git a/drivers/usb/chipidea/otg.c b/drivers/usb/chipidea/otg.c index ad6c87a..77fe2df 100644 --- a/drivers/usb/chipidea/otg.c +++ b/drivers/usb/chipidea/otg.c @@ -30,7 +30,41 @@ */ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) { - return hw_read(ci, OP_OTGSC, mask); + struct ci_hdrc_cable *cable; + u32 val = hw_read(ci, OP_OTGSC, mask); + + /* + * If using extcon framework for VBUS and/or ID signal + * detection overwrite OTGSC resiter value + */ + cable = &ci->platdata->vbus_extcon; + if (!IS_ERR(cable->edev)) { + if (cable->changed) + val |= OTGSC_BSVIS; + else + val &= ~OTGSC_BSVIS; + + if (cable->state) + val |= OTGSC_BSV; + else + val &= ~OTGSC_BSV; + } + + cable = &ci->platdata->id_extcon; + if (!IS_ERR(cable->edev)) { + if (cable->changed) + val |= OTGSC_IDIS; + else + val &= ~OTGSC_IDIS; + + if (cable->state) + val |= OTGSC_ID; + else + val &= ~OTGSC_ID; + } + + val &= mask; + return val; } /** @@ -40,7 +74,24 @@ u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) */ void hw_write_otgsc(struct ci_hdrc *ci, u32 mask, u32 data) { - hw_write(ci, OP_OTGSC, mask | OTGSC_INT_STATUS_BITS, data); + struct ci_hdrc_cable *cable; + + mask |= OTGSC_INT_STATUS_BITS; + + /* clear artificial bits */ + cable = &ci->platdata->vbus_extcon; + if (!IS_ERR(cable->edev)) { + if (data & OTGSC_BSVIS) + cable->changed = false; + } + + cable = &ci->platdata->id_extcon; + if (!IS_ERR(cable->edev)) { + if (data & OTGSC_IDIS) + cable->changed = false; + } + + hw_write(ci, OP_OTGSC, mask, data); } /** diff --git a/include/linux/usb/chipidea.h b/include/linux/usb/chipidea.h index ab94f78..05915cb1 100644 --- a/include/linux/usb/chipidea.h +++ b/include/linux/usb/chipidea.h @@ -5,9 +5,29 @@ #ifndef __LINUX_USB_CHIPIDEA_H #define __LINUX_USB_CHIPIDEA_H +#include #include struct ci_hdrc; + +/** + * struct ci_hdrc_cable - structure for external connector cable state tracking + * @state: current state of the line + * @changed: set to true when extcon event happen + * @edev: device which generate events + * @ci: driver state of the chipidea device + * @nb: hold event notification callback + * @conn: used for notification registration + */ +struct ci_hdrc_cable { + bool state; + bool changed; + struct extcon_dev *edev; + struct ci_hdrc *ci; + struct notifier_block nb; + struct extcon_specific_cable_nb conn; +}; + struct ci_hdrc_platform_data { const char *name; /* offset of the capability registers */ @@ -35,6 +55,10 @@ struct ci_hdrc_platform_data { void (*notify_event) (struct ci_hdrc *ci, unsigned event); struct regulator *reg_vbus; bool tpl_support; + + /* VBUS and ID signal state tracking, using extcon framework */ + struct ci_hdrc_cable vbus_extcon; + struct ci_hdrc_cable id_extcon; }; /* Default offset of capability registers */