diff options
Diffstat (limited to 'drivers/misc/mei/init.c')
-rw-r--r-- | drivers/misc/mei/init.c | 278 |
1 files changed, 182 insertions, 96 deletions
diff --git a/drivers/misc/mei/init.c b/drivers/misc/mei/init.c index f7f3abbe12b6..cdd31c2a2a2b 100644 --- a/drivers/misc/mei/init.c +++ b/drivers/misc/mei/init.c | |||
@@ -43,41 +43,119 @@ const char *mei_dev_state_str(int state) | |||
43 | #undef MEI_DEV_STATE | 43 | #undef MEI_DEV_STATE |
44 | } | 44 | } |
45 | 45 | ||
46 | void mei_device_init(struct mei_device *dev) | ||
47 | { | ||
48 | /* setup our list array */ | ||
49 | INIT_LIST_HEAD(&dev->file_list); | ||
50 | INIT_LIST_HEAD(&dev->device_list); | ||
51 | mutex_init(&dev->device_lock); | ||
52 | init_waitqueue_head(&dev->wait_hw_ready); | ||
53 | init_waitqueue_head(&dev->wait_recvd_msg); | ||
54 | init_waitqueue_head(&dev->wait_stop_wd); | ||
55 | dev->dev_state = MEI_DEV_INITIALIZING; | ||
56 | 46 | ||
57 | mei_io_list_init(&dev->read_list); | 47 | /** |
58 | mei_io_list_init(&dev->write_list); | 48 | * mei_cancel_work. Cancel mei background jobs |
59 | mei_io_list_init(&dev->write_waiting_list); | 49 | * |
60 | mei_io_list_init(&dev->ctrl_wr_list); | 50 | * @dev: the device structure |
61 | mei_io_list_init(&dev->ctrl_rd_list); | 51 | * |
52 | * returns 0 on success or < 0 if the reset hasn't succeeded | ||
53 | */ | ||
54 | void mei_cancel_work(struct mei_device *dev) | ||
55 | { | ||
56 | cancel_work_sync(&dev->init_work); | ||
57 | cancel_work_sync(&dev->reset_work); | ||
62 | 58 | ||
63 | INIT_DELAYED_WORK(&dev->timer_work, mei_timer); | 59 | cancel_delayed_work(&dev->timer_work); |
64 | INIT_WORK(&dev->init_work, mei_host_client_init); | 60 | } |
61 | EXPORT_SYMBOL_GPL(mei_cancel_work); | ||
65 | 62 | ||
66 | INIT_LIST_HEAD(&dev->wd_cl.link); | 63 | /** |
67 | INIT_LIST_HEAD(&dev->iamthif_cl.link); | 64 | * mei_reset - resets host and fw. |
68 | mei_io_list_init(&dev->amthif_cmd_list); | 65 | * |
69 | mei_io_list_init(&dev->amthif_rd_complete_list); | 66 | * @dev: the device structure |
67 | */ | ||
68 | int mei_reset(struct mei_device *dev) | ||
69 | { | ||
70 | enum mei_dev_state state = dev->dev_state; | ||
71 | bool interrupts_enabled; | ||
72 | int ret; | ||
70 | 73 | ||
71 | bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX); | 74 | if (state != MEI_DEV_INITIALIZING && |
72 | dev->open_handle_count = 0; | 75 | state != MEI_DEV_DISABLED && |
76 | state != MEI_DEV_POWER_DOWN && | ||
77 | state != MEI_DEV_POWER_UP) | ||
78 | dev_warn(&dev->pdev->dev, "unexpected reset: dev_state = %s\n", | ||
79 | mei_dev_state_str(state)); | ||
73 | 80 | ||
74 | /* | 81 | /* we're already in reset, cancel the init timer |
75 | * Reserving the first client ID | 82 | * if the reset was called due the hbm protocol error |
76 | * 0: Reserved for MEI Bus Message communications | 83 | * we need to call it before hw start |
84 | * so the hbm watchdog won't kick in | ||
77 | */ | 85 | */ |
78 | bitmap_set(dev->host_clients_map, 0, 1); | 86 | mei_hbm_idle(dev); |
87 | |||
88 | /* enter reset flow */ | ||
89 | interrupts_enabled = state != MEI_DEV_POWER_DOWN; | ||
90 | dev->dev_state = MEI_DEV_RESETTING; | ||
91 | |||
92 | dev->reset_count++; | ||
93 | if (dev->reset_count > MEI_MAX_CONSEC_RESET) { | ||
94 | dev_err(&dev->pdev->dev, "reset: reached maximal consecutive resets: disabling the device\n"); | ||
95 | dev->dev_state = MEI_DEV_DISABLED; | ||
96 | return -ENODEV; | ||
97 | } | ||
98 | |||
99 | ret = mei_hw_reset(dev, interrupts_enabled); | ||
100 | /* fall through and remove the sw state even if hw reset has failed */ | ||
101 | |||
102 | /* no need to clean up software state in case of power up */ | ||
103 | if (state != MEI_DEV_INITIALIZING && | ||
104 | state != MEI_DEV_POWER_UP) { | ||
105 | |||
106 | /* remove all waiting requests */ | ||
107 | mei_cl_all_write_clear(dev); | ||
108 | |||
109 | mei_cl_all_disconnect(dev); | ||
110 | |||
111 | /* wake up all readers and writers so they can be interrupted */ | ||
112 | mei_cl_all_wakeup(dev); | ||
113 | |||
114 | /* remove entry if already in list */ | ||
115 | dev_dbg(&dev->pdev->dev, "remove iamthif and wd from the file list.\n"); | ||
116 | mei_cl_unlink(&dev->wd_cl); | ||
117 | mei_cl_unlink(&dev->iamthif_cl); | ||
118 | mei_amthif_reset_params(dev); | ||
119 | memset(&dev->wr_ext_msg, 0, sizeof(dev->wr_ext_msg)); | ||
120 | } | ||
121 | |||
122 | |||
123 | dev->me_clients_num = 0; | ||
124 | dev->rd_msg_hdr = 0; | ||
125 | dev->wd_pending = false; | ||
126 | |||
127 | if (ret) { | ||
128 | dev_err(&dev->pdev->dev, "hw_reset failed ret = %d\n", ret); | ||
129 | dev->dev_state = MEI_DEV_DISABLED; | ||
130 | return ret; | ||
131 | } | ||
132 | |||
133 | if (state == MEI_DEV_POWER_DOWN) { | ||
134 | dev_dbg(&dev->pdev->dev, "powering down: end of reset\n"); | ||
135 | dev->dev_state = MEI_DEV_DISABLED; | ||
136 | return 0; | ||
137 | } | ||
138 | |||
139 | ret = mei_hw_start(dev); | ||
140 | if (ret) { | ||
141 | dev_err(&dev->pdev->dev, "hw_start failed ret = %d\n", ret); | ||
142 | dev->dev_state = MEI_DEV_DISABLED; | ||
143 | return ret; | ||
144 | } | ||
145 | |||
146 | dev_dbg(&dev->pdev->dev, "link is established start sending messages.\n"); | ||
147 | |||
148 | dev->dev_state = MEI_DEV_INIT_CLIENTS; | ||
149 | ret = mei_hbm_start_req(dev); | ||
150 | if (ret) { | ||
151 | dev_err(&dev->pdev->dev, "hbm_start failed ret = %d\n", ret); | ||
152 | dev->dev_state = MEI_DEV_DISABLED; | ||
153 | return ret; | ||
154 | } | ||
155 | |||
156 | return 0; | ||
79 | } | 157 | } |
80 | EXPORT_SYMBOL_GPL(mei_device_init); | 158 | EXPORT_SYMBOL_GPL(mei_reset); |
81 | 159 | ||
82 | /** | 160 | /** |
83 | * mei_start - initializes host and fw to start work. | 161 | * mei_start - initializes host and fw to start work. |
@@ -90,14 +168,21 @@ int mei_start(struct mei_device *dev) | |||
90 | { | 168 | { |
91 | mutex_lock(&dev->device_lock); | 169 | mutex_lock(&dev->device_lock); |
92 | 170 | ||
93 | /* acknowledge interrupt and stop interupts */ | 171 | /* acknowledge interrupt and stop interrupts */ |
94 | mei_clear_interrupts(dev); | 172 | mei_clear_interrupts(dev); |
95 | 173 | ||
96 | mei_hw_config(dev); | 174 | mei_hw_config(dev); |
97 | 175 | ||
98 | dev_dbg(&dev->pdev->dev, "reset in start the mei device.\n"); | 176 | dev_dbg(&dev->pdev->dev, "reset in start the mei device.\n"); |
99 | 177 | ||
100 | mei_reset(dev, 1); | 178 | dev->dev_state = MEI_DEV_INITIALIZING; |
179 | dev->reset_count = 0; | ||
180 | mei_reset(dev); | ||
181 | |||
182 | if (dev->dev_state == MEI_DEV_DISABLED) { | ||
183 | dev_err(&dev->pdev->dev, "reset failed"); | ||
184 | goto err; | ||
185 | } | ||
101 | 186 | ||
102 | if (mei_hbm_start_wait(dev)) { | 187 | if (mei_hbm_start_wait(dev)) { |
103 | dev_err(&dev->pdev->dev, "HBM haven't started"); | 188 | dev_err(&dev->pdev->dev, "HBM haven't started"); |
@@ -132,101 +217,64 @@ err: | |||
132 | EXPORT_SYMBOL_GPL(mei_start); | 217 | EXPORT_SYMBOL_GPL(mei_start); |
133 | 218 | ||
134 | /** | 219 | /** |
135 | * mei_reset - resets host and fw. | 220 | * mei_restart - restart device after suspend |
136 | * | 221 | * |
137 | * @dev: the device structure | 222 | * @dev: the device structure |
138 | * @interrupts_enabled: if interrupt should be enabled after reset. | 223 | * |
224 | * returns 0 on success or -ENODEV if the restart hasn't succeeded | ||
139 | */ | 225 | */ |
140 | void mei_reset(struct mei_device *dev, int interrupts_enabled) | 226 | int mei_restart(struct mei_device *dev) |
141 | { | 227 | { |
142 | bool unexpected; | 228 | int err; |
143 | int ret; | ||
144 | |||
145 | unexpected = (dev->dev_state != MEI_DEV_INITIALIZING && | ||
146 | dev->dev_state != MEI_DEV_DISABLED && | ||
147 | dev->dev_state != MEI_DEV_POWER_DOWN && | ||
148 | dev->dev_state != MEI_DEV_POWER_UP); | ||
149 | 229 | ||
150 | if (unexpected) | 230 | mutex_lock(&dev->device_lock); |
151 | dev_warn(&dev->pdev->dev, "unexpected reset: dev_state = %s\n", | ||
152 | mei_dev_state_str(dev->dev_state)); | ||
153 | |||
154 | ret = mei_hw_reset(dev, interrupts_enabled); | ||
155 | if (ret) { | ||
156 | dev_err(&dev->pdev->dev, "hw reset failed disabling the device\n"); | ||
157 | interrupts_enabled = false; | ||
158 | dev->dev_state = MEI_DEV_DISABLED; | ||
159 | } | ||
160 | |||
161 | dev->hbm_state = MEI_HBM_IDLE; | ||
162 | 231 | ||
163 | if (dev->dev_state != MEI_DEV_INITIALIZING && | 232 | mei_clear_interrupts(dev); |
164 | dev->dev_state != MEI_DEV_POWER_UP) { | ||
165 | if (dev->dev_state != MEI_DEV_DISABLED && | ||
166 | dev->dev_state != MEI_DEV_POWER_DOWN) | ||
167 | dev->dev_state = MEI_DEV_RESETTING; | ||
168 | 233 | ||
169 | /* remove all waiting requests */ | 234 | dev->dev_state = MEI_DEV_POWER_UP; |
170 | mei_cl_all_write_clear(dev); | 235 | dev->reset_count = 0; |
171 | 236 | ||
172 | mei_cl_all_disconnect(dev); | 237 | err = mei_reset(dev); |
173 | 238 | ||
174 | /* wake up all readings so they can be interrupted */ | 239 | mutex_unlock(&dev->device_lock); |
175 | mei_cl_all_wakeup(dev); | ||
176 | |||
177 | /* remove entry if already in list */ | ||
178 | dev_dbg(&dev->pdev->dev, "remove iamthif and wd from the file list.\n"); | ||
179 | mei_cl_unlink(&dev->wd_cl); | ||
180 | mei_cl_unlink(&dev->iamthif_cl); | ||
181 | mei_amthif_reset_params(dev); | ||
182 | memset(&dev->wr_ext_msg, 0, sizeof(dev->wr_ext_msg)); | ||
183 | } | ||
184 | 240 | ||
185 | /* we're already in reset, cancel the init timer */ | 241 | if (err || dev->dev_state == MEI_DEV_DISABLED) |
186 | dev->init_clients_timer = 0; | 242 | return -ENODEV; |
187 | 243 | ||
188 | dev->me_clients_num = 0; | 244 | return 0; |
189 | dev->rd_msg_hdr = 0; | 245 | } |
190 | dev->wd_pending = false; | 246 | EXPORT_SYMBOL_GPL(mei_restart); |
191 | 247 | ||
192 | if (!interrupts_enabled) { | ||
193 | dev_dbg(&dev->pdev->dev, "intr not enabled end of reset\n"); | ||
194 | return; | ||
195 | } | ||
196 | 248 | ||
197 | ret = mei_hw_start(dev); | 249 | static void mei_reset_work(struct work_struct *work) |
198 | if (ret) { | 250 | { |
199 | dev_err(&dev->pdev->dev, "hw_start failed disabling the device\n"); | 251 | struct mei_device *dev = |
200 | dev->dev_state = MEI_DEV_DISABLED; | 252 | container_of(work, struct mei_device, reset_work); |
201 | return; | ||
202 | } | ||
203 | 253 | ||
204 | dev_dbg(&dev->pdev->dev, "link is established start sending messages.\n"); | 254 | mutex_lock(&dev->device_lock); |
205 | /* link is established * start sending messages. */ | ||
206 | 255 | ||
207 | dev->dev_state = MEI_DEV_INIT_CLIENTS; | 256 | mei_reset(dev); |
208 | 257 | ||
209 | mei_hbm_start_req(dev); | 258 | mutex_unlock(&dev->device_lock); |
210 | 259 | ||
260 | if (dev->dev_state == MEI_DEV_DISABLED) | ||
261 | dev_err(&dev->pdev->dev, "reset failed"); | ||
211 | } | 262 | } |
212 | EXPORT_SYMBOL_GPL(mei_reset); | ||
213 | 263 | ||
214 | void mei_stop(struct mei_device *dev) | 264 | void mei_stop(struct mei_device *dev) |
215 | { | 265 | { |
216 | dev_dbg(&dev->pdev->dev, "stopping the device.\n"); | 266 | dev_dbg(&dev->pdev->dev, "stopping the device.\n"); |
217 | 267 | ||
218 | flush_scheduled_work(); | 268 | mei_cancel_work(dev); |
219 | 269 | ||
220 | mutex_lock(&dev->device_lock); | 270 | mei_nfc_host_exit(dev); |
221 | 271 | ||
222 | cancel_delayed_work(&dev->timer_work); | 272 | mutex_lock(&dev->device_lock); |
223 | 273 | ||
224 | mei_wd_stop(dev); | 274 | mei_wd_stop(dev); |
225 | 275 | ||
226 | mei_nfc_host_exit(); | ||
227 | |||
228 | dev->dev_state = MEI_DEV_POWER_DOWN; | 276 | dev->dev_state = MEI_DEV_POWER_DOWN; |
229 | mei_reset(dev, 0); | 277 | mei_reset(dev); |
230 | 278 | ||
231 | mutex_unlock(&dev->device_lock); | 279 | mutex_unlock(&dev->device_lock); |
232 | 280 | ||
@@ -236,3 +284,41 @@ EXPORT_SYMBOL_GPL(mei_stop); | |||
236 | 284 | ||
237 | 285 | ||
238 | 286 | ||
287 | void mei_device_init(struct mei_device *dev) | ||
288 | { | ||
289 | /* setup our list array */ | ||
290 | INIT_LIST_HEAD(&dev->file_list); | ||
291 | INIT_LIST_HEAD(&dev->device_list); | ||
292 | mutex_init(&dev->device_lock); | ||
293 | init_waitqueue_head(&dev->wait_hw_ready); | ||
294 | init_waitqueue_head(&dev->wait_recvd_msg); | ||
295 | init_waitqueue_head(&dev->wait_stop_wd); | ||
296 | dev->dev_state = MEI_DEV_INITIALIZING; | ||
297 | dev->reset_count = 0; | ||
298 | |||
299 | mei_io_list_init(&dev->read_list); | ||
300 | mei_io_list_init(&dev->write_list); | ||
301 | mei_io_list_init(&dev->write_waiting_list); | ||
302 | mei_io_list_init(&dev->ctrl_wr_list); | ||
303 | mei_io_list_init(&dev->ctrl_rd_list); | ||
304 | |||
305 | INIT_DELAYED_WORK(&dev->timer_work, mei_timer); | ||
306 | INIT_WORK(&dev->init_work, mei_host_client_init); | ||
307 | INIT_WORK(&dev->reset_work, mei_reset_work); | ||
308 | |||
309 | INIT_LIST_HEAD(&dev->wd_cl.link); | ||
310 | INIT_LIST_HEAD(&dev->iamthif_cl.link); | ||
311 | mei_io_list_init(&dev->amthif_cmd_list); | ||
312 | mei_io_list_init(&dev->amthif_rd_complete_list); | ||
313 | |||
314 | bitmap_zero(dev->host_clients_map, MEI_CLIENTS_MAX); | ||
315 | dev->open_handle_count = 0; | ||
316 | |||
317 | /* | ||
318 | * Reserving the first client ID | ||
319 | * 0: Reserved for MEI Bus Message communications | ||
320 | */ | ||
321 | bitmap_set(dev->host_clients_map, 0, 1); | ||
322 | } | ||
323 | EXPORT_SYMBOL_GPL(mei_device_init); | ||
324 | |||