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" | |
22 | #include "utils.h" | |
23 | #include "hbm.h" | |
24 | ||
25 | /* For FW reset flow */ | |
26 | static struct work_struct fw_reset_work; | |
27 | static struct ishtp_device *ishtp_dev; | |
28 | ||
29 | /** | |
30 | * ish_reg_read() - Read register | |
31 | * @dev: ISHTP device pointer | |
32 | * @offset: Register offset | |
33 | * | |
34 | * Read 32 bit register at a given offset | |
35 | * | |
36 | * Return: Read register value | |
37 | */ | |
38 | static inline uint32_t ish_reg_read(const struct ishtp_device *dev, | |
39 | unsigned long offset) | |
40 | { | |
41 | struct ish_hw *hw = to_ish_hw(dev); | |
42 | ||
43 | return readl(hw->mem_addr + offset); | |
44 | } | |
45 | ||
46 | /** | |
47 | * ish_reg_write() - Write register | |
48 | * @dev: ISHTP device pointer | |
49 | * @offset: Register offset | |
50 | * @value: Value to write | |
51 | * | |
52 | * Writes 32 bit register at a give offset | |
53 | */ | |
54 | static inline void ish_reg_write(struct ishtp_device *dev, | |
55 | unsigned long offset, | |
56 | uint32_t value) | |
57 | { | |
58 | struct ish_hw *hw = to_ish_hw(dev); | |
59 | ||
60 | writel(value, hw->mem_addr + offset); | |
61 | } | |
62 | ||
63 | /** | |
64 | * _ish_read_fw_sts_reg() - Read FW status register | |
65 | * @dev: ISHTP device pointer | |
66 | * | |
67 | * Read FW status register | |
68 | * | |
69 | * Return: Read register value | |
70 | */ | |
71 | static inline uint32_t _ish_read_fw_sts_reg(struct ishtp_device *dev) | |
72 | { | |
73 | return ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); | |
74 | } | |
75 | ||
76 | /** | |
77 | * check_generated_interrupt() - Check if ISH interrupt | |
78 | * @dev: ISHTP device pointer | |
79 | * | |
80 | * Check if an interrupt was generated for ISH | |
81 | * | |
82 | * Return: Read true or false | |
83 | */ | |
84 | static bool check_generated_interrupt(struct ishtp_device *dev) | |
85 | { | |
86 | bool interrupt_generated = true; | |
87 | uint32_t pisr_val = 0; | |
88 | ||
89 | if (dev->pdev->device == CHV_DEVICE_ID) { | |
90 | pisr_val = ish_reg_read(dev, IPC_REG_PISR_CHV_AB); | |
91 | interrupt_generated = | |
92 | IPC_INT_FROM_ISH_TO_HOST_CHV_AB(pisr_val); | |
93 | } else { | |
94 | pisr_val = ish_reg_read(dev, IPC_REG_PISR_BXT); | |
95 | interrupt_generated = IPC_INT_FROM_ISH_TO_HOST_BXT(pisr_val); | |
96 | } | |
97 | ||
98 | return interrupt_generated; | |
99 | } | |
100 | ||
101 | /** | |
102 | * ish_is_input_ready() - Check if FW ready for RX | |
103 | * @dev: ISHTP device pointer | |
104 | * | |
105 | * Check if ISH FW is ready for receiving data | |
106 | * | |
107 | * Return: Read true or false | |
108 | */ | |
109 | static bool ish_is_input_ready(struct ishtp_device *dev) | |
110 | { | |
111 | uint32_t doorbell_val; | |
112 | ||
113 | doorbell_val = ish_reg_read(dev, IPC_REG_HOST2ISH_DRBL); | |
114 | return !IPC_IS_BUSY(doorbell_val); | |
115 | } | |
116 | ||
117 | /** | |
118 | * set_host_ready() - Indicate host ready | |
119 | * @dev: ISHTP device pointer | |
120 | * | |
121 | * Set host ready indication to FW | |
122 | */ | |
123 | static void set_host_ready(struct ishtp_device *dev) | |
124 | { | |
125 | if (dev->pdev->device == CHV_DEVICE_ID) { | |
126 | if (dev->pdev->revision == REVISION_ID_CHT_A0 || | |
127 | (dev->pdev->revision & REVISION_ID_SI_MASK) == | |
128 | REVISION_ID_CHT_Ax_SI) | |
129 | ish_reg_write(dev, IPC_REG_HOST_COMM, 0x81); | |
130 | else if (dev->pdev->revision == REVISION_ID_CHT_B0 || | |
131 | (dev->pdev->revision & REVISION_ID_SI_MASK) == | |
132 | REVISION_ID_CHT_Bx_SI || | |
133 | (dev->pdev->revision & REVISION_ID_SI_MASK) == | |
134 | REVISION_ID_CHT_Kx_SI || | |
135 | (dev->pdev->revision & REVISION_ID_SI_MASK) == | |
136 | REVISION_ID_CHT_Dx_SI) { | |
137 | uint32_t host_comm_val; | |
138 | ||
139 | host_comm_val = ish_reg_read(dev, IPC_REG_HOST_COMM); | |
140 | host_comm_val |= IPC_HOSTCOMM_INT_EN_BIT_CHV_AB | 0x81; | |
141 | ish_reg_write(dev, IPC_REG_HOST_COMM, host_comm_val); | |
142 | } | |
143 | } else { | |
144 | uint32_t host_pimr_val; | |
145 | ||
146 | host_pimr_val = ish_reg_read(dev, IPC_REG_PIMR_BXT); | |
147 | host_pimr_val |= IPC_PIMR_INT_EN_BIT_BXT; | |
148 | /* | |
149 | * disable interrupt generated instead of | |
150 | * RX_complete_msg | |
151 | */ | |
152 | host_pimr_val &= ~IPC_HOST2ISH_BUSYCLEAR_MASK_BIT; | |
153 | ||
154 | ish_reg_write(dev, IPC_REG_PIMR_BXT, host_pimr_val); | |
155 | } | |
156 | } | |
157 | ||
158 | /** | |
159 | * ishtp_fw_is_ready() - Check if FW ready | |
160 | * @dev: ISHTP device pointer | |
161 | * | |
162 | * Check if ISH FW is ready | |
163 | * | |
164 | * Return: Read true or false | |
165 | */ | |
166 | static bool ishtp_fw_is_ready(struct ishtp_device *dev) | |
167 | { | |
168 | uint32_t ish_status = _ish_read_fw_sts_reg(dev); | |
169 | ||
170 | return IPC_IS_ISH_ILUP(ish_status) && | |
171 | IPC_IS_ISH_ISHTP_READY(ish_status); | |
172 | } | |
173 | ||
174 | /** | |
175 | * ish_set_host_rdy() - Indicate host ready | |
176 | * @dev: ISHTP device pointer | |
177 | * | |
178 | * Set host ready indication to FW | |
179 | */ | |
180 | static void ish_set_host_rdy(struct ishtp_device *dev) | |
181 | { | |
182 | uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM); | |
183 | ||
184 | IPC_SET_HOST_READY(host_status); | |
185 | ish_reg_write(dev, IPC_REG_HOST_COMM, host_status); | |
186 | } | |
187 | ||
188 | /** | |
189 | * ish_clr_host_rdy() - Indicate host not ready | |
190 | * @dev: ISHTP device pointer | |
191 | * | |
192 | * Send host not ready indication to FW | |
193 | */ | |
194 | static void ish_clr_host_rdy(struct ishtp_device *dev) | |
195 | { | |
196 | uint32_t host_status = ish_reg_read(dev, IPC_REG_HOST_COMM); | |
197 | ||
198 | IPC_CLEAR_HOST_READY(host_status); | |
199 | ish_reg_write(dev, IPC_REG_HOST_COMM, host_status); | |
200 | } | |
201 | ||
202 | /** | |
203 | * _ishtp_read_hdr() - Read message header | |
204 | * @dev: ISHTP device pointer | |
205 | * | |
206 | * Read header of 32bit length | |
207 | * | |
208 | * Return: Read register value | |
209 | */ | |
210 | static uint32_t _ishtp_read_hdr(const struct ishtp_device *dev) | |
211 | { | |
212 | return ish_reg_read(dev, IPC_REG_ISH2HOST_MSG); | |
213 | } | |
214 | ||
215 | /** | |
216 | * _ishtp_read - Read message | |
217 | * @dev: ISHTP device pointer | |
218 | * @buffer: message buffer | |
219 | * @buffer_length: length of message buffer | |
220 | * | |
221 | * Read message from FW | |
222 | * | |
223 | * Return: Always 0 | |
224 | */ | |
225 | static int _ishtp_read(struct ishtp_device *dev, unsigned char *buffer, | |
226 | unsigned long buffer_length) | |
227 | { | |
228 | uint32_t i; | |
229 | uint32_t *r_buf = (uint32_t *)buffer; | |
230 | uint32_t msg_offs; | |
231 | ||
232 | msg_offs = IPC_REG_ISH2HOST_MSG + sizeof(struct ishtp_msg_hdr); | |
233 | for (i = 0; i < buffer_length; i += sizeof(uint32_t)) | |
234 | *r_buf++ = ish_reg_read(dev, msg_offs + i); | |
235 | ||
236 | return 0; | |
237 | } | |
238 | ||
239 | /** | |
240 | * write_ipc_from_queue() - try to write ipc msg from Tx queue to device | |
241 | * @dev: ishtp device pointer | |
242 | * | |
243 | * Check if DRBL is cleared. if it is - write the first IPC msg, then call | |
244 | * the callback function (unless it's NULL) | |
245 | * | |
246 | * Return: 0 for success else failure code | |
247 | */ | |
248 | static int write_ipc_from_queue(struct ishtp_device *dev) | |
249 | { | |
250 | struct wr_msg_ctl_info *ipc_link; | |
251 | unsigned long length; | |
252 | unsigned long rem; | |
253 | unsigned long flags; | |
254 | uint32_t doorbell_val; | |
255 | uint32_t *r_buf; | |
256 | uint32_t reg_addr; | |
257 | int i; | |
258 | void (*ipc_send_compl)(void *); | |
259 | void *ipc_send_compl_prm; | |
260 | static int out_ipc_locked; | |
261 | unsigned long out_ipc_flags; | |
262 | ||
263 | if (dev->dev_state == ISHTP_DEV_DISABLED) | |
264 | return -EINVAL; | |
265 | ||
266 | spin_lock_irqsave(&dev->out_ipc_spinlock, out_ipc_flags); | |
267 | if (out_ipc_locked) { | |
268 | spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags); | |
269 | return -EBUSY; | |
270 | } | |
271 | out_ipc_locked = 1; | |
272 | if (!ish_is_input_ready(dev)) { | |
273 | out_ipc_locked = 0; | |
274 | spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags); | |
275 | return -EBUSY; | |
276 | } | |
277 | spin_unlock_irqrestore(&dev->out_ipc_spinlock, out_ipc_flags); | |
278 | ||
279 | spin_lock_irqsave(&dev->wr_processing_spinlock, flags); | |
280 | /* | |
281 | * if tx send list is empty - return 0; | |
282 | * may happen, as RX_COMPLETE handler doesn't check list emptiness. | |
283 | */ | |
284 | if (list_empty(&dev->wr_processing_list_head.link)) { | |
285 | spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); | |
286 | out_ipc_locked = 0; | |
287 | return 0; | |
288 | } | |
289 | ||
290 | ipc_link = list_entry(dev->wr_processing_list_head.link.next, | |
291 | struct wr_msg_ctl_info, link); | |
292 | /* first 4 bytes of the data is the doorbell value (IPC header) */ | |
293 | length = ipc_link->length - sizeof(uint32_t); | |
294 | doorbell_val = *(uint32_t *)ipc_link->inline_data; | |
295 | r_buf = (uint32_t *)(ipc_link->inline_data + sizeof(uint32_t)); | |
296 | ||
297 | /* If sending MNG_SYNC_FW_CLOCK, update clock again */ | |
298 | if (IPC_HEADER_GET_PROTOCOL(doorbell_val) == IPC_PROTOCOL_MNG && | |
299 | IPC_HEADER_GET_MNG_CMD(doorbell_val) == MNG_SYNC_FW_CLOCK) { | |
300 | struct timespec ts_system; | |
301 | struct timeval tv_utc; | |
302 | uint64_t usec_system, usec_utc; | |
303 | struct ipc_time_update_msg time_update; | |
304 | struct time_sync_format ts_format; | |
305 | ||
306 | get_monotonic_boottime(&ts_system); | |
307 | do_gettimeofday(&tv_utc); | |
308 | usec_system = (timespec_to_ns(&ts_system)) / NSEC_PER_USEC; | |
309 | usec_utc = (uint64_t)tv_utc.tv_sec * 1000000 + | |
310 | ((uint32_t)tv_utc.tv_usec); | |
311 | ts_format.ts1_source = HOST_SYSTEM_TIME_USEC; | |
312 | ts_format.ts2_source = HOST_UTC_TIME_USEC; | |
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 | ||
430 | /** | |
431 | * ish_fw_reset_handler() - FW reset handler | |
432 | * @dev: ishtp device pointer | |
433 | * | |
434 | * Handle FW reset | |
435 | * | |
436 | * Return: 0 for success else failure code | |
437 | */ | |
438 | static int ish_fw_reset_handler(struct ishtp_device *dev) | |
439 | { | |
440 | uint32_t reset_id; | |
441 | unsigned long flags; | |
442 | struct wr_msg_ctl_info *processing, *next; | |
443 | ||
444 | /* Read reset ID */ | |
445 | reset_id = ish_reg_read(dev, IPC_REG_ISH2HOST_MSG) & 0xFFFF; | |
446 | ||
447 | /* Clear IPC output queue */ | |
448 | spin_lock_irqsave(&dev->wr_processing_spinlock, flags); | |
449 | list_for_each_entry_safe(processing, next, | |
450 | &dev->wr_processing_list_head.link, link) { | |
67c0fe42 | 451 | list_move_tail(&processing->link, &dev->wr_free_list_head.link); |
ae02e5d4 SP |
452 | } |
453 | spin_unlock_irqrestore(&dev->wr_processing_spinlock, flags); | |
454 | ||
455 | /* ISHTP notification in IPC_RESET */ | |
456 | ishtp_reset_handler(dev); | |
457 | ||
458 | if (!ish_is_input_ready(dev)) | |
459 | timed_wait_for_timeout(WAIT_FOR_SEND_SLICE, | |
460 | ish_is_input_ready(dev), (2 * HZ)); | |
461 | ||
462 | /* ISH FW is dead */ | |
463 | if (!ish_is_input_ready(dev)) | |
464 | return -EPIPE; | |
465 | /* | |
466 | * Set HOST2ISH.ILUP. Apparently we need this BEFORE sending | |
467 | * RESET_NOTIFY_ACK - FW will be checking for it | |
468 | */ | |
469 | ish_set_host_rdy(dev); | |
470 | /* Send RESET_NOTIFY_ACK (with reset_id) */ | |
471 | ipc_send_mng_msg(dev, MNG_RESET_NOTIFY_ACK, &reset_id, | |
472 | sizeof(uint32_t)); | |
473 | ||
474 | /* Wait for ISH FW'es ILUP and ISHTP_READY */ | |
475 | timed_wait_for_timeout(WAIT_FOR_SEND_SLICE, ishtp_fw_is_ready(dev), | |
476 | (2 * HZ)); | |
477 | if (!ishtp_fw_is_ready(dev)) { | |
478 | /* ISH FW is dead */ | |
479 | uint32_t ish_status; | |
480 | ||
481 | ish_status = _ish_read_fw_sts_reg(dev); | |
482 | dev_err(dev->devc, | |
483 | "[ishtp-ish]: completed reset, ISH is dead (FWSTS = %08X)\n", | |
484 | ish_status); | |
485 | return -ENODEV; | |
486 | } | |
487 | return 0; | |
488 | } | |
489 | ||
490 | /** | |
491 | * ish_fw_reset_work_fn() - FW reset worker function | |
492 | * @unused: not used | |
493 | * | |
494 | * Call ish_fw_reset_handler to complete FW reset | |
495 | */ | |
496 | static void fw_reset_work_fn(struct work_struct *unused) | |
497 | { | |
498 | int rv; | |
499 | ||
500 | rv = ish_fw_reset_handler(ishtp_dev); | |
501 | if (!rv) { | |
502 | /* ISH is ILUP & ISHTP-ready. Restart ISHTP */ | |
503 | schedule_timeout(HZ / 3); | |
504 | ishtp_dev->recvd_hw_ready = 1; | |
505 | wake_up_interruptible(&ishtp_dev->wait_hw_ready); | |
506 | ||
507 | /* ISHTP notification in IPC_RESET sequence completion */ | |
508 | ishtp_reset_compl_handler(ishtp_dev); | |
509 | } else | |
510 | dev_err(ishtp_dev->devc, "[ishtp-ish]: FW reset failed (%d)\n", | |
511 | rv); | |
512 | } | |
513 | ||
514 | /** | |
515 | * _ish_sync_fw_clock() -Sync FW clock with the OS clock | |
516 | * @dev: ishtp device pointer | |
517 | * | |
518 | * Sync FW and OS time | |
519 | */ | |
520 | static void _ish_sync_fw_clock(struct ishtp_device *dev) | |
521 | { | |
522 | static unsigned long prev_sync; | |
523 | struct timespec ts; | |
524 | uint64_t usec; | |
525 | ||
526 | if (prev_sync && jiffies - prev_sync < 20 * HZ) | |
527 | return; | |
528 | ||
529 | prev_sync = jiffies; | |
530 | get_monotonic_boottime(&ts); | |
531 | usec = (timespec_to_ns(&ts)) / NSEC_PER_USEC; | |
532 | ipc_send_mng_msg(dev, MNG_SYNC_FW_CLOCK, &usec, sizeof(uint64_t)); | |
533 | } | |
534 | ||
535 | /** | |
536 | * recv_ipc() - Receive and process IPC management messages | |
537 | * @dev: ishtp device instance | |
538 | * @doorbell_val: doorbell value | |
539 | * | |
540 | * This function runs in ISR context. | |
541 | * NOTE: Any other mng command than reset_notify and reset_notify_ack | |
542 | * won't wake BH handler | |
543 | */ | |
544 | static void recv_ipc(struct ishtp_device *dev, uint32_t doorbell_val) | |
545 | { | |
546 | uint32_t mng_cmd; | |
547 | ||
548 | mng_cmd = IPC_HEADER_GET_MNG_CMD(doorbell_val); | |
549 | ||
550 | switch (mng_cmd) { | |
551 | default: | |
552 | break; | |
553 | ||
554 | case MNG_RX_CMPL_INDICATION: | |
555 | if (dev->suspend_flag) { | |
556 | dev->suspend_flag = 0; | |
557 | wake_up_interruptible(&dev->suspend_wait); | |
558 | } | |
559 | if (dev->resume_flag) { | |
560 | dev->resume_flag = 0; | |
561 | wake_up_interruptible(&dev->resume_wait); | |
562 | } | |
563 | ||
564 | write_ipc_from_queue(dev); | |
565 | break; | |
566 | ||
567 | case MNG_RESET_NOTIFY: | |
568 | if (!ishtp_dev) { | |
569 | ishtp_dev = dev; | |
570 | INIT_WORK(&fw_reset_work, fw_reset_work_fn); | |
571 | } | |
572 | schedule_work(&fw_reset_work); | |
573 | break; | |
574 | ||
575 | case MNG_RESET_NOTIFY_ACK: | |
576 | dev->recvd_hw_ready = 1; | |
577 | wake_up_interruptible(&dev->wait_hw_ready); | |
578 | break; | |
579 | } | |
580 | } | |
581 | ||
582 | /** | |
583 | * ish_irq_handler() - ISH IRQ handler | |
584 | * @irq: irq number | |
585 | * @dev_id: ishtp device pointer | |
586 | * | |
587 | * ISH IRQ handler. If interrupt is generated and is for ISH it will process | |
588 | * the interrupt. | |
589 | */ | |
590 | irqreturn_t ish_irq_handler(int irq, void *dev_id) | |
591 | { | |
592 | struct ishtp_device *dev = dev_id; | |
593 | uint32_t doorbell_val; | |
594 | bool interrupt_generated; | |
595 | ||
596 | /* Check that it's interrupt from ISH (may be shared) */ | |
597 | interrupt_generated = check_generated_interrupt(dev); | |
598 | ||
599 | if (!interrupt_generated) | |
600 | return IRQ_NONE; | |
601 | ||
602 | doorbell_val = ish_reg_read(dev, IPC_REG_ISH2HOST_DRBL); | |
603 | if (!IPC_IS_BUSY(doorbell_val)) | |
604 | return IRQ_HANDLED; | |
605 | ||
606 | if (dev->dev_state == ISHTP_DEV_DISABLED) | |
607 | return IRQ_HANDLED; | |
608 | ||
609 | /* Sanity check: IPC dgram length in header */ | |
610 | if (IPC_HEADER_GET_LENGTH(doorbell_val) > IPC_PAYLOAD_SIZE) { | |
611 | dev_err(dev->devc, | |
612 | "IPC hdr - bad length: %u; dropped\n", | |
613 | (unsigned int)IPC_HEADER_GET_LENGTH(doorbell_val)); | |
614 | goto eoi; | |
615 | } | |
616 | ||
617 | switch (IPC_HEADER_GET_PROTOCOL(doorbell_val)) { | |
618 | default: | |
619 | break; | |
620 | case IPC_PROTOCOL_MNG: | |
621 | recv_ipc(dev, doorbell_val); | |
622 | break; | |
623 | case IPC_PROTOCOL_ISHTP: | |
624 | ishtp_recv(dev); | |
625 | break; | |
626 | } | |
627 | ||
628 | eoi: | |
629 | /* Update IPC counters */ | |
630 | ++dev->ipc_rx_cnt; | |
631 | dev->ipc_rx_bytes_cnt += IPC_HEADER_GET_LENGTH(doorbell_val); | |
632 | ||
633 | ish_reg_write(dev, IPC_REG_ISH2HOST_DRBL, 0); | |
634 | /* Flush write to doorbell */ | |
635 | ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); | |
636 | ||
637 | return IRQ_HANDLED; | |
638 | } | |
639 | ||
640 | /** | |
641 | * _ish_hw_reset() - HW reset | |
642 | * @dev: ishtp device pointer | |
643 | * | |
644 | * Reset ISH HW to recover if any error | |
645 | * | |
646 | * Return: 0 for success else error fault code | |
647 | */ | |
648 | static int _ish_hw_reset(struct ishtp_device *dev) | |
649 | { | |
650 | struct pci_dev *pdev = dev->pdev; | |
651 | int rv; | |
652 | unsigned int dma_delay; | |
653 | uint16_t csr; | |
654 | ||
655 | if (!pdev) | |
656 | return -ENODEV; | |
657 | ||
658 | rv = pci_reset_function(pdev); | |
659 | if (!rv) | |
660 | dev->dev_state = ISHTP_DEV_RESETTING; | |
661 | ||
662 | if (!pdev->pm_cap) { | |
663 | dev_err(&pdev->dev, "Can't reset - no PM caps\n"); | |
664 | return -EINVAL; | |
665 | } | |
666 | ||
667 | /* Now trigger reset to FW */ | |
668 | ish_reg_write(dev, IPC_REG_ISH_RMP2, 0); | |
669 | ||
670 | for (dma_delay = 0; dma_delay < MAX_DMA_DELAY && | |
671 | _ish_read_fw_sts_reg(dev) & (IPC_ISH_IN_DMA); | |
672 | dma_delay += 5) | |
673 | mdelay(5); | |
674 | ||
675 | if (dma_delay >= MAX_DMA_DELAY) { | |
676 | dev_err(&pdev->dev, | |
677 | "Can't reset - stuck with DMA in-progress\n"); | |
678 | return -EBUSY; | |
679 | } | |
680 | ||
681 | pci_read_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, &csr); | |
682 | ||
683 | csr &= ~PCI_PM_CTRL_STATE_MASK; | |
684 | csr |= PCI_D3hot; | |
685 | pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr); | |
686 | ||
687 | mdelay(pdev->d3_delay); | |
688 | ||
689 | csr &= ~PCI_PM_CTRL_STATE_MASK; | |
690 | csr |= PCI_D0; | |
691 | pci_write_config_word(pdev, pdev->pm_cap + PCI_PM_CTRL, csr); | |
692 | ||
693 | ish_reg_write(dev, IPC_REG_ISH_RMP2, IPC_RMP2_DMA_ENABLED); | |
694 | ||
695 | /* | |
696 | * Send 0 IPC message so that ISH FW wakes up if it was already | |
697 | * asleep | |
698 | */ | |
699 | ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, IPC_DRBL_BUSY_BIT); | |
700 | ||
701 | /* Flush writes to doorbell and REMAP2 */ | |
702 | ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); | |
703 | ||
704 | return 0; | |
705 | } | |
706 | ||
707 | /** | |
708 | * _ish_ipc_reset() - IPC reset | |
709 | * @dev: ishtp device pointer | |
710 | * | |
711 | * Resets host and fw IPC and upper layers | |
712 | * | |
713 | * Return: 0 for success else error fault code | |
714 | */ | |
715 | static int _ish_ipc_reset(struct ishtp_device *dev) | |
716 | { | |
717 | struct ipc_rst_payload_type ipc_mng_msg; | |
718 | int rv = 0; | |
719 | ||
720 | ipc_mng_msg.reset_id = 1; | |
721 | ipc_mng_msg.reserved = 0; | |
722 | ||
723 | set_host_ready(dev); | |
724 | ||
725 | /* Clear the incoming doorbell */ | |
726 | ish_reg_write(dev, IPC_REG_ISH2HOST_DRBL, 0); | |
727 | /* Flush write to doorbell */ | |
728 | ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); | |
729 | ||
730 | dev->recvd_hw_ready = 0; | |
731 | ||
732 | /* send message */ | |
733 | rv = ipc_send_mng_msg(dev, MNG_RESET_NOTIFY, &ipc_mng_msg, | |
734 | sizeof(struct ipc_rst_payload_type)); | |
735 | if (rv) { | |
736 | dev_err(dev->devc, "Failed to send IPC MNG_RESET_NOTIFY\n"); | |
737 | return rv; | |
738 | } | |
739 | ||
740 | wait_event_interruptible_timeout(dev->wait_hw_ready, | |
741 | dev->recvd_hw_ready, 2 * HZ); | |
742 | if (!dev->recvd_hw_ready) { | |
743 | dev_err(dev->devc, "Timed out waiting for HW ready\n"); | |
744 | rv = -ENODEV; | |
745 | } | |
746 | ||
747 | return rv; | |
748 | } | |
749 | ||
750 | /** | |
751 | * ish_hw_start() -Start ISH HW | |
752 | * @dev: ishtp device pointer | |
753 | * | |
754 | * Set host to ready state and wait for FW reset | |
755 | * | |
756 | * Return: 0 for success else error fault code | |
757 | */ | |
758 | int ish_hw_start(struct ishtp_device *dev) | |
759 | { | |
760 | ish_set_host_rdy(dev); | |
761 | /* After that we can enable ISH DMA operation */ | |
762 | ish_reg_write(dev, IPC_REG_ISH_RMP2, IPC_RMP2_DMA_ENABLED); | |
763 | ||
764 | /* | |
765 | * Send 0 IPC message so that ISH FW wakes up if it was already | |
766 | * asleep | |
767 | */ | |
768 | ish_reg_write(dev, IPC_REG_HOST2ISH_DRBL, IPC_DRBL_BUSY_BIT); | |
769 | /* Flush write to doorbell */ | |
770 | ish_reg_read(dev, IPC_REG_ISH_HOST_FWSTS); | |
771 | ||
772 | set_host_ready(dev); | |
773 | ||
774 | /* wait for FW-initiated reset flow */ | |
775 | if (!dev->recvd_hw_ready) | |
776 | wait_event_interruptible_timeout(dev->wait_hw_ready, | |
777 | dev->recvd_hw_ready, | |
778 | 10 * HZ); | |
779 | ||
780 | if (!dev->recvd_hw_ready) { | |
781 | dev_err(dev->devc, | |
782 | "[ishtp-ish]: Timed out waiting for FW-initiated reset\n"); | |
783 | return -ENODEV; | |
784 | } | |
785 | ||
786 | return 0; | |
787 | } | |
788 | ||
789 | /** | |
790 | * ish_ipc_get_header() -Get doorbell value | |
791 | * @dev: ishtp device pointer | |
792 | * @length: length of message | |
793 | * @busy: busy status | |
794 | * | |
795 | * Get door bell value from message header | |
796 | * | |
797 | * Return: door bell value | |
798 | */ | |
799 | static uint32_t ish_ipc_get_header(struct ishtp_device *dev, int length, | |
800 | int busy) | |
801 | { | |
802 | uint32_t drbl_val; | |
803 | ||
804 | drbl_val = IPC_BUILD_HEADER(length, IPC_PROTOCOL_ISHTP, busy); | |
805 | ||
806 | return drbl_val; | |
807 | } | |
808 | ||
809 | static const struct ishtp_hw_ops ish_hw_ops = { | |
810 | .hw_reset = _ish_hw_reset, | |
811 | .ipc_reset = _ish_ipc_reset, | |
812 | .ipc_get_header = ish_ipc_get_header, | |
813 | .ishtp_read = _ishtp_read, | |
814 | .write = write_ipc_to_queue, | |
815 | .get_fw_status = _ish_read_fw_sts_reg, | |
816 | .sync_fw_clock = _ish_sync_fw_clock, | |
817 | .ishtp_read_hdr = _ishtp_read_hdr | |
818 | }; | |
819 | ||
820 | /** | |
821 | * ish_dev_init() -Initialize ISH devoce | |
822 | * @pdev: PCI device | |
823 | * | |
824 | * Allocate ISHTP device and initialize IPC processing | |
825 | * | |
826 | * Return: ISHTP device instance on success else NULL | |
827 | */ | |
828 | struct ishtp_device *ish_dev_init(struct pci_dev *pdev) | |
829 | { | |
830 | struct ishtp_device *dev; | |
831 | int i; | |
832 | ||
833 | dev = kzalloc(sizeof(struct ishtp_device) + sizeof(struct ish_hw), | |
834 | GFP_KERNEL); | |
835 | if (!dev) | |
836 | return NULL; | |
837 | ||
838 | ishtp_device_init(dev); | |
839 | ||
840 | init_waitqueue_head(&dev->wait_hw_ready); | |
841 | ||
842 | spin_lock_init(&dev->wr_processing_spinlock); | |
843 | spin_lock_init(&dev->out_ipc_spinlock); | |
844 | ||
845 | /* Init IPC processing and free lists */ | |
846 | INIT_LIST_HEAD(&dev->wr_processing_list_head.link); | |
847 | INIT_LIST_HEAD(&dev->wr_free_list_head.link); | |
848 | for (i = 0; i < IPC_TX_FIFO_SIZE; ++i) { | |
849 | struct wr_msg_ctl_info *tx_buf; | |
850 | ||
851 | tx_buf = kzalloc(sizeof(struct wr_msg_ctl_info), GFP_KERNEL); | |
852 | if (!tx_buf) { | |
853 | /* | |
854 | * IPC buffers may be limited or not available | |
855 | * at all - although this shouldn't happen | |
856 | */ | |
857 | dev_err(dev->devc, | |
858 | "[ishtp-ish]: failure in Tx FIFO allocations (%d)\n", | |
859 | i); | |
860 | break; | |
861 | } | |
862 | list_add_tail(&tx_buf->link, &dev->wr_free_list_head.link); | |
863 | } | |
864 | ||
865 | dev->ops = &ish_hw_ops; | |
866 | dev->devc = &pdev->dev; | |
867 | dev->mtu = IPC_PAYLOAD_SIZE - sizeof(struct ishtp_msg_hdr); | |
868 | return dev; | |
869 | } | |
870 | ||
871 | /** | |
872 | * ish_device_disable() - Disable ISH device | |
873 | * @dev: ISHTP device pointer | |
874 | * | |
875 | * Disable ISH by clearing host ready to inform firmware. | |
876 | */ | |
877 | void ish_device_disable(struct ishtp_device *dev) | |
878 | { | |
879 | dev->dev_state = ISHTP_DEV_DISABLED; | |
880 | ish_clr_host_rdy(dev); | |
881 | } |