diff options
author | Darius Augulis <augulis.darius@gmail.com> | 2009-01-21 08:19:19 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2009-03-24 19:20:29 -0400 |
commit | b633d28e2c5fbe1c8d163892644f57df04aa1421 (patch) | |
tree | ae4bb3f6086cc7f0dd2034fcb3be3d377945f602 /drivers/usb/gadget | |
parent | d24921a36df31332c32e1bb539671284d9e36bfa (diff) |
USB: imx_udc: Fix IMX UDC gadget general irq handling
Workaround of hw bug in IMX UDC.
This bug causes wrong handling of CFG_CHG interrupt.
Workaround is documented inline source code.
Signed-off-by: Darius Augulis <augulis.darius@gmail.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Diffstat (limited to 'drivers/usb/gadget')
-rw-r--r-- | drivers/usb/gadget/imx_udc.c | 158 | ||||
-rw-r--r-- | drivers/usb/gadget/imx_udc.h | 1 |
2 files changed, 94 insertions, 65 deletions
diff --git a/drivers/usb/gadget/imx_udc.c b/drivers/usb/gadget/imx_udc.c index e590464c3a50..8d3c6a960988 100644 --- a/drivers/usb/gadget/imx_udc.c +++ b/drivers/usb/gadget/imx_udc.c | |||
@@ -28,6 +28,7 @@ | |||
28 | #include <linux/dma-mapping.h> | 28 | #include <linux/dma-mapping.h> |
29 | #include <linux/clk.h> | 29 | #include <linux/clk.h> |
30 | #include <linux/delay.h> | 30 | #include <linux/delay.h> |
31 | #include <linux/timer.h> | ||
31 | 32 | ||
32 | #include <linux/usb/ch9.h> | 33 | #include <linux/usb/ch9.h> |
33 | #include <linux/usb/gadget.h> | 34 | #include <linux/usb/gadget.h> |
@@ -1013,70 +1014,32 @@ static void udc_stop_activity(struct imx_udc_struct *imx_usb, | |||
1013 | ******************************************************************************* | 1014 | ******************************************************************************* |
1014 | */ | 1015 | */ |
1015 | 1016 | ||
1016 | static irqreturn_t imx_udc_irq(int irq, void *dev) | 1017 | /* |
1018 | * Called when timer expires. | ||
1019 | * Timer is started when CFG_CHG is received. | ||
1020 | */ | ||
1021 | static void handle_config(unsigned long data) | ||
1017 | { | 1022 | { |
1018 | struct imx_udc_struct *imx_usb = dev; | 1023 | struct imx_udc_struct *imx_usb = (void *)data; |
1019 | struct usb_ctrlrequest u; | 1024 | struct usb_ctrlrequest u; |
1020 | int temp, cfg, intf, alt; | 1025 | int temp, cfg, intf, alt; |
1021 | int intr = __raw_readl(imx_usb->base + USB_INTR); | ||
1022 | |||
1023 | if (intr & (INTR_WAKEUP | INTR_SUSPEND | INTR_RESUME | INTR_RESET_START | ||
1024 | | INTR_RESET_STOP | INTR_CFG_CHG)) { | ||
1025 | dump_intr(__func__, intr, imx_usb->dev); | ||
1026 | dump_usb_stat(__func__, imx_usb); | ||
1027 | } | ||
1028 | |||
1029 | if (!imx_usb->driver) | ||
1030 | goto end_irq; | ||
1031 | |||
1032 | if (intr & INTR_WAKEUP) { | ||
1033 | if (imx_usb->gadget.speed == USB_SPEED_UNKNOWN | ||
1034 | && imx_usb->driver && imx_usb->driver->resume) | ||
1035 | imx_usb->driver->resume(&imx_usb->gadget); | ||
1036 | imx_usb->set_config = 0; | ||
1037 | imx_usb->gadget.speed = USB_SPEED_FULL; | ||
1038 | } | ||
1039 | |||
1040 | if (intr & INTR_SUSPEND) { | ||
1041 | if (imx_usb->gadget.speed != USB_SPEED_UNKNOWN | ||
1042 | && imx_usb->driver && imx_usb->driver->suspend) | ||
1043 | imx_usb->driver->suspend(&imx_usb->gadget); | ||
1044 | imx_usb->set_config = 0; | ||
1045 | imx_usb->gadget.speed = USB_SPEED_UNKNOWN; | ||
1046 | } | ||
1047 | 1026 | ||
1048 | if (intr & INTR_RESET_START) { | 1027 | local_irq_disable(); |
1049 | __raw_writel(intr, imx_usb->base + USB_INTR); | ||
1050 | udc_stop_activity(imx_usb, imx_usb->driver); | ||
1051 | imx_usb->set_config = 0; | ||
1052 | imx_usb->gadget.speed = USB_SPEED_UNKNOWN; | ||
1053 | } | ||
1054 | |||
1055 | if (intr & INTR_RESET_STOP) | ||
1056 | imx_usb->gadget.speed = USB_SPEED_FULL; | ||
1057 | 1028 | ||
1058 | if (intr & INTR_CFG_CHG) { | 1029 | temp = __raw_readl(imx_usb->base + USB_STAT); |
1059 | __raw_writel(INTR_CFG_CHG, imx_usb->base + USB_INTR); | 1030 | cfg = (temp & STAT_CFG) >> 5; |
1060 | temp = __raw_readl(imx_usb->base + USB_STAT); | 1031 | intf = (temp & STAT_INTF) >> 3; |
1061 | cfg = (temp & STAT_CFG) >> 5; | 1032 | alt = temp & STAT_ALTSET; |
1062 | intf = (temp & STAT_INTF) >> 3; | ||
1063 | alt = temp & STAT_ALTSET; | ||
1064 | 1033 | ||
1065 | D_REQ(imx_usb->dev, | 1034 | D_REQ(imx_usb->dev, |
1066 | "<%s> orig config C=%d, I=%d, A=%d / " | 1035 | "<%s> orig config C=%d, I=%d, A=%d / " |
1067 | "req config C=%d, I=%d, A=%d\n", | 1036 | "req config C=%d, I=%d, A=%d\n", |
1068 | __func__, imx_usb->cfg, imx_usb->intf, imx_usb->alt, | 1037 | __func__, imx_usb->cfg, imx_usb->intf, imx_usb->alt, |
1069 | cfg, intf, alt); | 1038 | cfg, intf, alt); |
1070 | 1039 | ||
1071 | if (cfg != 1 && cfg != 2) | 1040 | if (cfg == 1 || cfg == 2) { |
1072 | goto end_irq; | ||
1073 | 1041 | ||
1074 | imx_usb->set_config = 0; | ||
1075 | |||
1076 | /* Config setup */ | ||
1077 | if (imx_usb->cfg != cfg) { | 1042 | if (imx_usb->cfg != cfg) { |
1078 | D_REQ(imx_usb->dev, | ||
1079 | "<%s> Change config start\n", __func__); | ||
1080 | u.bRequest = USB_REQ_SET_CONFIGURATION; | 1043 | u.bRequest = USB_REQ_SET_CONFIGURATION; |
1081 | u.bRequestType = USB_DIR_OUT | | 1044 | u.bRequestType = USB_DIR_OUT | |
1082 | USB_TYPE_STANDARD | | 1045 | USB_TYPE_STANDARD | |
@@ -1085,16 +1048,10 @@ static irqreturn_t imx_udc_irq(int irq, void *dev) | |||
1085 | u.wIndex = 0; | 1048 | u.wIndex = 0; |
1086 | u.wLength = 0; | 1049 | u.wLength = 0; |
1087 | imx_usb->cfg = cfg; | 1050 | imx_usb->cfg = cfg; |
1088 | imx_usb->set_config = 1; | ||
1089 | imx_usb->driver->setup(&imx_usb->gadget, &u); | 1051 | imx_usb->driver->setup(&imx_usb->gadget, &u); |
1090 | imx_usb->set_config = 0; | ||
1091 | D_REQ(imx_usb->dev, | ||
1092 | "<%s> Change config done\n", __func__); | ||
1093 | 1052 | ||
1094 | } | 1053 | } |
1095 | if (imx_usb->intf != intf || imx_usb->alt != alt) { | 1054 | if (imx_usb->intf != intf || imx_usb->alt != alt) { |
1096 | D_REQ(imx_usb->dev, | ||
1097 | "<%s> Change interface start\n", __func__); | ||
1098 | u.bRequest = USB_REQ_SET_INTERFACE; | 1055 | u.bRequest = USB_REQ_SET_INTERFACE; |
1099 | u.bRequestType = USB_DIR_OUT | | 1056 | u.bRequestType = USB_DIR_OUT | |
1100 | USB_TYPE_STANDARD | | 1057 | USB_TYPE_STANDARD | |
@@ -1104,14 +1061,30 @@ static irqreturn_t imx_udc_irq(int irq, void *dev) | |||
1104 | u.wLength = 0; | 1061 | u.wLength = 0; |
1105 | imx_usb->intf = intf; | 1062 | imx_usb->intf = intf; |
1106 | imx_usb->alt = alt; | 1063 | imx_usb->alt = alt; |
1107 | imx_usb->set_config = 1; | ||
1108 | imx_usb->driver->setup(&imx_usb->gadget, &u); | 1064 | imx_usb->driver->setup(&imx_usb->gadget, &u); |
1109 | imx_usb->set_config = 0; | ||
1110 | D_REQ(imx_usb->dev, | ||
1111 | "<%s> Change interface done\n", __func__); | ||
1112 | } | 1065 | } |
1113 | } | 1066 | } |
1114 | 1067 | ||
1068 | imx_usb->set_config = 0; | ||
1069 | |||
1070 | local_irq_enable(); | ||
1071 | } | ||
1072 | |||
1073 | static irqreturn_t imx_udc_irq(int irq, void *dev) | ||
1074 | { | ||
1075 | struct imx_udc_struct *imx_usb = dev; | ||
1076 | int intr = __raw_readl(imx_usb->base + USB_INTR); | ||
1077 | int temp; | ||
1078 | |||
1079 | if (intr & (INTR_WAKEUP | INTR_SUSPEND | INTR_RESUME | INTR_RESET_START | ||
1080 | | INTR_RESET_STOP | INTR_CFG_CHG)) { | ||
1081 | dump_intr(__func__, intr, imx_usb->dev); | ||
1082 | dump_usb_stat(__func__, imx_usb); | ||
1083 | } | ||
1084 | |||
1085 | if (!imx_usb->driver) | ||
1086 | goto end_irq; | ||
1087 | |||
1115 | if (intr & INTR_SOF) { | 1088 | if (intr & INTR_SOF) { |
1116 | /* Copy from Freescale BSP. | 1089 | /* Copy from Freescale BSP. |
1117 | We must enable SOF intr and set CMDOVER. | 1090 | We must enable SOF intr and set CMDOVER. |
@@ -1125,6 +1098,55 @@ static irqreturn_t imx_udc_irq(int irq, void *dev) | |||
1125 | } | 1098 | } |
1126 | } | 1099 | } |
1127 | 1100 | ||
1101 | if (intr & INTR_CFG_CHG) { | ||
1102 | /* A workaround of serious IMX UDC bug. | ||
1103 | Handling of CFG_CHG should be delayed for some time, because | ||
1104 | IMX does not NACK the host when CFG_CHG interrupt is pending. | ||
1105 | There is no time to handle current CFG_CHG | ||
1106 | if next CFG_CHG or SETUP packed is send immediately. | ||
1107 | We have to clear CFG_CHG, start the timer and | ||
1108 | NACK the host by setting CTRL_CMDOVER | ||
1109 | if it sends any SETUP packet. | ||
1110 | When timer expires, handler is called to handle configuration | ||
1111 | changes. While CFG_CHG is not handled (set_config=1), | ||
1112 | we must NACK the host to every SETUP packed. | ||
1113 | This delay prevents from going out of sync with host. | ||
1114 | */ | ||
1115 | __raw_writel(INTR_CFG_CHG, imx_usb->base + USB_INTR); | ||
1116 | imx_usb->set_config = 1; | ||
1117 | mod_timer(&imx_usb->timer, jiffies + 5); | ||
1118 | goto end_irq; | ||
1119 | } | ||
1120 | |||
1121 | if (intr & INTR_WAKEUP) { | ||
1122 | if (imx_usb->gadget.speed == USB_SPEED_UNKNOWN | ||
1123 | && imx_usb->driver && imx_usb->driver->resume) | ||
1124 | imx_usb->driver->resume(&imx_usb->gadget); | ||
1125 | imx_usb->set_config = 0; | ||
1126 | del_timer(&imx_usb->timer); | ||
1127 | imx_usb->gadget.speed = USB_SPEED_FULL; | ||
1128 | } | ||
1129 | |||
1130 | if (intr & INTR_SUSPEND) { | ||
1131 | if (imx_usb->gadget.speed != USB_SPEED_UNKNOWN | ||
1132 | && imx_usb->driver && imx_usb->driver->suspend) | ||
1133 | imx_usb->driver->suspend(&imx_usb->gadget); | ||
1134 | imx_usb->set_config = 0; | ||
1135 | del_timer(&imx_usb->timer); | ||
1136 | imx_usb->gadget.speed = USB_SPEED_UNKNOWN; | ||
1137 | } | ||
1138 | |||
1139 | if (intr & INTR_RESET_START) { | ||
1140 | __raw_writel(intr, imx_usb->base + USB_INTR); | ||
1141 | udc_stop_activity(imx_usb, imx_usb->driver); | ||
1142 | imx_usb->set_config = 0; | ||
1143 | del_timer(&imx_usb->timer); | ||
1144 | imx_usb->gadget.speed = USB_SPEED_UNKNOWN; | ||
1145 | } | ||
1146 | |||
1147 | if (intr & INTR_RESET_STOP) | ||
1148 | imx_usb->gadget.speed = USB_SPEED_FULL; | ||
1149 | |||
1128 | end_irq: | 1150 | end_irq: |
1129 | __raw_writel(intr, imx_usb->base + USB_INTR); | 1151 | __raw_writel(intr, imx_usb->base + USB_INTR); |
1130 | return IRQ_HANDLED; | 1152 | return IRQ_HANDLED; |
@@ -1342,6 +1364,7 @@ int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) | |||
1342 | 1364 | ||
1343 | udc_stop_activity(imx_usb, driver); | 1365 | udc_stop_activity(imx_usb, driver); |
1344 | imx_udc_disable(imx_usb); | 1366 | imx_udc_disable(imx_usb); |
1367 | del_timer(&imx_usb->timer); | ||
1345 | 1368 | ||
1346 | driver->unbind(&imx_usb->gadget); | 1369 | driver->unbind(&imx_usb->gadget); |
1347 | imx_usb->gadget.dev.driver = NULL; | 1370 | imx_usb->gadget.dev.driver = NULL; |
@@ -1459,6 +1482,10 @@ static int __init imx_udc_probe(struct platform_device *pdev) | |||
1459 | usb_init_data(imx_usb); | 1482 | usb_init_data(imx_usb); |
1460 | imx_udc_init(imx_usb); | 1483 | imx_udc_init(imx_usb); |
1461 | 1484 | ||
1485 | init_timer(&imx_usb->timer); | ||
1486 | imx_usb->timer.function = handle_config; | ||
1487 | imx_usb->timer.data = (unsigned long)imx_usb; | ||
1488 | |||
1462 | return 0; | 1489 | return 0; |
1463 | 1490 | ||
1464 | fail3: | 1491 | fail3: |
@@ -1481,6 +1508,7 @@ static int __exit imx_udc_remove(struct platform_device *pdev) | |||
1481 | int i; | 1508 | int i; |
1482 | 1509 | ||
1483 | imx_udc_disable(imx_usb); | 1510 | imx_udc_disable(imx_usb); |
1511 | del_timer(&imx_usb->timer); | ||
1484 | 1512 | ||
1485 | for (i = 0; i < IMX_USB_NB_EP + 1; i++) | 1513 | for (i = 0; i < IMX_USB_NB_EP + 1; i++) |
1486 | free_irq(imx_usb->usbd_int[i], imx_usb); | 1514 | free_irq(imx_usb->usbd_int[i], imx_usb); |
diff --git a/drivers/usb/gadget/imx_udc.h b/drivers/usb/gadget/imx_udc.h index 6b0b1e3d6fc7..b48ad59603d1 100644 --- a/drivers/usb/gadget/imx_udc.h +++ b/drivers/usb/gadget/imx_udc.h | |||
@@ -59,6 +59,7 @@ struct imx_udc_struct { | |||
59 | struct device *dev; | 59 | struct device *dev; |
60 | struct imx_ep_struct imx_ep[IMX_USB_NB_EP]; | 60 | struct imx_ep_struct imx_ep[IMX_USB_NB_EP]; |
61 | struct clk *clk; | 61 | struct clk *clk; |
62 | struct timer_list timer; | ||
62 | enum ep0_state ep0state; | 63 | enum ep0_state ep0state; |
63 | struct resource *res; | 64 | struct resource *res; |
64 | void __iomem *base; | 65 | void __iomem *base; |