summaryrefslogtreecommitdiffstats
path: root/drivers/usb/gadget/udc/renesas_usb3.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/gadget/udc/renesas_usb3.c')
-rw-r--r--drivers/usb/gadget/udc/renesas_usb3.c164
1 files changed, 159 insertions, 5 deletions
diff --git a/drivers/usb/gadget/udc/renesas_usb3.c b/drivers/usb/gadget/udc/renesas_usb3.c
index 2218f91e92a6..5a2d845fb1a6 100644
--- a/drivers/usb/gadget/udc/renesas_usb3.c
+++ b/drivers/usb/gadget/udc/renesas_usb3.c
@@ -10,6 +10,7 @@
#include <linux/delay.h>
#include <linux/err.h>
+#include <linux/extcon.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/module.h>
@@ -37,6 +38,9 @@
#define USB3_USB_INT_ENA_2 0x22c
#define USB3_STUP_DAT_0 0x230
#define USB3_STUP_DAT_1 0x234
+#define USB3_USB_OTG_STA 0x268
+#define USB3_USB_OTG_INT_STA 0x26c
+#define USB3_USB_OTG_INT_ENA 0x270
#define USB3_P0_MOD 0x280
#define USB3_P0_CON 0x288
#define USB3_P0_STA 0x28c
@@ -124,6 +128,9 @@
/* USB_INT_ENA_2 and USB_INT_STA_2 */
#define USB_INT_2_PIPE(n) BIT(n)
+/* USB_OTG_STA, USB_OTG_INT_STA and USB_OTG_INT_ENA */
+#define USB_OTG_IDMON BIT(4)
+
/* P0_MOD */
#define P0_MOD_DIR BIT(6)
@@ -257,6 +264,8 @@ struct renesas_usb3 {
struct usb_gadget gadget;
struct usb_gadget_driver *driver;
+ struct extcon_dev *extcon;
+ struct work_struct extcon_work;
struct renesas_usb3_ep *usb3_ep;
int num_usb3_eps;
@@ -269,6 +278,8 @@ struct renesas_usb3 {
u8 ep0_buf[USB3_EP0_BUF_SIZE];
bool softconnect;
bool workaround_for_vbus;
+ bool extcon_host; /* check id and set EXTCON_USB_HOST */
+ bool extcon_usb; /* check vbus and set EXTCON_USB */
};
#define gadget_to_renesas_usb3(_gadget) \
@@ -332,6 +343,15 @@ static int usb3_wait(struct renesas_usb3 *usb3, u32 reg, u32 mask,
return -EBUSY;
}
+static void renesas_usb3_extcon_work(struct work_struct *work)
+{
+ struct renesas_usb3 *usb3 = container_of(work, struct renesas_usb3,
+ extcon_work);
+
+ extcon_set_state_sync(usb3->extcon, EXTCON_USB_HOST, usb3->extcon_host);
+ extcon_set_state_sync(usb3->extcon, EXTCON_USB, usb3->extcon_usb);
+}
+
static void usb3_enable_irq_1(struct renesas_usb3 *usb3, u32 bits)
{
usb3_set_bit(usb3, bits, USB3_USB_INT_ENA_1);
@@ -352,6 +372,11 @@ static void usb3_disable_pipe_irq(struct renesas_usb3 *usb3, int num)
usb3_clear_bit(usb3, USB_INT_2_PIPE(num), USB3_USB_INT_ENA_2);
}
+static bool usb3_is_host(struct renesas_usb3 *usb3)
+{
+ return !(usb3_read(usb3, USB3_DRD_CON) & DRD_CON_PERI_CON);
+}
+
static void usb3_init_axi_bridge(struct renesas_usb3 *usb3)
{
/* Set AXI_INT */
@@ -362,10 +387,6 @@ static void usb3_init_axi_bridge(struct renesas_usb3 *usb3)
static void usb3_init_epc_registers(struct renesas_usb3 *usb3)
{
- /* FIXME: How to change host / peripheral mode as well? */
- usb3_set_bit(usb3, DRD_CON_PERI_CON, USB3_DRD_CON);
- usb3_clear_bit(usb3, DRD_CON_VBOUT, USB3_DRD_CON);
-
usb3_write(usb3, ~0, USB3_USB_INT_STA_1);
usb3_enable_irq_1(usb3, USB_INT_1_VBUS_CNG);
}
@@ -531,18 +552,70 @@ static void usb3_check_vbus(struct renesas_usb3 *usb3)
if (usb3->workaround_for_vbus) {
usb3_connect(usb3);
} else {
- if (usb3_read(usb3, USB3_USB_STA) & USB_STA_VBUS_STA)
+ usb3->extcon_usb = !!(usb3_read(usb3, USB3_USB_STA) &
+ USB_STA_VBUS_STA);
+ if (usb3->extcon_usb)
usb3_connect(usb3);
else
usb3_disconnect(usb3);
+
+ schedule_work(&usb3->extcon_work);
}
}
+static void usb3_set_mode(struct renesas_usb3 *usb3, bool host)
+{
+ if (host)
+ usb3_clear_bit(usb3, DRD_CON_PERI_CON, USB3_DRD_CON);
+ else
+ usb3_set_bit(usb3, DRD_CON_PERI_CON, USB3_DRD_CON);
+}
+
+static void usb3_vbus_out(struct renesas_usb3 *usb3, bool enable)
+{
+ if (enable)
+ usb3_set_bit(usb3, DRD_CON_VBOUT, USB3_DRD_CON);
+ else
+ usb3_clear_bit(usb3, DRD_CON_VBOUT, USB3_DRD_CON);
+}
+
+static void usb3_mode_config(struct renesas_usb3 *usb3, bool host, bool a_dev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&usb3->lock, flags);
+ usb3_set_mode(usb3, host);
+ usb3_vbus_out(usb3, a_dev);
+ if (!host && a_dev) /* for A-Peripheral */
+ usb3_connect(usb3);
+ spin_unlock_irqrestore(&usb3->lock, flags);
+}
+
+static bool usb3_is_a_device(struct renesas_usb3 *usb3)
+{
+ return !(usb3_read(usb3, USB3_USB_OTG_STA) & USB_OTG_IDMON);
+}
+
+static void usb3_check_id(struct renesas_usb3 *usb3)
+{
+ usb3->extcon_host = usb3_is_a_device(usb3);
+
+ if (usb3->extcon_host)
+ usb3_mode_config(usb3, true, true);
+ else
+ usb3_mode_config(usb3, false, false);
+
+ schedule_work(&usb3->extcon_work);
+}
+
static void renesas_usb3_init_controller(struct renesas_usb3 *usb3)
{
usb3_init_axi_bridge(usb3);
usb3_init_epc_registers(usb3);
+ usb3_write(usb3, USB_OTG_IDMON, USB3_USB_OTG_INT_STA);
+ usb3_write(usb3, USB_OTG_IDMON, USB3_USB_OTG_INT_ENA);
+ usb3_check_id(usb3);
usb3_check_vbus(usb3);
}
@@ -551,6 +624,7 @@ static void renesas_usb3_stop_controller(struct renesas_usb3 *usb3)
usb3_disconnect(usb3);
usb3_write(usb3, 0, USB3_P0_INT_ENA);
usb3_write(usb3, 0, USB3_PN_INT_ENA);
+ usb3_write(usb3, 0, USB3_USB_OTG_INT_ENA);
usb3_write(usb3, 0, USB3_USB_INT_ENA_1);
usb3_write(usb3, 0, USB3_USB_INT_ENA_2);
usb3_write(usb3, 0, USB3_AXI_INT_ENA);
@@ -1474,10 +1548,22 @@ static void usb3_irq_epc_int_2(struct renesas_usb3 *usb3, u32 int_sta_2)
}
}
+static void usb3_irq_idmon_change(struct renesas_usb3 *usb3)
+{
+ usb3_check_id(usb3);
+}
+
+static void usb3_irq_otg_int(struct renesas_usb3 *usb3, u32 otg_int_sta)
+{
+ if (otg_int_sta & USB_OTG_IDMON)
+ usb3_irq_idmon_change(usb3);
+}
+
static void usb3_irq_epc(struct renesas_usb3 *usb3)
{
u32 int_sta_1 = usb3_read(usb3, USB3_USB_INT_STA_1);
u32 int_sta_2 = usb3_read(usb3, USB3_USB_INT_STA_2);
+ u32 otg_int_sta = usb3_read(usb3, USB3_USB_OTG_INT_STA);
int_sta_1 &= usb3_read(usb3, USB3_USB_INT_ENA_1);
if (int_sta_1) {
@@ -1488,6 +1574,12 @@ static void usb3_irq_epc(struct renesas_usb3 *usb3)
int_sta_2 &= usb3_read(usb3, USB3_USB_INT_ENA_2);
if (int_sta_2)
usb3_irq_epc_int_2(usb3, int_sta_2);
+
+ otg_int_sta &= usb3_read(usb3, USB3_USB_OTG_INT_ENA);
+ if (otg_int_sta) {
+ usb3_write(usb3, otg_int_sta, USB3_USB_OTG_INT_STA);
+ usb3_irq_otg_int(usb3, otg_int_sta);
+ }
}
static irqreturn_t renesas_usb3_irq(int irq, void *_usb3)
@@ -1756,11 +1848,49 @@ static const struct usb_gadget_ops renesas_usb3_gadget_ops = {
.set_selfpowered = renesas_usb3_set_selfpowered,
};
+static ssize_t role_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct renesas_usb3 *usb3 = dev_get_drvdata(dev);
+ bool new_mode_is_host;
+
+ if (!usb3->driver)
+ return -ENODEV;
+
+ if (!strncmp(buf, "host", strlen("host")))
+ new_mode_is_host = true;
+ else if (!strncmp(buf, "peripheral", strlen("peripheral")))
+ new_mode_is_host = false;
+ else
+ return -EINVAL;
+
+ if (new_mode_is_host == usb3_is_host(usb3))
+ return -EINVAL;
+
+ usb3_mode_config(usb3, new_mode_is_host, usb3_is_a_device(usb3));
+
+ return count;
+}
+
+static ssize_t role_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct renesas_usb3 *usb3 = dev_get_drvdata(dev);
+
+ if (!usb3->driver)
+ return -ENODEV;
+
+ return sprintf(buf, "%s\n", usb3_is_host(usb3) ? "host" : "peripheral");
+}
+static DEVICE_ATTR_RW(role);
+
/*------- platform_driver ------------------------------------------------*/
static int renesas_usb3_remove(struct platform_device *pdev)
{
struct renesas_usb3 *usb3 = platform_get_drvdata(pdev);
+ device_remove_file(&pdev->dev, &dev_attr_role);
+
pm_runtime_put(&pdev->dev);
pm_runtime_disable(&pdev->dev);
@@ -1894,6 +2024,12 @@ static const struct of_device_id usb3_of_match[] = {
};
MODULE_DEVICE_TABLE(of, usb3_of_match);
+static const unsigned int renesas_usb3_cable[] = {
+ EXTCON_USB,
+ EXTCON_USB_HOST,
+ EXTCON_NONE,
+};
+
static int renesas_usb3_probe(struct platform_device *pdev)
{
struct renesas_usb3 *usb3;
@@ -1937,6 +2073,17 @@ static int renesas_usb3_probe(struct platform_device *pdev)
if (ret < 0)
return ret;
+ INIT_WORK(&usb3->extcon_work, renesas_usb3_extcon_work);
+ usb3->extcon = devm_extcon_dev_allocate(&pdev->dev, renesas_usb3_cable);
+ if (IS_ERR(usb3->extcon))
+ return PTR_ERR(usb3->extcon);
+
+ ret = devm_extcon_dev_register(&pdev->dev, usb3->extcon);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to register extcon\n");
+ return ret;
+ }
+
/* for ep0 handling */
usb3->ep0_req = __renesas_usb3_ep_alloc_request(GFP_KERNEL);
if (!usb3->ep0_req)
@@ -1946,6 +2093,10 @@ static int renesas_usb3_probe(struct platform_device *pdev)
if (ret < 0)
goto err_add_udc;
+ ret = device_create_file(&pdev->dev, &dev_attr_role);
+ if (ret < 0)
+ goto err_dev_create;
+
usb3->workaround_for_vbus = priv->workaround_for_vbus;
pm_runtime_enable(&pdev->dev);
@@ -1955,6 +2106,9 @@ static int renesas_usb3_probe(struct platform_device *pdev)
return 0;
+err_dev_create:
+ usb_del_gadget_udc(&usb3->gadget);
+
err_add_udc:
__renesas_usb3_ep_free_request(usb3->ep0_req);