]>
Commit | Line | Data |
---|---|---|
ae02e5d4 SP |
1 | /* |
2 | * H/W layer of ISHTP provider device (ISH) | |
3 | * | |
4 | * Copyright (c) 2014-2016, Intel Corporation. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms and conditions of the GNU General Public License, | |
8 | * version 2, as published by the Free Software Foundation. | |
9 | * | |
10 | * This program is distributed in the hope it will be useful, but WITHOUT | |
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
13 | * more details. | |
14 | */ | |
15 | ||
16 | #include <linux/sched.h> | |
17 | #include <linux/spinlock.h> | |
18 | #include <linux/delay.h> | |
19 | #include <linux/jiffies.h> | |
20 | #include "client.h" | |
21 | #include "hw-ish.h" | |
ae02e5d4 SP |
22 | #include "hbm.h" |
23 | ||
24 | /* For FW reset flow */ | |
25 | static struct work_struct fw_reset_work; | |
26 | static struct ishtp_device *ishtp_dev; | |
27 | ||
28 | /** | |
29 | * ish_reg_read() - Read register | |
30 | * @dev: ISHTP device pointer | |
31 | * @offset: Register offset | |
32 | * | |
33 | * Read 32 bit register at a given offset | |
34 | * | |
35 | * Return: Read register value | |
36 | */ | |
37 | static inline uint32_t ish_reg_read(const struct ishtp_device *dev, | |
38 | unsigned long offset) | |
39 | { | |
40 | struct ish_hw *hw = to_ish_hw(dev); | |
41 | ||
42 | return readl(hw->mem_addr + offset); | |
43 | } | |
44 | ||
45 | /** | |
46 | * ish_reg_write() - Write register | |
47 | * @dev: ISHTP device pointer | |
48 | * @offset: Register offset | |
49 | * @value: Value to write | |
50 | * | |
51 | * Writes 32 bit register at a give offset | |
52 | */ | |
53 | static inline void ish_reg_write(struct ishtp_device *dev, | |
54 | unsigned long offset, | |
55 | uint32_t value) | |
56 | { | |
57 | struct ish_hw *hw = to_ish_hw(dev); | |
58 | ||
59 | writel(value, hw->mem_addr + offset); | |
60 | } | |
61 | ||
62 | /** | |
63 | * _ish_read_fw_sts_reg() - Read FW status register | |
64 | * @dev: ISHTP device pointer | |
65 | * | |
66 | * Read FW status register | |
67 | * | |
68 | * Return: Read register value | |
69 | */ | |
70 | static inline uint32_t _ish_read_fw_sts_reg(struct ishtp_device *dev) | |
71 | { | |
72 | return ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); | |
73 | } | |
74 | ||
75 | /** | |
76 | * check_generated_interrupt() - Check if ISH interrupt | |
77 | * @dev: ISHTP device pointer | |
78 | * | |
79 | * Check if an interrupt was generated for ISH | |
80 | * | |
81 | * Return: Read true or false | |
82 | */ | |
83 | static bool check_generated_interrupt(struct ishtp_device *dev) | |
84 | { | |
85 | bool interrupt_generated = true; | |
86 | uint32_t pisr_val = 0; | |
87 | ||
88 | if (dev->pdev->device == CHV_DEVICE_ID) { | |
89 | pisr_val = ish_reg_read(dev, IPC_REG_PISR_CHV_AB); | |
90 | interrupt_generated = | |
91 | IPC_INT_FROM_ISH_TO_HOST_CHV_AB(pisr_val); | |
92 | } else { | |
93 | pisr_val = ish_reg_read(dev, IPC_REG_PISR_BXT); | |
94 | interrupt_generated = IPC_INT_FROM_ISH_TO_HOST_BXT(pisr_val); | |
95 | } | |
96 | ||
97 | return interrupt_generated; | |
98 | } | |
99 | ||
100 | /** | |
101 | * ish_is_input_ready() - Check if FW ready for RX | |
102 | * @dev: ISHTP device pointer | |
103 | * | |
104 | * Check if ISH FW is ready for receiving data | |
105 | * | |
106 | * Return: Read true or false | |
107 | */ | |
108 | static bool ish_is_input_ready(struct ishtp_device *dev) | |
109 | { | |
110 | uint32_t doorbell_val; | |
111 | ||
112 | doorbell_val = ish_reg_read(dev, IPC_REG_HOST2ISH_DRBL); | |
113 | return !IPC_IS_BUSY(doorbell_val); | |
114 | } | |
115 | ||
116 | /** | |
117 | * set_host_ready() - Indicate host ready | |
118 | * @dev: ISHTP device pointer | |
119 | * | |
120 | * Set host ready indication to FW | |
121 | */ | |
122 | static void set_host_ready(struct ishtp_device *dev) | |
123 | { | |
124 | if (dev->pdev->device == CHV_DEVICE_ID) { | |
125 | if (dev->pdev->revision == REVISION_ID_CHT_A0 || | |
126 | (dev->pdev->revision & REVISION_ID_SI_MASK) == | |
127 | REVISION_ID_CHT_Ax_SI) | |
128 | ish_reg_write(dev, IPC_REG_HOST_COMM, 0x81); | |
129 | else if (dev->pdev->revision == REVISION_ID_CHT_B0 || | |
130 | (dev->pdev->revision & REVISION_ID_SI_MASK) == | |
131 | REVISION_ID_CHT_Bx_SI || | |
132 | (dev->pdev->revision & REVISION_ID_SI_MASK) == | |
133 | REVISION_ID_CHT_Kx_SI || | |
134 | (dev->pdev->revision & REVISION_ID_SI_MASK) == | |
135 | REVISION_ID_CHT_Dx_SI) { | |
136 | uint32_t host_comm_val; | |
137 | ||
138 | host_comm_val = ish_reg_read(dev, IPC_REG_HOST_COMM); | |
139 | host_comm_val |= IPC_HOSTCOMM_INT_EN_BIT_CHV_AB | 0x81; | |
140 | ish_reg_write(dev, IPC_REG_HOST_COMM, host_comm_val); | |
141 | } | |
142 | } else { | |
143 | uint32_t host_pimr_val; | |
144 | ||
145 | host_pimr_val = ish_reg_read(dev, IPC_REG_PIMR_BXT); | |
146 | host_pimr_val |= IPC_PIMR_INT_EN_BIT_BXT; | |
147 | /* | |
148 | * disable interrupt generated instead of | |
149 | * RX_complete_msg | |
150 | */ | |
151 | host_pimr_val &= ~IPC_HOST2ISH_BUSYCLEAR_MASK_BIT; | |
152 | ||
153 | ish_reg_write(dev, IPC_REG_PIMR_BXT, host_pimr_val); | |
154 | } | |
155 | } | |
156 | ||
157 | /** | |
158 | * ishtp_fw_is_ready() - Check if FW ready | |
159 | * @dev: ISHTP device pointer | |
160 | * | |
161 | * Check if ISH FW is ready | |
162 | * | |
163 | * Return: Read true or false | |
164 | */ | |
165 | static bool ishtp_fw_is_ready(struct ishtp_device *dev) | |
166 | { | |
167 | uint32_t ish_status = _ish_read_fw_sts_reg(dev); | |
168 | ||
169 | return IPC_IS_ISH_ILUP(ish_status) && | |
170 | IPC_IS_ISH_ISHTP_READY(ish_status); | |
171 | } | |
172 | ||
173 | /** | |
174 | * ish_set_host_rdy() - Indicate host ready | |
175 | * @dev: ISHTP device pointer | |
176 | * | |
177 | * Set host ready indication to FW | |
178 | */ | |
179 | static void ish_set_host_rdy(struct ishtp_device *dev) | |
180 | { | |
181 | uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM); | |
182 | ||
183 | IPC_SET_HOST_READY(host_status); | |
184 | ish_reg_write(dev, IPC_REG_HOST_COMM, host_status); | |
185 | } | |
186 | ||
187 | /** | |
188 | * ish_clr_host_rdy() - Indicate host not ready | |
189 | * @dev: ISHTP device pointer | |
190 | * | |
191 | * Send host not ready indication to FW | |
192 | */ | |
193 | static void ish_clr_host_rdy(struct ishtp_device *dev) | |
194 | { | |
195 | uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM); | |
196 | ||
197 | IPC_CLEAR_HOST_READY(host_status); | |
198 | ish_reg_write(dev, IPC_REG_HOST_COMM, host_status); | |
199 | } | |
200 | ||
201 | /** | |
202 | * _ishtp_read_hdr() - Read message header | |
203 | * @dev: ISHTP device pointer | |
204 | * | |
205 | * Read header of 32bit length | |
206 | * | |
207 | * Return: Read register value | |
208 | */ | |
209 | static uint32_t _ishtp_read_hdr(const struct ishtp_device *dev) | |
210 | { | |
211 | return ish_reg_read(dev, IPC_REG_ISH2HOST_MSG); | |
212 | } | |
213 | ||
214 | /** | |
215 | * _ishtp_read - Read message | |
216 | * @dev: ISHTP device pointer | |
217 | * @buffer: message buffer | |
218 | * @buffer_length: length of message buffer | |
219 | * | |
220 | * Read message from FW | |
221 | * | |
222 | * Return: Always 0 | |
223 | */ | |
224 | static int _ishtp_read(struct ishtp_device *dev, unsigned char *buffer, | |
225 | unsigned long buffer_length) | |
226 | { | |
227 | uint32_t i; | |
228 | uint32_t *r_buf = (uint32_t *)buffer; | |
229 | uint32_t msg_offs; | |
230 | ||
231 | msg_offs = IPC_REG_ISH2HOST_MSG + sizeof(struct ishtp_msg_hdr); | |
232 | for (i = 0; i < buffer_length; i += sizeof(uint32_t)) | |
233 | *r_buf++ = ish_reg_read(dev, msg_offs + i); | |
234 | ||
235 | return 0; | |
236 | } | |
237 | ||
238 | /** | |
239 | * write_ipc_from_queue() - try to write ipc msg from Tx queue to device | |
240 | * @dev: ishtp device pointer | |
241 | * | |
242 | * Check if DRBL is cleared. if it is - write the first IPC msg, then call | |
243 | * the callback function (unless it's NULL) | |
244 | * | |
245 | * Return: 0 for success else failure code | |
246 | */ | |
247 | static int write_ipc_from_queue(struct ishtp_device *dev) | |
248 | { | |
249 | struct wr_msg_ctl_info *ipc_link; | |
250 | unsigned long length; | |
251 | unsigned long rem; | |
252 | unsigned long flags; | |
253 | uint32_t doorbell_val; | |
254 | uint32_t *r_buf; | |
255 | uint32_t reg_addr; | |
256 | int i; | |
257 | void (*ipc_send_compl)(void *); | |
258 | void *ipc_send_compl_prm; | |
259 | static int out_ipc_locked; | |
260 | unsigned long out_ipc_flags; | |
261 | ||
262 | if (dev->dev_state == ISHTP_DEV_DISABLED) | |
263 | return -EINVAL; | |
264 | ||
265 | spin_lock_irqsave(&dev->out_ipc_spinlock, out_ipc_flags); | |
266 | if (out_ipc_locked) { | |
267 | spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags); | |
268 | return -EBUSY; | |
269 | } | |
270 | out_ipc_locked = 1; | |
271 | if (!ish_is_input_ready(dev)) { | |
272 | out_ipc_locked = 0; | |
273 | spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags); | |
274 | return -EBUSY; | |
275 | } | |
276 | spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags); | |
277 | ||
278 | spin_lock_irqsave(&dev->wr_processing_spinlock, flags); | |
279 | /* | |
280 | * if tx send list is empty - return 0; | |
281 | * may happen, as RX_COMPLETE handler doesn't check list emptiness. | |
282 | */ | |
283 | if (list_empty(&dev->wr_processing_list_head.link)) { | |
284 | spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); | |
285 | out_ipc_locked = 0; | |
286 | return 0; | |
287 | } | |
288 | ||
289 | ipc_link = list_entry(dev->wr_processing_list_head.link.next, | |
290 | struct wr_msg_ctl_info, link); | |
291 | /* first 4 bytes of the data is the doorbell value (IPC header) */ | |
292 | length = ipc_link->length - sizeof(uint32_t); | |
293 | doorbell_val = *(uint32_t *)ipc_link->inline_data; | |
294 | r_buf = (uint32_t *)(ipc_link->inline_data + sizeof(uint32_t)); | |
295 | ||
296 | /* If sending MNG_SYNC_FW_CLOCK, update clock again */ | |
297 | if (IPC_HEADER_GET_PROTOCOL(doorbell_val) == IPC_PROTOCOL_MNG && | |
298 | IPC_HEADER_GET_MNG_CMD(doorbell_val) == MNG_SYNC_FW_CLOCK) { | |
299 | struct timespec ts_system; | |
300 | struct timeval tv_utc; | |
301 | uint64_t usec_system, usec_utc; | |
302 | struct ipc_time_update_msg time_update; | |
303 | struct time_sync_format ts_format; | |
304 | ||
305 | get_monotonic_boottime(&ts_system); | |
306 | do_gettimeofday(&tv_utc); | |
307 | usec_system = (timespec_to_ns(&ts_system)) / NSEC_PER_USEC; | |
308 | usec_utc = (uint64_t)tv_utc.tv_sec * 1000000 + | |
309 | ((uint32_t)tv_utc.tv_usec); | |
310 | ts_format.ts1_source = HOST_SYSTEM_TIME_USEC; | |
311 | ts_format.ts2_source = HOST_UTC_TIME_USEC; | |
15607a3a | 312 | ts_format.reserved = 0; |
ae02e5d4 SP |
313 | |
314 | time_update.primary_host_time = usec_system; | |
315 | time_update.secondary_host_time = usec_utc; | |
316 | time_update.sync_info = ts_format; | |
317 | ||
318 | memcpy(r_buf, &time_update, | |
319 | sizeof(struct ipc_time_update_msg)); | |
320 | } | |
321 | ||
322 | for (i = 0, reg_addr = IPC_REG_HOST2ISH_MSG; i < length >> 2; i++, | |
323 | reg_addr += 4) | |
324 | ish_reg_write(dev, reg_addr, r_buf[i]); | |
325 | ||
326 | rem = length & 0x3; | |
327 | if (rem > 0) { | |
328 | uint32_t reg = 0; | |
329 | ||
330 | memcpy(®, &r_buf[length >> 2], rem); | |
331 | ish_reg_write(dev, reg_addr, reg); | |
332 | } | |
333 | /* Flush writes to msg registers and doorbell */ | |
334 | ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); | |
335 | ||
336 | /* Update IPC counters */ | |
337 | ++dev->ipc_tx_cnt; | |
338 | dev->ipc_tx_bytes_cnt += IPC_HEADER_GET_LENGTH(doorbell_val); | |
339 | ||
340 | ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, doorbell_val); | |
341 | out_ipc_locked = 0; | |
342 | ||
343 | ipc_send_compl = ipc_link->ipc_send_compl; | |
344 | ipc_send_compl_prm = ipc_link->ipc_send_compl_prm; | |
345 | list_del_init(&ipc_link->link); | |
346 | list_add_tail(&ipc_link->link, &dev->wr_free_list_head.link); | |
347 | spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); | |
348 | ||
349 | /* | |
350 | * callback will be called out of spinlock, | |
351 | * after ipc_link returned to free list | |
352 | */ | |
353 | if (ipc_send_compl) | |
354 | ipc_send_compl(ipc_send_compl_prm); | |
355 | ||
356 | return 0; | |
357 | } | |
358 | ||
359 | /** | |
360 | * write_ipc_to_queue() - write ipc msg to Tx queue | |
361 | * @dev: ishtp device instance | |
362 | * @ipc_send_compl: Send complete callback | |
363 | * @ipc_send_compl_prm: Parameter to send in complete callback | |
364 | * @msg: Pointer to message | |
365 | * @length: Length of message | |
366 | * | |
367 | * Recived msg with IPC (and upper protocol) header and add it to the device | |
368 | * Tx-to-write list then try to send the first IPC waiting msg | |
369 | * (if DRBL is cleared) | |
370 | * This function returns negative value for failure (means free list | |
371 | * is empty, or msg too long) and 0 for success. | |
372 | * | |
373 | * Return: 0 for success else failure code | |
374 | */ | |
375 | static int write_ipc_to_queue(struct ishtp_device *dev, | |
376 | void (*ipc_send_compl)(void *), void *ipc_send_compl_prm, | |
377 | unsigned char *msg, int length) | |
378 | { | |
379 | struct wr_msg_ctl_info *ipc_link; | |
380 | unsigned long flags; | |
381 | ||
382 | if (length > IPC_FULL_MSG_SIZE) | |
383 | return -EMSGSIZE; | |
384 | ||
385 | spin_lock_irqsave(&dev->wr_processing_spinlock, flags); | |
386 | if (list_empty(&dev->wr_free_list_head.link)) { | |
387 | spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); | |
388 | return -ENOMEM; | |
389 | } | |
390 | ipc_link = list_entry(dev->wr_free_list_head.link.next, | |
391 | struct wr_msg_ctl_info, link); | |
392 | list_del_init(&ipc_link->link); | |
393 | ||
394 | ipc_link->ipc_send_compl = ipc_send_compl; | |
395 | ipc_link->ipc_send_compl_prm = ipc_send_compl_prm; | |
396 | ipc_link->length = length; | |
397 | memcpy(ipc_link->inline_data, msg, length); | |
398 | ||
399 | list_add_tail(&ipc_link->link, &dev->wr_processing_list_head.link); | |
400 | spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); | |
401 | ||
402 | write_ipc_from_queue(dev); | |
403 | ||
404 | return 0; | |
405 | } | |
406 | ||
407 | /** | |
408 | * ipc_send_mng_msg() - Send management message | |
409 | * @dev: ishtp device instance | |
410 | * @msg_code: Message code | |
411 | * @msg: Pointer to message | |
412 | * @size: Length of message | |
413 | * | |
414 | * Send management message to FW | |
415 | * | |
416 | * Return: 0 for success else failure code | |
417 | */ | |
418 | static int ipc_send_mng_msg(struct ishtp_device *dev, uint32_t msg_code, | |
419 | void *msg, size_t size) | |
420 | { | |
421 | unsigned char ipc_msg[IPC_FULL_MSG_SIZE]; | |
422 | uint32_t drbl_val = IPC_BUILD_MNG_MSG(msg_code, size); | |
423 | ||
424 | memcpy(ipc_msg, &drbl_val, sizeof(uint32_t)); | |
425 | memcpy(ipc_msg + sizeof(uint32_t), msg, size); | |
426 | return write_ipc_to_queue(dev, NULL, NULL, ipc_msg, | |
427 | sizeof(uint32_t) + size); | |
428 | } | |
429 | ||
7ede704d EX |
430 | #define WAIT_FOR_FW_RDY 0x1 |
431 | #define WAIT_FOR_INPUT_RDY 0x2 | |
432 | ||
433 | /** | |
434 | * timed_wait_for_timeout() - wait special event with timeout | |
435 | * @dev: ISHTP device pointer | |
436 | * @condition: indicate the condition for waiting | |
437 | * @timeinc: time slice for every wait cycle, in ms | |
438 | * @timeout: time in ms for timeout | |
439 | * | |
440 | * This function will check special event to be ready in a loop, the loop | |
441 | * period is specificd in timeinc. Wait timeout will causes failure. | |
442 | * | |
443 | * Return: 0 for success else failure code | |
444 | */ | |
445 | static int timed_wait_for_timeout(struct ishtp_device *dev, int condition, | |
446 | unsigned int timeinc, unsigned int timeout) | |
447 | { | |
448 | bool complete = false; | |
449 | int ret; | |
450 | ||
451 | do { | |
452 | if (condition == WAIT_FOR_FW_RDY) { | |
453 | complete = ishtp_fw_is_ready(dev); | |
454 | } else if (condition == WAIT_FOR_INPUT_RDY) { | |
455 | complete = ish_is_input_ready(dev); | |
456 | } else { | |
457 | ret = -EINVAL; | |
458 | goto out; | |
459 | } | |
460 | ||
461 | if (!complete) { | |
462 | unsigned long left_time; | |
463 | ||
464 | left_time = msleep_interruptible(timeinc); | |
465 | timeout -= (timeinc - left_time); | |
466 | } | |
467 | } while (!complete && timeout > 0); | |
468 | ||
469 | if (complete) | |
470 | ret = 0; | |
471 | else | |
472 | ret = -EBUSY; | |
473 | ||
474 | out: | |
475 | return ret; | |
476 | } | |
477 | ||
478 | #define TIME_SLICE_FOR_FW_RDY_MS 100 | |
479 | #define TIME_SLICE_FOR_INPUT_RDY_MS 100 | |
480 | #define TIMEOUT_FOR_FW_RDY_MS 2000 | |
481 | #define TIMEOUT_FOR_INPUT_RDY_MS 2000 | |
482 | ||
ae02e5d4 SP |
483 | /** |
484 | * ish_fw_reset_handler() - FW reset handler | |
485 | * @dev: ishtp device pointer | |
486 | * | |
487 | * Handle FW reset | |
488 | * | |
489 | * Return: 0 for success else failure code | |
490 | */ | |
491 | static int ish_fw_reset_handler(struct ishtp_device *dev) | |
492 | { | |
493 | uint32_t reset_id; | |
494 | unsigned long flags; | |
495 | struct wr_msg_ctl_info *processing, *next; | |
496 | ||
497 | /* Read reset ID */ | |
498 | reset_id = ish_reg_read(dev, IPC_REG_ISH2HOST_MSG) & 0xFFFF; | |
499 | ||
500 | /* Clear IPC output queue */ | |
501 | spin_lock_irqsave(&dev->wr_processing_spinlock, flags); | |
502 | list_for_each_entry_safe(processing, next, | |
503 | &dev->wr_processing_list_head.link, link) { | |
67c0fe42 | 504 | list_move_tail(&processing->link, &dev->wr_free_list_head.link); |
ae02e5d4 SP |
505 | } |
506 | spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); | |
507 | ||
508 | /* ISHTP notification in IPC_RESET */ | |
509 | ishtp_reset_handler(dev); | |
510 | ||
511 | if (!ish_is_input_ready(dev)) | |
7ede704d EX |
512 | timed_wait_for_timeout(dev, WAIT_FOR_INPUT_RDY, |
513 | TIME_SLICE_FOR_INPUT_RDY_MS, TIMEOUT_FOR_INPUT_RDY_MS); | |
ae02e5d4 SP |
514 | |
515 | /* ISH FW is dead */ | |
516 | if (!ish_is_input_ready(dev)) | |
517 | return -EPIPE; | |
518 | /* | |
519 | * Set HOST2ISH.ILUP. Apparently we need this BEFORE sending | |
520 | * RESET_NOTIFY_ACK - FW will be checking for it | |
521 | */ | |
522 | ish_set_host_rdy(dev); | |
523 | /* Send RESET_NOTIFY_ACK (with reset_id) */ | |
524 | ipc_send_mng_msg(dev, MNG_RESET_NOTIFY_ACK, &reset_id, | |
525 | sizeof(uint32_t)); | |
526 | ||
527 | /* Wait for ISH FW'es ILUP and ISHTP_READY */ | |
7ede704d EX |
528 | timed_wait_for_timeout(dev, WAIT_FOR_FW_RDY, |
529 | TIME_SLICE_FOR_FW_RDY_MS, TIMEOUT_FOR_FW_RDY_MS); | |
ae02e5d4 SP |
530 | if (!ishtp_fw_is_ready(dev)) { |
531 | /* ISH FW is dead */ | |
532 | uint32_t ish_status; | |
533 | ||
534 | ish_status = _ish_read_fw_sts_reg(dev); | |
535 | dev_err(dev->devc, | |
536 | "[ishtp-ish]: completed reset, ISH is dead (FWSTS = %08X)\n", | |
537 | ish_status); | |
538 | return -ENODEV; | |
539 | } | |
540 | return 0; | |
541 | } | |
542 | ||
e5b56aa7 EX |
543 | #define TIMEOUT_FOR_HW_RDY_MS 300 |
544 | ||
ae02e5d4 SP |
545 | /** |
546 | * ish_fw_reset_work_fn() - FW reset worker function | |
547 | * @unused: not used | |
548 | * | |
549 | * Call ish_fw_reset_handler to complete FW reset | |
550 | */ | |
551 | static void fw_reset_work_fn(struct work_struct *unused) | |
552 | { | |
553 | int rv; | |
554 | ||
555 | rv = ish_fw_reset_handler(ishtp_dev); | |
556 | if (!rv) { | |
557 | /* ISH is ILUP & ISHTP-ready. Restart ISHTP */ | |
e5b56aa7 | 558 | msleep_interruptible(TIMEOUT_FOR_HW_RDY_MS); |
ae02e5d4 SP |
559 | ishtp_dev->recvd_hw_ready = 1; |
560 | wake_up_interruptible(&ishtp_dev->wait_hw_ready); | |
561 | ||
562 | /* ISHTP notification in IPC_RESET sequence completion */ | |
563 | ishtp_reset_compl_handler(ishtp_dev); | |
564 | } else | |
565 | dev_err(ishtp_dev->devc, "[ishtp-ish]: FW reset failed (%d)\n", | |
566 | rv); | |
567 | } | |
568 | ||
569 | /** | |
570 | * _ish_sync_fw_clock() -Sync FW clock with the OS clock | |
571 | * @dev: ishtp device pointer | |
572 | * | |
573 | * Sync FW and OS time | |
574 | */ | |
575 | static void _ish_sync_fw_clock(struct ishtp_device *dev) | |
576 | { | |
577 | static unsigned long prev_sync; | |
578 | struct timespec ts; | |
579 | uint64_t usec; | |
580 | ||
581 | if (prev_sync && jiffies - prev_sync < 20 * HZ) | |
582 | return; | |
583 | ||
584 | prev_sync = jiffies; | |
585 | get_monotonic_boottime(&ts); | |
586 | usec = (timespec_to_ns(&ts)) / NSEC_PER_USEC; | |
587 | ipc_send_mng_msg(dev, MNG_SYNC_FW_CLOCK, &usec, sizeof(uint64_t)); | |
588 | } | |
589 | ||
590 | /** | |
591 | * recv_ipc() - Receive and process IPC management messages | |
592 | * @dev: ishtp device instance | |
593 | * @doorbell_val: doorbell value | |
594 | * | |
595 | * This function runs in ISR context. | |
596 | * NOTE: Any other mng command than reset_notify and reset_notify_ack | |
597 | * won't wake BH handler | |
598 | */ | |
599 | static void recv_ipc(struct ishtp_device *dev, uint32_t doorbell_val) | |
600 | { | |
601 | uint32_t mng_cmd; | |
602 | ||
603 | mng_cmd = IPC_HEADER_GET_MNG_CMD(doorbell_val); | |
604 | ||
605 | switch (mng_cmd) { | |
606 | default: | |
607 | break; | |
608 | ||
609 | case MNG_RX_CMPL_INDICATION: | |
610 | if (dev->suspend_flag) { | |
611 | dev->suspend_flag = 0; | |
612 | wake_up_interruptible(&dev->suspend_wait); | |
613 | } | |
614 | if (dev->resume_flag) { | |
615 | dev->resume_flag = 0; | |
616 | wake_up_interruptible(&dev->resume_wait); | |
617 | } | |
618 | ||
619 | write_ipc_from_queue(dev); | |
620 | break; | |
621 | ||
622 | case MNG_RESET_NOTIFY: | |
623 | if (!ishtp_dev) { | |
624 | ishtp_dev = dev; | |
625 | INIT_WORK(&fw_reset_work, fw_reset_work_fn); | |
626 | } | |
627 | schedule_work(&fw_reset_work); | |
628 | break; | |
629 | ||
630 | case MNG_RESET_NOTIFY_ACK: | |
631 | dev->recvd_hw_ready = 1; | |
632 | wake_up_interruptible(&dev->wait_hw_ready); | |
633 | break; | |
634 | } | |
635 | } | |
636 | ||
637 | /** | |
638 | * ish_irq_handler() - ISH IRQ handler | |
639 | * @irq: irq number | |
640 | * @dev_id: ishtp device pointer | |
641 | * | |
642 | * ISH IRQ handler. If interrupt is generated and is for ISH it will process | |
643 | * the interrupt. | |
644 | */ | |
645 | irqreturn_t ish_irq_handler(int irq, void *dev_id) | |
646 | { | |
647 | struct ishtp_device *dev = dev_id; | |
648 | uint32_t doorbell_val; | |
649 | bool interrupt_generated; | |
650 | ||
651 | /* Check that it's interrupt from ISH (may be shared) */ | |
652 | interrupt_generated = check_generated_interrupt(dev); | |
653 | ||
654 | if (!interrupt_generated) | |
655 | return IRQ_NONE; | |
656 | ||
657 | doorbell_val = ish_reg_read(dev, IPC_REG_ISH2HOST_DRBL); | |
658 | if (!IPC_IS_BUSY(doorbell_val)) | |
659 | return IRQ_HANDLED; | |
660 | ||
661 | if (dev->dev_state == ISHTP_DEV_DISABLED) | |
662 | return IRQ_HANDLED; | |
663 | ||
664 | /* Sanity check: IPC dgram length in header */ | |
665 | if (IPC_HEADER_GET_LENGTH(doorbell_val) > IPC_PAYLOAD_SIZE) { | |
666 | dev_err(dev->devc, | |
667 | "IPC hdr - bad length: %u; dropped\n", | |
668 | (unsigned int)IPC_HEADER_GET_LENGTH(doorbell_val)); | |
669 | goto eoi; | |
670 | } | |
671 | ||
672 | switch (IPC_HEADER_GET_PROTOCOL(doorbell_val)) { | |
673 | default: | |
674 | break; | |
675 | case IPC_PROTOCOL_MNG: | |
676 | recv_ipc(dev, doorbell_val); | |
677 | break; | |
678 | case IPC_PROTOCOL_ISHTP: | |
679 | ishtp_recv(dev); | |
680 | break; | |
681 | } | |
682 | ||
683 | eoi: | |
684 | /* Update IPC counters */ | |
685 | ++dev->ipc_rx_cnt; | |
686 | dev->ipc_rx_bytes_cnt += IPC_HEADER_GET_LENGTH(doorbell_val); | |
687 | ||
688 | ish_reg_write(dev, IPC_REG_ISH2HOST_DRBL, 0); | |
689 | /* Flush write to doorbell */ | |
690 | ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); | |
691 | ||
692 | return IRQ_HANDLED; | |
693 | } | |
694 | ||
8b2979fe EX |
695 | /** |
696 | * ish_disable_dma() - disable dma communication between host and ISHFW | |
697 | * @dev: ishtp device pointer | |
698 | * | |
699 | * Clear the dma enable bit and wait for dma inactive. | |
700 | * | |
701 | * Return: 0 for success else error code. | |
702 | */ | |
703 | static int ish_disable_dma(struct ishtp_device *dev) | |
704 | { | |
705 | unsigned int dma_delay; | |
706 | ||
707 | /* Clear the dma enable bit */ | |
708 | ish_reg_write(dev, IPC_REG_ISH_RMP2, 0); | |
709 | ||
710 | /* wait for dma inactive */ | |
711 | for (dma_delay = 0; dma_delay < MAX_DMA_DELAY && | |
712 | _ish_read_fw_sts_reg(dev) & (IPC_ISH_IN_DMA); | |
713 | dma_delay += 5) | |
714 | mdelay(5); | |
715 | ||
716 | if (dma_delay >= MAX_DMA_DELAY) { | |
717 | dev_err(dev->devc, | |
718 | "Wait for DMA inactive timeout\n"); | |
719 | return -EBUSY; | |
720 | } | |
721 | ||
722 | return 0; | |
723 | } | |
724 | ||
c2ed83f5 EX |
725 | /** |
726 | * ish_wakeup() - wakeup ishfw from waiting-for-host state | |
727 | * @dev: ishtp device pointer | |
728 | * | |
729 | * Set the dma enable bit and send a void message to FW, | |
730 | * it wil wakeup FW from waiting-for-host state. | |
731 | */ | |
732 | static void ish_wakeup(struct ishtp_device *dev) | |
733 | { | |
734 | /* Set dma enable bit */ | |
735 | ish_reg_write(dev, IPC_REG_ISH_RMP2, IPC_RMP2_DMA_ENABLED); | |
736 | ||
737 | /* | |
738 | * Send 0 IPC message so that ISH FW wakes up if it was already | |
739 | * asleep. | |
740 | */ | |
741 | ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, IPC_DRBL_BUSY_BIT); | |
742 | ||
743 | /* Flush writes to doorbell and REMAP2 */ | |
744 | ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); | |
745 | } | |
746 | ||
ae02e5d4 SP |
747 | /** |
748 | * _ish_hw_reset() - HW reset | |
749 | * @dev: ishtp device pointer | |
750 | * | |
751 | * Reset ISH HW to recover if any error | |
752 | * | |
753 | * Return: 0 for success else error fault code | |
754 | */ | |
755 | static int _ish_hw_reset(struct ishtp_device *dev) | |
756 | { | |
757 | struct pci_dev *pdev = dev->pdev; | |
758 | int rv; | |
ae02e5d4 SP |
759 | uint16_t csr; |
760 | ||
761 | if (!pdev) | |
762 | return -ENODEV; | |
763 | ||
764 | rv = pci_reset_function(pdev); | |
765 | if (!rv) | |
766 | dev->dev_state = ISHTP_DEV_RESETTING; | |
767 | ||
768 | if (!pdev->pm_cap) { | |
769 | dev_err(&pdev->dev, "Can't reset - no PM caps\n"); | |
770 | return -EINVAL; | |
771 | } | |
772 | ||
8b2979fe EX |
773 | /* Disable dma communication between FW and host */ |
774 | if (ish_disable_dma(dev)) { | |
ae02e5d4 SP |
775 | dev_err(&pdev->dev, |
776 | "Can't reset - stuck with DMA in-progress\n"); | |
777 | return -EBUSY; | |
778 | } | |
779 | ||
780 | pci_read_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, &csr); | |
781 | ||
782 | csr &= ~PCI_PM_CTRL_STATE_MASK; | |
783 | csr |= PCI_D3hot; | |
784 | pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr); | |
785 | ||
786 | mdelay(pdev->d3_delay); | |
787 | ||
788 | csr &= ~PCI_PM_CTRL_STATE_MASK; | |
789 | csr |= PCI_D0; | |
790 | pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr); | |
791 | ||
c2ed83f5 EX |
792 | /* Now we can enable ISH DMA operation and wakeup ISHFW */ |
793 | ish_wakeup(dev); | |
ae02e5d4 SP |
794 | |
795 | return 0; | |
796 | } | |
797 | ||
798 | /** | |
799 | * _ish_ipc_reset() - IPC reset | |
800 | * @dev: ishtp device pointer | |
801 | * | |
802 | * Resets host and fw IPC and upper layers | |
803 | * | |
804 | * Return: 0 for success else error fault code | |
805 | */ | |
806 | static int _ish_ipc_reset(struct ishtp_device *dev) | |
807 | { | |
808 | struct ipc_rst_payload_type ipc_mng_msg; | |
809 | int rv = 0; | |
810 | ||
811 | ipc_mng_msg.reset_id = 1; | |
812 | ipc_mng_msg.reserved = 0; | |
813 | ||
814 | set_host_ready(dev); | |
815 | ||
816 | /* Clear the incoming doorbell */ | |
817 | ish_reg_write(dev, IPC_REG_ISH2HOST_DRBL, 0); | |
818 | /* Flush write to doorbell */ | |
819 | ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); | |
820 | ||
821 | dev->recvd_hw_ready = 0; | |
822 | ||
823 | /* send message */ | |
824 | rv = ipc_send_mng_msg(dev, MNG_RESET_NOTIFY, &ipc_mng_msg, | |
825 | sizeof(struct ipc_rst_payload_type)); | |
826 | if (rv) { | |
827 | dev_err(dev->devc, "Failed to send IPC MNG_RESET_NOTIFY\n"); | |
828 | return rv; | |
829 | } | |
830 | ||
831 | wait_event_interruptible_timeout(dev->wait_hw_ready, | |
832 | dev->recvd_hw_ready, 2 * HZ); | |
833 | if (!dev->recvd_hw_ready) { | |
834 | dev_err(dev->devc, "Timed out waiting for HW ready\n"); | |
835 | rv = -ENODEV; | |
836 | } | |
837 | ||
838 | return rv; | |
839 | } | |
840 | ||
841 | /** | |
842 | * ish_hw_start() -Start ISH HW | |
843 | * @dev: ishtp device pointer | |
844 | * | |
845 | * Set host to ready state and wait for FW reset | |
846 | * | |
847 | * Return: 0 for success else error fault code | |
848 | */ | |
849 | int ish_hw_start(struct ishtp_device *dev) | |
850 | { | |
851 | ish_set_host_rdy(dev); | |
ae02e5d4 | 852 | |
c2ed83f5 EX |
853 | /* After that we can enable ISH DMA operation and wakeup ISHFW */ |
854 | ish_wakeup(dev); | |
ae02e5d4 SP |
855 | |
856 | set_host_ready(dev); | |
857 | ||
858 | /* wait for FW-initiated reset flow */ | |
859 | if (!dev->recvd_hw_ready) | |
860 | wait_event_interruptible_timeout(dev->wait_hw_ready, | |
861 | dev->recvd_hw_ready, | |
862 | 10 * HZ); | |
863 | ||
864 | if (!dev->recvd_hw_ready) { | |
865 | dev_err(dev->devc, | |
866 | "[ishtp-ish]: Timed out waiting for FW-initiated reset\n"); | |
867 | return -ENODEV; | |
868 | } | |
869 | ||
870 | return 0; | |
871 | } | |
872 | ||
873 | /** | |
874 | * ish_ipc_get_header() -Get doorbell value | |
875 | * @dev: ishtp device pointer | |
876 | * @length: length of message | |
877 | * @busy: busy status | |
878 | * | |
879 | * Get door bell value from message header | |
880 | * | |
881 | * Return: door bell value | |
882 | */ | |
883 | static uint32_t ish_ipc_get_header(struct ishtp_device *dev, int length, | |
884 | int busy) | |
885 | { | |
886 | uint32_t drbl_val; | |
887 | ||
888 | drbl_val = IPC_BUILD_HEADER(length, IPC_PROTOCOL_ISHTP, busy); | |
889 | ||
890 | return drbl_val; | |
891 | } | |
892 | ||
893 | static const struct ishtp_hw_ops ish_hw_ops = { | |
894 | .hw_reset = _ish_hw_reset, | |
895 | .ipc_reset = _ish_ipc_reset, | |
896 | .ipc_get_header = ish_ipc_get_header, | |
897 | .ishtp_read = _ishtp_read, | |
898 | .write = write_ipc_to_queue, | |
899 | .get_fw_status = _ish_read_fw_sts_reg, | |
900 | .sync_fw_clock = _ish_sync_fw_clock, | |
901 | .ishtp_read_hdr = _ishtp_read_hdr | |
902 | }; | |
903 | ||
904 | /** | |
905 | * ish_dev_init() -Initialize ISH devoce | |
906 | * @pdev: PCI device | |
907 | * | |
908 | * Allocate ISHTP device and initialize IPC processing | |
909 | * | |
910 | * Return: ISHTP device instance on success else NULL | |
911 | */ | |
912 | struct ishtp_device *ish_dev_init(struct pci_dev *pdev) | |
913 | { | |
914 | struct ishtp_device *dev; | |
915 | int i; | |
916 | ||
917 | dev = kzalloc(sizeof(struct ishtp_device) + sizeof(struct ish_hw), | |
918 | GFP_KERNEL); | |
919 | if (!dev) | |
920 | return NULL; | |
921 | ||
922 | ishtp_device_init(dev); | |
923 | ||
924 | init_waitqueue_head(&dev->wait_hw_ready); | |
925 | ||
926 | spin_lock_init(&dev->wr_processing_spinlock); | |
927 | spin_lock_init(&dev->out_ipc_spinlock); | |
928 | ||
929 | /* Init IPC processing and free lists */ | |
930 | INIT_LIST_HEAD(&dev->wr_processing_list_head.link); | |
931 | INIT_LIST_HEAD(&dev->wr_free_list_head.link); | |
932 | for (i = 0; i < IPC_TX_FIFO_SIZE; ++i) { | |
933 | struct wr_msg_ctl_info *tx_buf; | |
934 | ||
935 | tx_buf = kzalloc(sizeof(struct wr_msg_ctl_info), GFP_KERNEL); | |
936 | if (!tx_buf) { | |
937 | /* | |
938 | * IPC buffers may be limited or not available | |
939 | * at all - although this shouldn't happen | |
940 | */ | |
941 | dev_err(dev->devc, | |
942 | "[ishtp-ish]: failure in Tx FIFO allocations (%d)\n", | |
943 | i); | |
944 | break; | |
945 | } | |
946 | list_add_tail(&tx_buf->link, &dev->wr_free_list_head.link); | |
947 | } | |
948 | ||
949 | dev->ops = &ish_hw_ops; | |
950 | dev->devc = &pdev->dev; | |
951 | dev->mtu = IPC_PAYLOAD_SIZE - sizeof(struct ishtp_msg_hdr); | |
952 | return dev; | |
953 | } | |
954 | ||
955 | /** | |
956 | * ish_device_disable() - Disable ISH device | |
957 | * @dev: ISHTP device pointer | |
958 | * | |
959 | * Disable ISH by clearing host ready to inform firmware. | |
960 | */ | |
961 | void ish_device_disable(struct ishtp_device *dev) | |
962 | { | |
2a1e3b93 EX |
963 | struct pci_dev *pdev = dev->pdev; |
964 | ||
965 | if (!pdev) | |
966 | return; | |
967 | ||
968 | /* Disable dma communication between FW and host */ | |
969 | if (ish_disable_dma(dev)) { | |
970 | dev_err(&pdev->dev, | |
971 | "Can't reset - stuck with DMA in-progress\n"); | |
972 | return; | |
973 | } | |
974 | ||
975 | /* Put ISH to D3hot state for power saving */ | |
976 | pci_set_power_state(pdev, PCI_D3hot); | |
977 | ||
ae02e5d4 SP |
978 | dev->dev_state = ISHTP_DEV_DISABLED; |
979 | ish_clr_host_rdy(dev); | |
980 | } |