diff options
Diffstat (limited to 'drivers/usb/gadget/udc/renesas_usb3.c')
-rw-r--r-- | drivers/usb/gadget/udc/renesas_usb3.c | 164 |
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); |