Loading Documentation/devicetree/bindings/platform/msm/gpio-usbdetect.txt 0 → 100644 +25 −0 Original line number Diff line number Diff line GPIO USB VBUS Detection Discrete USB VBUS detection circuitry can be connected to the AP or PMICs. Such circuits can be used to detect the when a USB cable is connected to an upstream port such as a standard host or a wall charger by detecting the presence of VBUS voltage. The GPIO can be configured to trigger an interrupt, and allow the software driver to in turn notify the USB subsytem using the power_supply framework. Required Properties: - compatible: must be "qcom,gpio-usbdetect" - qcom,vbus-det-gpio: GPIO from which VBUS detection can be read from. - interrupts: an interrupt triggered by the output of the detection circuit - interrupt-names: must be "vbus_det_irq" Optional Properties: - vin-supply: phandle to a regulator that powers this circuit, if needed Example: usb_detect { compatible = "qcom,gpio-usbdetect"; qcom,vbus-det-gpio = <&pm8084 2 0>; vin-supply = <&vbus_det_reg>; }; drivers/platform/msm/Kconfig +8 −0 Original line number Diff line number Diff line Loading @@ -152,6 +152,14 @@ config QPNP_REVID numbers in the kernel log along with the PMIC option status. The PMIC type is mapped to a QTI chip part number and logged as well. config GPIO_USB_DETECT tristate "GPIO-based USB VBUS Detection" depends on POWER_SUPPLY help This driver supports external USB VBUS detection circuitry whose output is connected to a GPIO. The driver in turn notifies the USB driver of VBUS presence/disconnection using the power_supply framework. config MSM_MHI_DEV tristate "Modem Device Interface Driver" Loading drivers/platform/msm/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ obj-$(CONFIG_IPA3) += ipa/ obj-$(CONFIG_SPS) += sps/ obj-$(CONFIG_QPNP_COINCELL) += qpnp-coincell.o obj-$(CONFIG_QPNP_REVID) += qpnp-revid.o obj-$(CONFIG_GPIO_USB_DETECT) += gpio-usbdetect.o obj-$(CONFIG_EP_PCIE) += ep_pcie/ obj-$(CONFIG_MSM_MHI_DEV) += mhi_dev/ obj-$(CONFIG_USB_BAM) += usb_bam.o Loading drivers/platform/msm/gpio-usbdetect.c 0 → 100644 +266 −0 Original line number Diff line number Diff line /* Copyright (c) 2013-2015, 2017-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/err.h> #include <linux/errno.h> #include <linux/slab.h> #include <linux/of_platform.h> #include <linux/interrupt.h> #include <linux/of_gpio.h> #include <linux/gpio.h> #include <linux/extcon.h> #include <linux/regulator/consumer.h> struct gpio_usbdetect { struct platform_device *pdev; struct regulator *vin; int vbus_det_irq; int id_det_irq; int gpio; struct extcon_dev *extcon_dev; int vbus_state; bool id_state; }; static const unsigned int gpio_usb_extcon_table[] = { EXTCON_USB, EXTCON_USB_HOST, EXTCON_NONE, }; static irqreturn_t gpio_usbdetect_vbus_irq(int irq, void *data) { struct gpio_usbdetect *usb = data; union extcon_property_value val; usb->vbus_state = gpio_get_value(usb->gpio); if (usb->vbus_state) { dev_dbg(&usb->pdev->dev, "setting vbus notification\n"); val.intval = true; extcon_set_property(usb->extcon_dev, EXTCON_USB, EXTCON_PROP_USB_SS, val); extcon_set_cable_state_(usb->extcon_dev, EXTCON_USB, 1); } else { dev_dbg(&usb->pdev->dev, "setting vbus removed notification\n"); extcon_set_cable_state_(usb->extcon_dev, EXTCON_USB, 0); } return IRQ_HANDLED; } static irqreturn_t gpio_usbdetect_id_irq(int irq, void *data) { struct gpio_usbdetect *usb = data; int ret; ret = irq_get_irqchip_state(irq, IRQCHIP_STATE_LINE_LEVEL, &usb->id_state); if (ret < 0) { dev_err(&usb->pdev->dev, "unable to read ID IRQ LINE\n"); return IRQ_HANDLED; } return IRQ_WAKE_THREAD; } static irqreturn_t gpio_usbdetect_id_irq_thread(int irq, void *data) { struct gpio_usbdetect *usb = data; bool curr_id_state; static int prev_id_state = -EINVAL; union extcon_property_value val; curr_id_state = usb->id_state; if (curr_id_state == prev_id_state) { dev_dbg(&usb->pdev->dev, "no change in ID state\n"); return IRQ_HANDLED; } if (curr_id_state) { dev_dbg(&usb->pdev->dev, "stopping usb host\n"); extcon_set_cable_state_(usb->extcon_dev, EXTCON_USB_HOST, 0); enable_irq(usb->vbus_det_irq); } else { dev_dbg(&usb->pdev->dev, "starting usb HOST\n"); disable_irq(usb->vbus_det_irq); val.intval = true; extcon_set_property(usb->extcon_dev, EXTCON_USB_HOST, EXTCON_PROP_USB_SS, val); extcon_set_cable_state_(usb->extcon_dev, EXTCON_USB_HOST, 1); } prev_id_state = curr_id_state; return IRQ_HANDLED; } static const u32 gpio_usb_extcon_exclusive[] = {0x3, 0}; static int gpio_usbdetect_probe(struct platform_device *pdev) { struct gpio_usbdetect *usb; int rc; usb = devm_kzalloc(&pdev->dev, sizeof(*usb), GFP_KERNEL); if (!usb) return -ENOMEM; usb->pdev = pdev; usb->extcon_dev = devm_extcon_dev_allocate(&pdev->dev, gpio_usb_extcon_table); if (IS_ERR(usb->extcon_dev)) { dev_err(&pdev->dev, "failed to allocate a extcon device\n"); return PTR_ERR(usb->extcon_dev); } usb->extcon_dev->mutually_exclusive = gpio_usb_extcon_exclusive; rc = devm_extcon_dev_register(&pdev->dev, usb->extcon_dev); if (rc) { dev_err(&pdev->dev, "failed to register extcon device\n"); return rc; } rc = extcon_set_property_capability(usb->extcon_dev, EXTCON_USB, EXTCON_PROP_USB_SS); rc |= extcon_set_property_capability(usb->extcon_dev, EXTCON_USB_HOST, EXTCON_PROP_USB_SS); if (rc) { dev_err(&pdev->dev, "failed to register extcon props rc=%d\n", rc); return rc; } if (of_get_property(pdev->dev.of_node, "vin-supply", NULL)) { usb->vin = devm_regulator_get(&pdev->dev, "vin"); if (IS_ERR(usb->vin)) { dev_err(&pdev->dev, "Failed to get VIN regulator: %ld\n", PTR_ERR(usb->vin)); return PTR_ERR(usb->vin); } } if (usb->vin) { rc = regulator_enable(usb->vin); if (rc) { dev_err(&pdev->dev, "Failed to enable VIN regulator: %d\n", rc); return rc; } } usb->gpio = of_get_named_gpio(pdev->dev.of_node, "qcom,vbus-det-gpio", 0); if (usb->gpio < 0) { dev_err(&pdev->dev, "Failed to get gpio: %d\n", usb->gpio); rc = usb->gpio; goto error; } rc = gpio_request(usb->gpio, "vbus-det-gpio"); if (rc < 0) { dev_err(&pdev->dev, "Failed to request gpio: %d\n", rc); goto error; } usb->vbus_det_irq = gpio_to_irq(usb->gpio); if (usb->vbus_det_irq < 0) { dev_err(&pdev->dev, "get vbus_det_irq failed\n"); rc = usb->vbus_det_irq; goto error; } rc = devm_request_threaded_irq(&pdev->dev, usb->vbus_det_irq, NULL, gpio_usbdetect_vbus_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "vbus_det_irq", usb); if (rc) { dev_err(&pdev->dev, "request for vbus_det_irq failed: %d\n", rc); goto error; } usb->id_det_irq = platform_get_irq_byname(pdev, "pmic_id_irq"); if (usb->id_det_irq < 0) { dev_err(&pdev->dev, "get id_det_irq failed\n"); rc = usb->id_det_irq; goto error; } rc = devm_request_threaded_irq(&pdev->dev, usb->id_det_irq, gpio_usbdetect_id_irq, gpio_usbdetect_id_irq_thread, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "id_det_irq", usb); if (rc) { dev_err(&pdev->dev, "request for id_det_irq failed: %d\n", rc); goto error; } enable_irq_wake(usb->vbus_det_irq); enable_irq_wake(usb->id_det_irq); dev_set_drvdata(&pdev->dev, usb); if (usb->id_det_irq) { gpio_usbdetect_id_irq(usb->id_det_irq, usb); if (!usb->id_state) { gpio_usbdetect_id_irq_thread(usb->id_det_irq, usb); return 0; } } /* Read and report initial VBUS state */ gpio_usbdetect_vbus_irq(usb->vbus_det_irq, usb); return 0; error: if (usb->vin) regulator_disable(usb->vin); return rc; } static int gpio_usbdetect_remove(struct platform_device *pdev) { struct gpio_usbdetect *usb = dev_get_drvdata(&pdev->dev); disable_irq_wake(usb->vbus_det_irq); disable_irq(usb->vbus_det_irq); disable_irq_wake(usb->id_det_irq); disable_irq(usb->id_det_irq); if (usb->vin) regulator_disable(usb->vin); return 0; } static const struct of_device_id of_match_table[] = { { .compatible = "qcom,gpio-usbdetect", }, {} }; static struct platform_driver gpio_usbdetect_driver = { .driver = { .name = "qcom,gpio-usbdetect", .of_match_table = of_match_table, }, .probe = gpio_usbdetect_probe, .remove = gpio_usbdetect_remove, }; module_driver(gpio_usbdetect_driver, platform_driver_register, platform_driver_unregister); MODULE_DESCRIPTION("GPIO USB VBUS Detection driver"); MODULE_LICENSE("GPL v2"); Loading
Documentation/devicetree/bindings/platform/msm/gpio-usbdetect.txt 0 → 100644 +25 −0 Original line number Diff line number Diff line GPIO USB VBUS Detection Discrete USB VBUS detection circuitry can be connected to the AP or PMICs. Such circuits can be used to detect the when a USB cable is connected to an upstream port such as a standard host or a wall charger by detecting the presence of VBUS voltage. The GPIO can be configured to trigger an interrupt, and allow the software driver to in turn notify the USB subsytem using the power_supply framework. Required Properties: - compatible: must be "qcom,gpio-usbdetect" - qcom,vbus-det-gpio: GPIO from which VBUS detection can be read from. - interrupts: an interrupt triggered by the output of the detection circuit - interrupt-names: must be "vbus_det_irq" Optional Properties: - vin-supply: phandle to a regulator that powers this circuit, if needed Example: usb_detect { compatible = "qcom,gpio-usbdetect"; qcom,vbus-det-gpio = <&pm8084 2 0>; vin-supply = <&vbus_det_reg>; };
drivers/platform/msm/Kconfig +8 −0 Original line number Diff line number Diff line Loading @@ -152,6 +152,14 @@ config QPNP_REVID numbers in the kernel log along with the PMIC option status. The PMIC type is mapped to a QTI chip part number and logged as well. config GPIO_USB_DETECT tristate "GPIO-based USB VBUS Detection" depends on POWER_SUPPLY help This driver supports external USB VBUS detection circuitry whose output is connected to a GPIO. The driver in turn notifies the USB driver of VBUS presence/disconnection using the power_supply framework. config MSM_MHI_DEV tristate "Modem Device Interface Driver" Loading
drivers/platform/msm/Makefile +1 −0 Original line number Diff line number Diff line Loading @@ -7,6 +7,7 @@ obj-$(CONFIG_IPA3) += ipa/ obj-$(CONFIG_SPS) += sps/ obj-$(CONFIG_QPNP_COINCELL) += qpnp-coincell.o obj-$(CONFIG_QPNP_REVID) += qpnp-revid.o obj-$(CONFIG_GPIO_USB_DETECT) += gpio-usbdetect.o obj-$(CONFIG_EP_PCIE) += ep_pcie/ obj-$(CONFIG_MSM_MHI_DEV) += mhi_dev/ obj-$(CONFIG_USB_BAM) += usb_bam.o Loading
drivers/platform/msm/gpio-usbdetect.c 0 → 100644 +266 −0 Original line number Diff line number Diff line /* Copyright (c) 2013-2015, 2017-2018, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include <linux/module.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/err.h> #include <linux/errno.h> #include <linux/slab.h> #include <linux/of_platform.h> #include <linux/interrupt.h> #include <linux/of_gpio.h> #include <linux/gpio.h> #include <linux/extcon.h> #include <linux/regulator/consumer.h> struct gpio_usbdetect { struct platform_device *pdev; struct regulator *vin; int vbus_det_irq; int id_det_irq; int gpio; struct extcon_dev *extcon_dev; int vbus_state; bool id_state; }; static const unsigned int gpio_usb_extcon_table[] = { EXTCON_USB, EXTCON_USB_HOST, EXTCON_NONE, }; static irqreturn_t gpio_usbdetect_vbus_irq(int irq, void *data) { struct gpio_usbdetect *usb = data; union extcon_property_value val; usb->vbus_state = gpio_get_value(usb->gpio); if (usb->vbus_state) { dev_dbg(&usb->pdev->dev, "setting vbus notification\n"); val.intval = true; extcon_set_property(usb->extcon_dev, EXTCON_USB, EXTCON_PROP_USB_SS, val); extcon_set_cable_state_(usb->extcon_dev, EXTCON_USB, 1); } else { dev_dbg(&usb->pdev->dev, "setting vbus removed notification\n"); extcon_set_cable_state_(usb->extcon_dev, EXTCON_USB, 0); } return IRQ_HANDLED; } static irqreturn_t gpio_usbdetect_id_irq(int irq, void *data) { struct gpio_usbdetect *usb = data; int ret; ret = irq_get_irqchip_state(irq, IRQCHIP_STATE_LINE_LEVEL, &usb->id_state); if (ret < 0) { dev_err(&usb->pdev->dev, "unable to read ID IRQ LINE\n"); return IRQ_HANDLED; } return IRQ_WAKE_THREAD; } static irqreturn_t gpio_usbdetect_id_irq_thread(int irq, void *data) { struct gpio_usbdetect *usb = data; bool curr_id_state; static int prev_id_state = -EINVAL; union extcon_property_value val; curr_id_state = usb->id_state; if (curr_id_state == prev_id_state) { dev_dbg(&usb->pdev->dev, "no change in ID state\n"); return IRQ_HANDLED; } if (curr_id_state) { dev_dbg(&usb->pdev->dev, "stopping usb host\n"); extcon_set_cable_state_(usb->extcon_dev, EXTCON_USB_HOST, 0); enable_irq(usb->vbus_det_irq); } else { dev_dbg(&usb->pdev->dev, "starting usb HOST\n"); disable_irq(usb->vbus_det_irq); val.intval = true; extcon_set_property(usb->extcon_dev, EXTCON_USB_HOST, EXTCON_PROP_USB_SS, val); extcon_set_cable_state_(usb->extcon_dev, EXTCON_USB_HOST, 1); } prev_id_state = curr_id_state; return IRQ_HANDLED; } static const u32 gpio_usb_extcon_exclusive[] = {0x3, 0}; static int gpio_usbdetect_probe(struct platform_device *pdev) { struct gpio_usbdetect *usb; int rc; usb = devm_kzalloc(&pdev->dev, sizeof(*usb), GFP_KERNEL); if (!usb) return -ENOMEM; usb->pdev = pdev; usb->extcon_dev = devm_extcon_dev_allocate(&pdev->dev, gpio_usb_extcon_table); if (IS_ERR(usb->extcon_dev)) { dev_err(&pdev->dev, "failed to allocate a extcon device\n"); return PTR_ERR(usb->extcon_dev); } usb->extcon_dev->mutually_exclusive = gpio_usb_extcon_exclusive; rc = devm_extcon_dev_register(&pdev->dev, usb->extcon_dev); if (rc) { dev_err(&pdev->dev, "failed to register extcon device\n"); return rc; } rc = extcon_set_property_capability(usb->extcon_dev, EXTCON_USB, EXTCON_PROP_USB_SS); rc |= extcon_set_property_capability(usb->extcon_dev, EXTCON_USB_HOST, EXTCON_PROP_USB_SS); if (rc) { dev_err(&pdev->dev, "failed to register extcon props rc=%d\n", rc); return rc; } if (of_get_property(pdev->dev.of_node, "vin-supply", NULL)) { usb->vin = devm_regulator_get(&pdev->dev, "vin"); if (IS_ERR(usb->vin)) { dev_err(&pdev->dev, "Failed to get VIN regulator: %ld\n", PTR_ERR(usb->vin)); return PTR_ERR(usb->vin); } } if (usb->vin) { rc = regulator_enable(usb->vin); if (rc) { dev_err(&pdev->dev, "Failed to enable VIN regulator: %d\n", rc); return rc; } } usb->gpio = of_get_named_gpio(pdev->dev.of_node, "qcom,vbus-det-gpio", 0); if (usb->gpio < 0) { dev_err(&pdev->dev, "Failed to get gpio: %d\n", usb->gpio); rc = usb->gpio; goto error; } rc = gpio_request(usb->gpio, "vbus-det-gpio"); if (rc < 0) { dev_err(&pdev->dev, "Failed to request gpio: %d\n", rc); goto error; } usb->vbus_det_irq = gpio_to_irq(usb->gpio); if (usb->vbus_det_irq < 0) { dev_err(&pdev->dev, "get vbus_det_irq failed\n"); rc = usb->vbus_det_irq; goto error; } rc = devm_request_threaded_irq(&pdev->dev, usb->vbus_det_irq, NULL, gpio_usbdetect_vbus_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "vbus_det_irq", usb); if (rc) { dev_err(&pdev->dev, "request for vbus_det_irq failed: %d\n", rc); goto error; } usb->id_det_irq = platform_get_irq_byname(pdev, "pmic_id_irq"); if (usb->id_det_irq < 0) { dev_err(&pdev->dev, "get id_det_irq failed\n"); rc = usb->id_det_irq; goto error; } rc = devm_request_threaded_irq(&pdev->dev, usb->id_det_irq, gpio_usbdetect_id_irq, gpio_usbdetect_id_irq_thread, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "id_det_irq", usb); if (rc) { dev_err(&pdev->dev, "request for id_det_irq failed: %d\n", rc); goto error; } enable_irq_wake(usb->vbus_det_irq); enable_irq_wake(usb->id_det_irq); dev_set_drvdata(&pdev->dev, usb); if (usb->id_det_irq) { gpio_usbdetect_id_irq(usb->id_det_irq, usb); if (!usb->id_state) { gpio_usbdetect_id_irq_thread(usb->id_det_irq, usb); return 0; } } /* Read and report initial VBUS state */ gpio_usbdetect_vbus_irq(usb->vbus_det_irq, usb); return 0; error: if (usb->vin) regulator_disable(usb->vin); return rc; } static int gpio_usbdetect_remove(struct platform_device *pdev) { struct gpio_usbdetect *usb = dev_get_drvdata(&pdev->dev); disable_irq_wake(usb->vbus_det_irq); disable_irq(usb->vbus_det_irq); disable_irq_wake(usb->id_det_irq); disable_irq(usb->id_det_irq); if (usb->vin) regulator_disable(usb->vin); return 0; } static const struct of_device_id of_match_table[] = { { .compatible = "qcom,gpio-usbdetect", }, {} }; static struct platform_driver gpio_usbdetect_driver = { .driver = { .name = "qcom,gpio-usbdetect", .of_match_table = of_match_table, }, .probe = gpio_usbdetect_probe, .remove = gpio_usbdetect_remove, }; module_driver(gpio_usbdetect_driver, platform_driver_register, platform_driver_unregister); MODULE_DESCRIPTION("GPIO USB VBUS Detection driver"); MODULE_LICENSE("GPL v2");