-
Notifications
You must be signed in to change notification settings - Fork 1
/
wakeup.patch
160 lines (152 loc) · 5.02 KB
/
wakeup.patch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c
index 0909b088a284..31ff5348020c 100644
--- a/drivers/usb/dwc2/gadget.c
+++ b/drivers/usb/dwc2/gadget.c
@@ -4763,6 +4763,78 @@ static void dwc2_gadget_set_speed(struct usb_gadget *g, enum usb_device_speed sp
spin_unlock_irqrestore(&hsotg->lock, flags);
}
+/**
+ * dwc2_hsotg_wakeup - send wakeup signal to the host
+ * @gadget: The usb gadget state
+ *
+ * If the gadget is in device mode and in the L1 or L2 state,
+ * it sends a wakeup signal to the host.
+ */
+static int dwc2_hsotg_wakeup(struct usb_gadget *gadget)
+{
+ struct dwc2_hsotg *hsotg = to_hsotg(gadget);
+ int ret = -1;
+ unsigned long flags;
+
+ spin_lock_irqsave(&hsotg->lock, flags);
+
+ if (!hsotg->remote_wakeup_allowed) {
+ dev_dbg(hsotg->dev,
+ "wakeup: signalling skipped: is not allowed by host\n");
+ goto skip;
+ }
+ if (hsotg->lx_state != DWC2_L1 && hsotg->lx_state != DWC2_L2) {
+ dev_dbg(hsotg->dev,
+ "wakeup: signalling skipped: gadget not in L1/L2 state: %d\n", hsotg->lx_state);
+ goto skip;
+ }
+ if (!dwc2_is_device_mode(hsotg)) {
+ dev_dbg(hsotg->dev,
+ "wakeup: signalling skipped: gadget not in device mode\n");
+ goto skip;
+ }
+
+ /*if (hsotg->in_ppd) {
+ if (dwc2_exit_partial_power_down(hsotg, 1, true))
+ dev_err(hsotg->dev, "wakeup: exit partial_power_down failed\n");
+ call_gadget(hsotg, resume);
+ }*/
+ if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended) {
+ u32 pcgctl;
+
+ dev_dbg(hsotg->dev, "wakeup: exiting device clock gating\n");
+
+ /* Clear the Gate hclk. */
+ pcgctl = dwc2_readl(hsotg, PCGCTL);
+ pcgctl &= ~PCGCTL_GATEHCLK;
+ dwc2_writel(hsotg, pcgctl, PCGCTL);
+ udelay(5);
+
+ /* Phy Clock bit. */
+ pcgctl = dwc2_readl(hsotg, PCGCTL);
+ pcgctl &= ~PCGCTL_STOPPCLK;
+ dwc2_writel(hsotg, pcgctl, PCGCTL);
+ udelay(5);
+
+ hsotg->bus_suspended = false;
+ }
+
+ dev_dbg(hsotg->dev, "wakeup: sending signal to the host");
+
+ dwc2_set_bit(hsotg, DCTL, DCTL_RMTWKUPSIG);
+ mdelay(10);
+ dwc2_clear_bit(hsotg, DCTL, DCTL_RMTWKUPSIG);
+
+ /* After the signalling, the USB core wakes up to L0 */
+ call_gadget(hsotg, resume);
+ hsotg->lx_state = DWC2_L0;
+
+ ret = 0;
+skip:
+ spin_unlock_irqrestore(&hsotg->lock, flags);
+ return ret;
+}
+
static const struct usb_gadget_ops dwc2_hsotg_gadget_ops = {
.get_frame = dwc2_hsotg_gadget_getframe,
.set_selfpowered = dwc2_hsotg_set_selfpowered,
@@ -4772,6 +4844,7 @@ static const struct usb_gadget_ops dwc2_hsotg_gadget_ops = {
.udc_set_speed = dwc2_gadget_set_speed,
.vbus_session = dwc2_hsotg_vbus_session,
.vbus_draw = dwc2_hsotg_vbus_draw,
+ .wakeup = dwc2_hsotg_wakeup,
};
/**
diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c
index ca0a7d9eaa34..cf11ab8e88e0 100644
--- a/drivers/usb/gadget/function/f_hid.c
+++ b/drivers/usb/gadget/function/f_hid.c
@@ -54,6 +54,8 @@ struct f_hidg {
* will be used to receive reports.
*/
bool use_out_ep;
+ /* attempt to wake up the host before write */
+ bool wakeup_on_write;
/* recv report */
spinlock_t read_spinlock;
@@ -422,10 +424,19 @@ static ssize_t f_hidg_write(struct file *file, const char __user *buffer,
size_t count, loff_t *offp)
{
struct f_hidg *hidg = file->private_data;
+ struct usb_composite_dev *cdev = hidg->func.config->cdev;
struct usb_request *req;
unsigned long flags;
ssize_t status = -ENOMEM;
+ /*
+ * remote wakeup is allowed only when the corresponding bit
+ * in config descriptor is set and wakeup_on_write is enabled.
+ * FIXME: cdev->config can be NULLed on disconnect.
+ */
+ if (hidg->wakeup_on_write /*&& cdev->config->bmAttributes & 0x20*/)
+ usb_gadget_wakeup(cdev->gadget);
+
spin_lock_irqsave(&hidg->write_spinlock, flags);
if (!hidg->req) {
@@ -1101,6 +1112,7 @@ CONFIGFS_ATTR(f_hid_opts_, name)
F_HID_OPT(subclass, 8, 255);
F_HID_OPT(protocol, 8, 255);
F_HID_OPT(no_out_endpoint, 8, 1);
+F_HID_OPT(wakeup_on_write, 8, 1);
F_HID_OPT(report_length, 16, 65535);
static ssize_t f_hid_opts_report_desc_show(struct config_item *item, char *page)
@@ -1161,6 +1173,7 @@ static struct configfs_attribute *hid_attrs[] = {
&f_hid_opts_attr_subclass,
&f_hid_opts_attr_protocol,
&f_hid_opts_attr_no_out_endpoint,
+ &f_hid_opts_attr_wakeup_on_write,
&f_hid_opts_attr_report_length,
&f_hid_opts_attr_report_desc,
&f_hid_opts_attr_dev,
@@ -1293,6 +1306,7 @@ static struct usb_function *hidg_alloc(struct usb_function_instance *fi)
}
}
hidg->use_out_ep = !opts->no_out_endpoint;
+ hidg->wakeup_on_write = opts->wakeup_on_write;
mutex_unlock(&opts->lock);
diff --git a/drivers/usb/gadget/function/u_hid.h b/drivers/usb/gadget/function/u_hid.h
index 84bb70292855..f7fcaf1eaf1d 100644
--- a/drivers/usb/gadget/function/u_hid.h
+++ b/drivers/usb/gadget/function/u_hid.h
@@ -21,6 +21,7 @@ struct f_hid_opts {
unsigned char subclass;
unsigned char protocol;
unsigned char no_out_endpoint;
+ unsigned char wakeup_on_write;
unsigned short report_length;
unsigned short report_desc_length;
unsigned char *report_desc;