Commit 3ecb3e09 authored by Ivan T. Ivanov's avatar Ivan T. Ivanov Committed by Peter Chen

usb: chipidea: Use extcon framework for VBUS and ID detect

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: default avatarIvan T. Ivanov <ivan.ivanov@linaro.org>
Signed-off-by: default avatarPeter Chen <peter.chen@freescale.com>
parent ffa23666
...@@ -46,6 +46,11 @@ Optional properties: ...@@ -46,6 +46,11 @@ Optional properties:
(4 bytes), This register represents the maximum length of a the burst (4 bytes), This register represents the maximum length of a the burst
in 32-bit words while moving data from the USB bus to system memory, in 32-bit words while moving data from the USB bus to system memory,
changing this value takes effect only the SBUSCFG.AHBBRST is 0. changing this value takes effect only the SBUSCFG.AHBBRST is 0.
- 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.
Example: Example:
...@@ -62,4 +67,5 @@ Example: ...@@ -62,4 +67,5 @@ Example:
ahb-burst-config = <0x0>; ahb-burst-config = <0x0>;
tx-burst-size-dword = <0x10>; /* 64 bytes */ tx-burst-size-dword = <0x10>; /* 64 bytes */
rx-burst-size-dword = <0x10>; rx-burst-size-dword = <0x10>;
extcon = <0>, <&usb_id>;
}; };
config USB_CHIPIDEA config USB_CHIPIDEA
tristate "ChipIdea Highspeed Dual Role Controller" 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 depends on ((USB_EHCI_HCD && USB_GADGET) || (USB_EHCI_HCD && !USB_GADGET) || (!USB_EHCI_HCD && USB_GADGET)) && HAS_DMA
select EXTCON
help help
Say Y here if your system has a dual role high speed USB Say Y here if your system has a dual role high speed USB
controller based on ChipIdea silicon IP. Currently, only the controller based on ChipIdea silicon IP. Currently, only the
......
...@@ -47,6 +47,7 @@ ...@@ -47,6 +47,7 @@
#include <linux/delay.h> #include <linux/delay.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/dma-mapping.h> #include <linux/dma-mapping.h>
#include <linux/extcon.h>
#include <linux/phy/phy.h> #include <linux/phy/phy.h>
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/module.h> #include <linux/module.h>
...@@ -602,9 +603,45 @@ static irqreturn_t ci_irq(int irq, void *data) ...@@ -602,9 +603,45 @@ static irqreturn_t ci_irq(int irq, void *data)
return ret; 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, static int ci_get_platdata(struct device *dev,
struct ci_hdrc_platform_data *platdata) struct ci_hdrc_platform_data *platdata)
{ {
struct extcon_dev *ext_vbus, *ext_id;
struct ci_hdrc_cable *cable;
int ret; int ret;
if (!platdata->phy_mode) if (!platdata->phy_mode)
...@@ -695,9 +732,91 @@ static int ci_get_platdata(struct device *dev, ...@@ -695,9 +732,91 @@ static int ci_get_platdata(struct device *dev,
platdata->flags |= CI_HDRC_OVERRIDE_RX_BURST; platdata->flags |= CI_HDRC_OVERRIDE_RX_BURST;
} }
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, EXTCON_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, EXTCON_USB_HOST);
if (ret)
cable->state = false;
else
cable->state = true;
}
return 0; 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_notifier(id->edev, EXTCON_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_notifier(vbus->edev, EXTCON_USB,
&vbus->nb);
if (ret < 0) {
extcon_unregister_notifier(id->edev, EXTCON_USB_HOST,
&id->nb);
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_notifier(cable->edev, EXTCON_USB_HOST,
&cable->nb);
cable = &ci->platdata->vbus_extcon;
if (!IS_ERR(cable->edev))
extcon_unregister_notifier(cable->edev, EXTCON_USB, &cable->nb);
}
static DEFINE_IDA(ci_ida); static DEFINE_IDA(ci_ida);
struct platform_device *ci_hdrc_add_device(struct device *dev, struct platform_device *ci_hdrc_add_device(struct device *dev,
...@@ -921,6 +1040,10 @@ static int ci_hdrc_probe(struct platform_device *pdev) ...@@ -921,6 +1040,10 @@ static int ci_hdrc_probe(struct platform_device *pdev)
if (ret) if (ret)
goto stop; goto stop;
ret = ci_extcon_register(ci);
if (ret)
goto stop;
if (ci->supports_runtime_pm) { if (ci->supports_runtime_pm) {
pm_runtime_set_active(&pdev->dev); pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev); pm_runtime_enable(&pdev->dev);
...@@ -938,6 +1061,7 @@ static int ci_hdrc_probe(struct platform_device *pdev) ...@@ -938,6 +1061,7 @@ static int ci_hdrc_probe(struct platform_device *pdev)
if (!ret) if (!ret)
return 0; return 0;
ci_extcon_unregister(ci);
stop: stop:
ci_role_destroy(ci); ci_role_destroy(ci);
deinit_phy: deinit_phy:
...@@ -957,6 +1081,7 @@ static int ci_hdrc_remove(struct platform_device *pdev) ...@@ -957,6 +1081,7 @@ static int ci_hdrc_remove(struct platform_device *pdev)
} }
dbg_remove_files(ci); dbg_remove_files(ci);
ci_extcon_unregister(ci);
ci_role_destroy(ci); ci_role_destroy(ci);
ci_hdrc_enter_lpm(ci, true); ci_hdrc_enter_lpm(ci, true);
ci_usb_phy_exit(ci); ci_usb_phy_exit(ci);
......
...@@ -30,7 +30,44 @@ ...@@ -30,7 +30,44 @@
*/ */
u32 hw_read_otgsc(struct ci_hdrc *ci, u32 mask) 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 register value
*/
cable = &ci->platdata->vbus_extcon;
if (!IS_ERR(cable->edev)) {
if (cable->changed)
val |= OTGSC_BSVIS;
else
val &= ~OTGSC_BSVIS;
cable->changed = false;
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;
cable->changed = false;
if (cable->state)
val |= OTGSC_ID;
else
val &= ~OTGSC_ID;
}
return val;
} }
/** /**
......
...@@ -5,9 +5,28 @@ ...@@ -5,9 +5,28 @@
#ifndef __LINUX_USB_CHIPIDEA_H #ifndef __LINUX_USB_CHIPIDEA_H
#define __LINUX_USB_CHIPIDEA_H #define __LINUX_USB_CHIPIDEA_H
#include <linux/extcon.h>
#include <linux/usb/otg.h> #include <linux/usb/otg.h>
struct ci_hdrc; 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 ci_hdrc_platform_data { struct ci_hdrc_platform_data {
const char *name; const char *name;
/* offset of the capability registers */ /* offset of the capability registers */
...@@ -48,6 +67,10 @@ struct ci_hdrc_platform_data { ...@@ -48,6 +67,10 @@ struct ci_hdrc_platform_data {
u32 ahb_burst_config; u32 ahb_burst_config;
u32 tx_burst_size; u32 tx_burst_size;
u32 rx_burst_size; u32 rx_burst_size;
/* 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 */ /* Default offset of capability registers */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment