xref: /linux/drivers/misc/issei/main.c (revision 93e4b3076b5f2d853462b9777d083c77fc0b7b23)
1*7bd4b999SAlexander Usyskin // SPDX-License-Identifier: GPL-2.0
2*7bd4b999SAlexander Usyskin /* Copyright (C) 2023-2026 Intel Corporation */
3*7bd4b999SAlexander Usyskin #include <linux/atomic.h>
4*7bd4b999SAlexander Usyskin #include <linux/device.h>
5*7bd4b999SAlexander Usyskin #include <linux/dev_printk.h>
6*7bd4b999SAlexander Usyskin #include <linux/err.h>
7*7bd4b999SAlexander Usyskin #include <linux/export.h>
8*7bd4b999SAlexander Usyskin #include <linux/jiffies.h>
9*7bd4b999SAlexander Usyskin #include <linux/kthread.h>
10*7bd4b999SAlexander Usyskin #include <linux/slab.h>
11*7bd4b999SAlexander Usyskin #include <linux/wait.h>
12*7bd4b999SAlexander Usyskin 
13*7bd4b999SAlexander Usyskin #include "cdev.h"
14*7bd4b999SAlexander Usyskin #include "fw_client.h"
15*7bd4b999SAlexander Usyskin #include "host_client.h"
16*7bd4b999SAlexander Usyskin #include "ham.h"
17*7bd4b999SAlexander Usyskin #include "issei_dev.h"
18*7bd4b999SAlexander Usyskin 
19*7bd4b999SAlexander Usyskin static void issei_rst_state_set(struct issei_device *idev, enum issei_rst_state state)
20*7bd4b999SAlexander Usyskin {
21*7bd4b999SAlexander Usyskin 	idev->rst_state = state;
22*7bd4b999SAlexander Usyskin 	/* wake up the thread */
23*7bd4b999SAlexander Usyskin 	if (waitqueue_active(&idev->wait_rst_state))
24*7bd4b999SAlexander Usyskin 		wake_up(&idev->wait_rst_state);
25*7bd4b999SAlexander Usyskin }
26*7bd4b999SAlexander Usyskin 
27*7bd4b999SAlexander Usyskin static int issei_reset(struct issei_device *idev)
28*7bd4b999SAlexander Usyskin {
29*7bd4b999SAlexander Usyskin 	int ret;
30*7bd4b999SAlexander Usyskin 
31*7bd4b999SAlexander Usyskin 	idev->ops->irq_clear(idev);
32*7bd4b999SAlexander Usyskin 
33*7bd4b999SAlexander Usyskin 	issei_cl_all_disconnect(idev);
34*7bd4b999SAlexander Usyskin 	issei_fw_cl_remove_all(idev);
35*7bd4b999SAlexander Usyskin 	/* No need to check for overflow here, the counter is used only for info */
36*7bd4b999SAlexander Usyskin 	idev->all_reset_count++;
37*7bd4b999SAlexander Usyskin 	ret = idev->ops->hw_reset(idev, !idev->power_down);
38*7bd4b999SAlexander Usyskin 	issei_dmam_setup(idev);
39*7bd4b999SAlexander Usyskin 	if (ret) {
40*7bd4b999SAlexander Usyskin 		dev_err(&idev->dev, "hw_reset failed ret = %d\n", ret);
41*7bd4b999SAlexander Usyskin 		return ret;
42*7bd4b999SAlexander Usyskin 	}
43*7bd4b999SAlexander Usyskin 
44*7bd4b999SAlexander Usyskin 	if (idev->power_down) {
45*7bd4b999SAlexander Usyskin 		dev_dbg(&idev->dev, "powering down: end of reset\n");
46*7bd4b999SAlexander Usyskin 		issei_rst_state_set(idev, ISSEI_RST_STATE_DISABLED);
47*7bd4b999SAlexander Usyskin 		return -ENODEV;
48*7bd4b999SAlexander Usyskin 	}
49*7bd4b999SAlexander Usyskin 	return 0;
50*7bd4b999SAlexander Usyskin }
51*7bd4b999SAlexander Usyskin 
52*7bd4b999SAlexander Usyskin static int issei_process_read_msg(struct issei_device *idev)
53*7bd4b999SAlexander Usyskin {
54*7bd4b999SAlexander Usyskin 	struct issei_dma_data data = {};
55*7bd4b999SAlexander Usyskin 	int ret;
56*7bd4b999SAlexander Usyskin 
57*7bd4b999SAlexander Usyskin 	ret = issei_dma_read(idev, &data);
58*7bd4b999SAlexander Usyskin 	if (ret)
59*7bd4b999SAlexander Usyskin 		return ret;
60*7bd4b999SAlexander Usyskin 
61*7bd4b999SAlexander Usyskin 	dev_dbg(&idev->dev, "Processing response %u %u %u %u\n", data.fw_id, data.host_id,
62*7bd4b999SAlexander Usyskin 		data.status, data.length);
63*7bd4b999SAlexander Usyskin 	if (data.status != HAMS_SUCCESS) {
64*7bd4b999SAlexander Usyskin 		dev_err(&idev->dev, "Command failed with status 0x%02X", data.status);
65*7bd4b999SAlexander Usyskin 		kfree(data.buf);
66*7bd4b999SAlexander Usyskin 		ret = -EIO;
67*7bd4b999SAlexander Usyskin 	} else {
68*7bd4b999SAlexander Usyskin 		if (issei_is_ham_rsp(data.fw_id, data.host_id))
69*7bd4b999SAlexander Usyskin 			ret = issei_ham_process_ham_rsp(idev, data.buf, data.length);
70*7bd4b999SAlexander Usyskin 		else
71*7bd4b999SAlexander Usyskin 			ret = issei_cl_read_buf(idev, data.fw_id, data.host_id,
72*7bd4b999SAlexander Usyskin 						data.buf, data.length);
73*7bd4b999SAlexander Usyskin 	}
74*7bd4b999SAlexander Usyskin 	idev->ops->irq_write_generate(idev);
75*7bd4b999SAlexander Usyskin 	return ret;
76*7bd4b999SAlexander Usyskin }
77*7bd4b999SAlexander Usyskin 
78*7bd4b999SAlexander Usyskin static int issei_process_write_msg(struct issei_device *idev)
79*7bd4b999SAlexander Usyskin {
80*7bd4b999SAlexander Usyskin 	if (idev->rst_state != ISSEI_RST_STATE_DONE)
81*7bd4b999SAlexander Usyskin 		return 0;
82*7bd4b999SAlexander Usyskin 
83*7bd4b999SAlexander Usyskin 	return issei_cl_write_from_queue(idev);
84*7bd4b999SAlexander Usyskin }
85*7bd4b999SAlexander Usyskin 
86*7bd4b999SAlexander Usyskin static int issei_process_thread(void *_dev)
87*7bd4b999SAlexander Usyskin {
88*7bd4b999SAlexander Usyskin 	long timeout, old_timeout = MAX_SCHEDULE_TIMEOUT;
89*7bd4b999SAlexander Usyskin 	struct issei_device *idev = _dev;
90*7bd4b999SAlexander Usyskin 	int ret;
91*7bd4b999SAlexander Usyskin 
92*7bd4b999SAlexander Usyskin 	while (!kthread_should_stop()) {
93*7bd4b999SAlexander Usyskin 		dev_dbg(&idev->dev, "process_work in %d\n", idev->rst_state);
94*7bd4b999SAlexander Usyskin 		if (!idev->ops->hw_is_ready(idev) && idev->rst_state > ISSEI_RST_STATE_HW_READY) {
95*7bd4b999SAlexander Usyskin 			if (!idev->power_down)
96*7bd4b999SAlexander Usyskin 				dev_dbg(&idev->dev, "HW not ready, resetting\n");
97*7bd4b999SAlexander Usyskin 			idev->rst_state = ISSEI_RST_STATE_INIT;
98*7bd4b999SAlexander Usyskin 		}
99*7bd4b999SAlexander Usyskin 		if (idev->power_down)
100*7bd4b999SAlexander Usyskin 			idev->rst_state = ISSEI_RST_STATE_INIT;
101*7bd4b999SAlexander Usyskin 		WRITE_ONCE(idev->has_data, false);
102*7bd4b999SAlexander Usyskin 		dev_dbg(&idev->dev, "reset_step in %d\n", idev->rst_state);
103*7bd4b999SAlexander Usyskin 		timeout = MAX_SCHEDULE_TIMEOUT;
104*7bd4b999SAlexander Usyskin 		ret = 0;
105*7bd4b999SAlexander Usyskin 		switch (idev->rst_state) {
106*7bd4b999SAlexander Usyskin 		case ISSEI_RST_STATE_DISABLED:
107*7bd4b999SAlexander Usyskin 			if (idev->power_down) {
108*7bd4b999SAlexander Usyskin 				dev_dbg(&idev->dev, "Interrupt in power down?\n");
109*7bd4b999SAlexander Usyskin 				break;
110*7bd4b999SAlexander Usyskin 			}
111*7bd4b999SAlexander Usyskin 			idev->rst_state = ISSEI_RST_STATE_INIT;
112*7bd4b999SAlexander Usyskin 			fallthrough;
113*7bd4b999SAlexander Usyskin 
114*7bd4b999SAlexander Usyskin 		case ISSEI_RST_STATE_INIT:
115*7bd4b999SAlexander Usyskin 			idev->ops->irq_clear(idev);
116*7bd4b999SAlexander Usyskin 			idev->ops->irq_sync(idev);
117*7bd4b999SAlexander Usyskin 
118*7bd4b999SAlexander Usyskin 			if (!idev->power_down) {
119*7bd4b999SAlexander Usyskin 				idev->reset_count++;
120*7bd4b999SAlexander Usyskin 				if (idev->reset_count > ISSEI_MAX_CONSEC_RESET) {
121*7bd4b999SAlexander Usyskin 					dev_err(&idev->dev, "reset: reached maximal consecutive resets: disabling the device\n");
122*7bd4b999SAlexander Usyskin 					issei_rst_state_set(idev, ISSEI_RST_STATE_DISABLED);
123*7bd4b999SAlexander Usyskin 					break;
124*7bd4b999SAlexander Usyskin 				}
125*7bd4b999SAlexander Usyskin 			}
126*7bd4b999SAlexander Usyskin 
127*7bd4b999SAlexander Usyskin 			ret = issei_reset(idev);
128*7bd4b999SAlexander Usyskin 			if (idev->power_down) {
129*7bd4b999SAlexander Usyskin 				dev_dbg(&idev->dev, "Powering down\n");
130*7bd4b999SAlexander Usyskin 				return 0;
131*7bd4b999SAlexander Usyskin 			}
132*7bd4b999SAlexander Usyskin 			if (ret)
133*7bd4b999SAlexander Usyskin 				break;
134*7bd4b999SAlexander Usyskin 
135*7bd4b999SAlexander Usyskin 			idev->rst_state = ISSEI_RST_STATE_HW_READY;
136*7bd4b999SAlexander Usyskin 			timeout = msecs_to_jiffies(ISSEI_RST_HW_READY_TIMEOUT_MSEC);
137*7bd4b999SAlexander Usyskin 			break;
138*7bd4b999SAlexander Usyskin 
139*7bd4b999SAlexander Usyskin 		case ISSEI_RST_STATE_HW_READY:
140*7bd4b999SAlexander Usyskin 			if (!idev->ops->hw_is_ready(idev)) {
141*7bd4b999SAlexander Usyskin 				dev_dbg(&idev->dev, "HW is not ready?\n");
142*7bd4b999SAlexander Usyskin 				timeout = old_timeout;
143*7bd4b999SAlexander Usyskin 				break;
144*7bd4b999SAlexander Usyskin 			}
145*7bd4b999SAlexander Usyskin 
146*7bd4b999SAlexander Usyskin 			dev_dbg(&idev->dev, "HW is ready\n");
147*7bd4b999SAlexander Usyskin 			idev->ops->hw_reset_release(idev);
148*7bd4b999SAlexander Usyskin 			idev->ops->host_set_ready(idev);
149*7bd4b999SAlexander Usyskin 			ret = idev->ops->setup_message_send(idev);
150*7bd4b999SAlexander Usyskin 			if (ret)
151*7bd4b999SAlexander Usyskin 				break;
152*7bd4b999SAlexander Usyskin 
153*7bd4b999SAlexander Usyskin 			idev->rst_state = ISSEI_RST_STATE_SETUP;
154*7bd4b999SAlexander Usyskin 			timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC);
155*7bd4b999SAlexander Usyskin 			break;
156*7bd4b999SAlexander Usyskin 
157*7bd4b999SAlexander Usyskin 		case ISSEI_RST_STATE_SETUP:
158*7bd4b999SAlexander Usyskin 			ret = idev->ops->setup_message_recv(idev);
159*7bd4b999SAlexander Usyskin 			if (ret) {
160*7bd4b999SAlexander Usyskin 				if (ret == -ENODATA) {
161*7bd4b999SAlexander Usyskin 					ret = 0;
162*7bd4b999SAlexander Usyskin 					timeout = old_timeout;
163*7bd4b999SAlexander Usyskin 				}
164*7bd4b999SAlexander Usyskin 			} else {
165*7bd4b999SAlexander Usyskin 				timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC);
166*7bd4b999SAlexander Usyskin 				ret = issei_ham_send_start_req(idev);
167*7bd4b999SAlexander Usyskin 				idev->rst_state = ISSEI_RST_STATE_START;
168*7bd4b999SAlexander Usyskin 			}
169*7bd4b999SAlexander Usyskin 			break;
170*7bd4b999SAlexander Usyskin 
171*7bd4b999SAlexander Usyskin 		case ISSEI_RST_STATE_START:
172*7bd4b999SAlexander Usyskin 			ret = issei_process_read_msg(idev);
173*7bd4b999SAlexander Usyskin 			if (!ret) {
174*7bd4b999SAlexander Usyskin 				timeout = msecs_to_jiffies(ISSEI_RST_STEP_TIMEOUT_MSEC);
175*7bd4b999SAlexander Usyskin 				idev->rst_state = ISSEI_RST_STATE_CLIENT_ENUM;
176*7bd4b999SAlexander Usyskin 			} else if (ret == -ENODATA) {
177*7bd4b999SAlexander Usyskin 				ret = 0;
178*7bd4b999SAlexander Usyskin 				timeout = old_timeout;
179*7bd4b999SAlexander Usyskin 			}
180*7bd4b999SAlexander Usyskin 			break;
181*7bd4b999SAlexander Usyskin 
182*7bd4b999SAlexander Usyskin 		case ISSEI_RST_STATE_CLIENT_ENUM:
183*7bd4b999SAlexander Usyskin 			ret = issei_process_read_msg(idev);
184*7bd4b999SAlexander Usyskin 			if (ret) {
185*7bd4b999SAlexander Usyskin 				if (ret == -ENODATA) {
186*7bd4b999SAlexander Usyskin 					ret = 0;
187*7bd4b999SAlexander Usyskin 					timeout = old_timeout;
188*7bd4b999SAlexander Usyskin 				}
189*7bd4b999SAlexander Usyskin 			} else {
190*7bd4b999SAlexander Usyskin 				idev->reset_count = 0;
191*7bd4b999SAlexander Usyskin 				idev->rst_state = ISSEI_RST_STATE_DONE;
192*7bd4b999SAlexander Usyskin 				dev_dbg(&idev->dev, "Reset finished successfully\n");
193*7bd4b999SAlexander Usyskin 			}
194*7bd4b999SAlexander Usyskin 			break;
195*7bd4b999SAlexander Usyskin 
196*7bd4b999SAlexander Usyskin 		case ISSEI_RST_STATE_DONE:
197*7bd4b999SAlexander Usyskin 			ret = issei_process_read_msg(idev);
198*7bd4b999SAlexander Usyskin 			if (ret != 0 && ret != -ENODATA)
199*7bd4b999SAlexander Usyskin 				break;
200*7bd4b999SAlexander Usyskin 
201*7bd4b999SAlexander Usyskin 			ret = issei_process_write_msg(idev);
202*7bd4b999SAlexander Usyskin 			break;
203*7bd4b999SAlexander Usyskin 		}
204*7bd4b999SAlexander Usyskin 
205*7bd4b999SAlexander Usyskin 		if (ret) {
206*7bd4b999SAlexander Usyskin 			dev_warn(&idev->dev, "Process failed ret = %d\n", ret);
207*7bd4b999SAlexander Usyskin 			idev->rst_state = ISSEI_RST_STATE_INIT;
208*7bd4b999SAlexander Usyskin 			continue;
209*7bd4b999SAlexander Usyskin 		}
210*7bd4b999SAlexander Usyskin 
211*7bd4b999SAlexander Usyskin 		/*
212*7bd4b999SAlexander Usyskin 		 * Every thread that has data to process sets the 'has_data' flag and
213*7bd4b999SAlexander Usyskin 		 * triggers the wait queue.
214*7bd4b999SAlexander Usyskin 		 * The processing thread, in each loop iteration, resets 'has_data'
215*7bd4b999SAlexander Usyskin 		 * and processes all available data.
216*7bd4b999SAlexander Usyskin 		 *
217*7bd4b999SAlexander Usyskin 		 * After processing, the thread waits for 'has_data' to be set again.
218*7bd4b999SAlexander Usyskin 		 *
219*7bd4b999SAlexander Usyskin 		 * If the wait function times out but 'has_data' becomes 1 before
220*7bd4b999SAlexander Usyskin 		 * the subsequent atomic read check, this is acceptable from a flow
221*7bd4b999SAlexander Usyskin 		 * perspective - the thread will continue processing the data.
222*7bd4b999SAlexander Usyskin 		 *
223*7bd4b999SAlexander Usyskin 		 * The 'has_data' flag cannot become 0 between the wait function and
224*7bd4b999SAlexander Usyskin 		 * the atomic read check, since only this thread is allowed to reset it to 0.
225*7bd4b999SAlexander Usyskin 		 */
226*7bd4b999SAlexander Usyskin 
227*7bd4b999SAlexander Usyskin 		old_timeout = wait_event_interruptible_timeout(idev->wait_has_data,
228*7bd4b999SAlexander Usyskin 							       READ_ONCE(idev->has_data),
229*7bd4b999SAlexander Usyskin 							       timeout);
230*7bd4b999SAlexander Usyskin 		if (idev->rst_state == ISSEI_RST_STATE_DISABLED)
231*7bd4b999SAlexander Usyskin 			continue;
232*7bd4b999SAlexander Usyskin 
233*7bd4b999SAlexander Usyskin 		if (!READ_ONCE(idev->has_data)) {
234*7bd4b999SAlexander Usyskin 			dev_warn(&idev->dev, "Timed out at state %d, resetting\n",
235*7bd4b999SAlexander Usyskin 				 idev->rst_state);
236*7bd4b999SAlexander Usyskin 			idev->rst_state = ISSEI_RST_STATE_INIT;
237*7bd4b999SAlexander Usyskin 		}
238*7bd4b999SAlexander Usyskin 	}
239*7bd4b999SAlexander Usyskin 
240*7bd4b999SAlexander Usyskin 	return 0;
241*7bd4b999SAlexander Usyskin }
242*7bd4b999SAlexander Usyskin 
243*7bd4b999SAlexander Usyskin /**
244*7bd4b999SAlexander Usyskin  * issei_start - configure HW device and start processing thread.
245*7bd4b999SAlexander Usyskin  * @idev: the device structure
246*7bd4b999SAlexander Usyskin  *
247*7bd4b999SAlexander Usyskin  * Return: 0 on success, < 0 on failure
248*7bd4b999SAlexander Usyskin  */
249*7bd4b999SAlexander Usyskin int issei_start(struct issei_device *idev)
250*7bd4b999SAlexander Usyskin {
251*7bd4b999SAlexander Usyskin 	int ret;
252*7bd4b999SAlexander Usyskin 
253*7bd4b999SAlexander Usyskin 	idev->power_down = false;
254*7bd4b999SAlexander Usyskin 
255*7bd4b999SAlexander Usyskin 	ret = issei_dmam_setup(idev);
256*7bd4b999SAlexander Usyskin 	if (ret)
257*7bd4b999SAlexander Usyskin 		return ret;
258*7bd4b999SAlexander Usyskin 
259*7bd4b999SAlexander Usyskin 	idev->ops->irq_clear(idev);
260*7bd4b999SAlexander Usyskin 
261*7bd4b999SAlexander Usyskin 	ret = idev->ops->hw_config(idev);
262*7bd4b999SAlexander Usyskin 	if (ret)
263*7bd4b999SAlexander Usyskin 		return ret;
264*7bd4b999SAlexander Usyskin 
265*7bd4b999SAlexander Usyskin 	idev->process_thread = kthread_run(issei_process_thread, idev,
266*7bd4b999SAlexander Usyskin 					   "kisseiprocess/%s", dev_name(&idev->dev));
267*7bd4b999SAlexander Usyskin 	if (IS_ERR(idev->process_thread)) {
268*7bd4b999SAlexander Usyskin 		ret = PTR_ERR(idev->process_thread);
269*7bd4b999SAlexander Usyskin 		dev_err(&idev->dev, "unable to create process thread. ret = %d\n", ret);
270*7bd4b999SAlexander Usyskin 		return ret;
271*7bd4b999SAlexander Usyskin 	}
272*7bd4b999SAlexander Usyskin 
273*7bd4b999SAlexander Usyskin 	issei_poke_process_thread(idev);
274*7bd4b999SAlexander Usyskin 	return 0;
275*7bd4b999SAlexander Usyskin }
276*7bd4b999SAlexander Usyskin EXPORT_SYMBOL_GPL(issei_start);
277*7bd4b999SAlexander Usyskin 
278*7bd4b999SAlexander Usyskin /**
279*7bd4b999SAlexander Usyskin  * issei_stop - stop interrupts and processing thread.
280*7bd4b999SAlexander Usyskin  * @idev: the device structure
281*7bd4b999SAlexander Usyskin  */
282*7bd4b999SAlexander Usyskin void issei_stop(struct issei_device *idev)
283*7bd4b999SAlexander Usyskin {
284*7bd4b999SAlexander Usyskin 	idev->power_down = true;
285*7bd4b999SAlexander Usyskin 
286*7bd4b999SAlexander Usyskin 	idev->ops->irq_clear(idev);
287*7bd4b999SAlexander Usyskin 	idev->ops->irq_sync(idev);
288*7bd4b999SAlexander Usyskin 
289*7bd4b999SAlexander Usyskin 	issei_poke_process_thread(idev);
290*7bd4b999SAlexander Usyskin 
291*7bd4b999SAlexander Usyskin 	wait_event_timeout(idev->wait_rst_state,
292*7bd4b999SAlexander Usyskin 			   (idev->rst_state == ISSEI_RST_STATE_DISABLED),
293*7bd4b999SAlexander Usyskin 			   msecs_to_jiffies(ISSEI_STOP_TIMEOUT_MSEC));
294*7bd4b999SAlexander Usyskin 	kthread_stop(idev->process_thread);
295*7bd4b999SAlexander Usyskin }
296*7bd4b999SAlexander Usyskin EXPORT_SYMBOL_GPL(issei_stop);
297