1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (C) 2014, Samsung Electronics Co. Ltd. All Rights Reserved. |
4 | */ |
5 | |
6 | #include <linux/iio/iio.h> |
7 | #include <linux/interrupt.h> |
8 | #include <linux/io.h> |
9 | #include <linux/mfd/core.h> |
10 | #include <linux/mod_devicetable.h> |
11 | #include <linux/module.h> |
12 | #include <linux/property.h> |
13 | |
14 | #include "ssp.h" |
15 | |
16 | #define SSP_WDT_TIME 10000 |
17 | #define SSP_LIMIT_RESET_CNT 20 |
18 | #define SSP_LIMIT_TIMEOUT_CNT 3 |
19 | |
20 | /* It is possible that it is max clk rate for version 1.0 of bootcode */ |
21 | #define SSP_BOOT_SPI_HZ 400000 |
22 | |
23 | /* |
24 | * These fields can look enigmatic but this structure is used mainly to flat |
25 | * some values and depends on command type. |
26 | */ |
27 | struct ssp_instruction { |
28 | __le32 a; |
29 | __le32 b; |
30 | u8 c; |
31 | } __attribute__((__packed__)); |
32 | |
33 | static const u8 ssp_magnitude_table[] = {110, 85, 171, 71, 203, 195, 0, 67, |
34 | 208, 56, 175, 244, 206, 213, 0, 92, 250, 0, 55, 48, 189, 252, 171, |
35 | 243, 13, 45, 250}; |
36 | |
37 | static const struct ssp_sensorhub_info ssp_rinato_info = { |
38 | .fw_name = "ssp_B2.fw" , |
39 | .fw_crashed_name = "ssp_crashed.fw" , |
40 | .fw_rev = 14052300, |
41 | .mag_table = ssp_magnitude_table, |
42 | .mag_length = ARRAY_SIZE(ssp_magnitude_table), |
43 | }; |
44 | |
45 | static const struct ssp_sensorhub_info ssp_thermostat_info = { |
46 | .fw_name = "thermostat_B2.fw" , |
47 | .fw_crashed_name = "ssp_crashed.fw" , |
48 | .fw_rev = 14080600, |
49 | .mag_table = ssp_magnitude_table, |
50 | .mag_length = ARRAY_SIZE(ssp_magnitude_table), |
51 | }; |
52 | |
53 | static const struct mfd_cell sensorhub_sensor_devs[] = { |
54 | { |
55 | .name = "ssp-accelerometer" , |
56 | }, |
57 | { |
58 | .name = "ssp-gyroscope" , |
59 | }, |
60 | }; |
61 | |
62 | static void ssp_toggle_mcu_reset_gpio(struct ssp_data *data) |
63 | { |
64 | gpiod_set_value(desc: data->mcu_reset_gpiod, value: 0); |
65 | usleep_range(min: 1000, max: 1200); |
66 | gpiod_set_value(desc: data->mcu_reset_gpiod, value: 1); |
67 | msleep(msecs: 50); |
68 | } |
69 | |
70 | static void ssp_sync_available_sensors(struct ssp_data *data) |
71 | { |
72 | int i, ret; |
73 | |
74 | for (i = 0; i < SSP_SENSOR_MAX; ++i) { |
75 | if (data->available_sensors & BIT(i)) { |
76 | ret = ssp_enable_sensor(data, type: i, delay: data->delay_buf[i]); |
77 | if (ret < 0) { |
78 | dev_err(&data->spi->dev, |
79 | "Sync sensor nr: %d fail\n" , i); |
80 | continue; |
81 | } |
82 | } |
83 | } |
84 | |
85 | ret = ssp_command(data, SSP_MSG2SSP_AP_MCU_SET_DUMPMODE, |
86 | arg: data->mcu_dump_mode); |
87 | if (ret < 0) |
88 | dev_err(&data->spi->dev, |
89 | "SSP_MSG2SSP_AP_MCU_SET_DUMPMODE failed\n" ); |
90 | } |
91 | |
92 | static void ssp_enable_mcu(struct ssp_data *data, bool enable) |
93 | { |
94 | dev_info(&data->spi->dev, "current shutdown = %d, old = %d\n" , enable, |
95 | data->shut_down); |
96 | |
97 | if (enable && data->shut_down) { |
98 | data->shut_down = false; |
99 | enable_irq(irq: data->spi->irq); |
100 | enable_irq_wake(irq: data->spi->irq); |
101 | } else if (!enable && !data->shut_down) { |
102 | data->shut_down = true; |
103 | disable_irq(irq: data->spi->irq); |
104 | disable_irq_wake(irq: data->spi->irq); |
105 | } else { |
106 | dev_warn(&data->spi->dev, "current shutdown = %d, old = %d\n" , |
107 | enable, data->shut_down); |
108 | } |
109 | } |
110 | |
111 | /* |
112 | * This function is the first one which communicates with the mcu so it is |
113 | * possible that the first attempt will fail |
114 | */ |
115 | static int ssp_check_fwbl(struct ssp_data *data) |
116 | { |
117 | int retries = 0; |
118 | |
119 | while (retries++ < 5) { |
120 | data->cur_firm_rev = ssp_get_firmware_rev(data); |
121 | if (data->cur_firm_rev == SSP_INVALID_REVISION || |
122 | data->cur_firm_rev == SSP_INVALID_REVISION2) { |
123 | dev_warn(&data->spi->dev, |
124 | "Invalid revision, trying %d time\n" , retries); |
125 | } else { |
126 | break; |
127 | } |
128 | } |
129 | |
130 | if (data->cur_firm_rev == SSP_INVALID_REVISION || |
131 | data->cur_firm_rev == SSP_INVALID_REVISION2) { |
132 | dev_err(&data->spi->dev, "SSP_INVALID_REVISION\n" ); |
133 | return SSP_FW_DL_STATE_NEED_TO_SCHEDULE; |
134 | } |
135 | |
136 | dev_info(&data->spi->dev, |
137 | "MCU Firm Rev : Old = %8u, New = %8u\n" , |
138 | data->cur_firm_rev, |
139 | data->sensorhub_info->fw_rev); |
140 | |
141 | if (data->cur_firm_rev != data->sensorhub_info->fw_rev) |
142 | return SSP_FW_DL_STATE_NEED_TO_SCHEDULE; |
143 | |
144 | return SSP_FW_DL_STATE_NONE; |
145 | } |
146 | |
147 | static void ssp_reset_mcu(struct ssp_data *data) |
148 | { |
149 | ssp_enable_mcu(data, enable: false); |
150 | ssp_clean_pending_list(data); |
151 | ssp_toggle_mcu_reset_gpio(data); |
152 | ssp_enable_mcu(data, enable: true); |
153 | } |
154 | |
155 | static void ssp_wdt_work_func(struct work_struct *work) |
156 | { |
157 | struct ssp_data *data = container_of(work, struct ssp_data, work_wdt); |
158 | |
159 | dev_err(&data->spi->dev, "%s - Sensor state: 0x%x, RC: %u, CC: %u\n" , |
160 | __func__, data->available_sensors, data->reset_cnt, |
161 | data->com_fail_cnt); |
162 | |
163 | ssp_reset_mcu(data); |
164 | data->com_fail_cnt = 0; |
165 | data->timeout_cnt = 0; |
166 | } |
167 | |
168 | static void ssp_wdt_timer_func(struct timer_list *t) |
169 | { |
170 | struct ssp_data *data = from_timer(data, t, wdt_timer); |
171 | |
172 | switch (data->fw_dl_state) { |
173 | case SSP_FW_DL_STATE_FAIL: |
174 | case SSP_FW_DL_STATE_DOWNLOADING: |
175 | case SSP_FW_DL_STATE_SYNC: |
176 | goto _mod; |
177 | } |
178 | |
179 | if (data->timeout_cnt > SSP_LIMIT_TIMEOUT_CNT || |
180 | data->com_fail_cnt > SSP_LIMIT_RESET_CNT) |
181 | queue_work(wq: system_power_efficient_wq, work: &data->work_wdt); |
182 | _mod: |
183 | mod_timer(timer: &data->wdt_timer, expires: jiffies + msecs_to_jiffies(SSP_WDT_TIME)); |
184 | } |
185 | |
186 | static void ssp_enable_wdt_timer(struct ssp_data *data) |
187 | { |
188 | mod_timer(timer: &data->wdt_timer, expires: jiffies + msecs_to_jiffies(SSP_WDT_TIME)); |
189 | } |
190 | |
191 | static void ssp_disable_wdt_timer(struct ssp_data *data) |
192 | { |
193 | del_timer_sync(timer: &data->wdt_timer); |
194 | cancel_work_sync(work: &data->work_wdt); |
195 | } |
196 | |
197 | /** |
198 | * ssp_get_sensor_delay() - gets sensor data acquisition period |
199 | * @data: sensorhub structure |
200 | * @type: SSP sensor type |
201 | * |
202 | * Returns acquisition period in ms |
203 | */ |
204 | u32 ssp_get_sensor_delay(struct ssp_data *data, enum ssp_sensor_type type) |
205 | { |
206 | return data->delay_buf[type]; |
207 | } |
208 | EXPORT_SYMBOL_NS(ssp_get_sensor_delay, IIO_SSP_SENSORS); |
209 | |
210 | /** |
211 | * ssp_enable_sensor() - enables data acquisition for sensor |
212 | * @data: sensorhub structure |
213 | * @type: SSP sensor type |
214 | * @delay: delay in ms |
215 | * |
216 | * Returns 0 or negative value in case of error |
217 | */ |
218 | int ssp_enable_sensor(struct ssp_data *data, enum ssp_sensor_type type, |
219 | u32 delay) |
220 | { |
221 | int ret; |
222 | struct ssp_instruction to_send; |
223 | |
224 | to_send.a = cpu_to_le32(delay); |
225 | to_send.b = cpu_to_le32(data->batch_latency_buf[type]); |
226 | to_send.c = data->batch_opt_buf[type]; |
227 | |
228 | switch (data->check_status[type]) { |
229 | case SSP_INITIALIZATION_STATE: |
230 | /* do calibration step, now just enable */ |
231 | case SSP_ADD_SENSOR_STATE: |
232 | ret = ssp_send_instruction(data, |
233 | SSP_MSG2SSP_INST_BYPASS_SENSOR_ADD, |
234 | sensor_type: type, |
235 | send_buf: (u8 *)&to_send, length: sizeof(to_send)); |
236 | if (ret < 0) { |
237 | dev_err(&data->spi->dev, "Enabling sensor failed\n" ); |
238 | data->check_status[type] = SSP_NO_SENSOR_STATE; |
239 | goto derror; |
240 | } |
241 | |
242 | data->sensor_enable |= BIT(type); |
243 | data->check_status[type] = SSP_RUNNING_SENSOR_STATE; |
244 | break; |
245 | case SSP_RUNNING_SENSOR_STATE: |
246 | ret = ssp_send_instruction(data, |
247 | SSP_MSG2SSP_INST_CHANGE_DELAY, sensor_type: type, |
248 | send_buf: (u8 *)&to_send, length: sizeof(to_send)); |
249 | if (ret < 0) { |
250 | dev_err(&data->spi->dev, |
251 | "Changing sensor delay failed\n" ); |
252 | goto derror; |
253 | } |
254 | break; |
255 | default: |
256 | data->check_status[type] = SSP_ADD_SENSOR_STATE; |
257 | break; |
258 | } |
259 | |
260 | data->delay_buf[type] = delay; |
261 | |
262 | if (atomic_inc_return(v: &data->enable_refcount) == 1) |
263 | ssp_enable_wdt_timer(data); |
264 | |
265 | return 0; |
266 | |
267 | derror: |
268 | return ret; |
269 | } |
270 | EXPORT_SYMBOL_NS(ssp_enable_sensor, IIO_SSP_SENSORS); |
271 | |
272 | /** |
273 | * ssp_change_delay() - changes data acquisition for sensor |
274 | * @data: sensorhub structure |
275 | * @type: SSP sensor type |
276 | * @delay: delay in ms |
277 | * |
278 | * Returns 0 or negative value in case of error |
279 | */ |
280 | int ssp_change_delay(struct ssp_data *data, enum ssp_sensor_type type, |
281 | u32 delay) |
282 | { |
283 | int ret; |
284 | struct ssp_instruction to_send; |
285 | |
286 | to_send.a = cpu_to_le32(delay); |
287 | to_send.b = cpu_to_le32(data->batch_latency_buf[type]); |
288 | to_send.c = data->batch_opt_buf[type]; |
289 | |
290 | ret = ssp_send_instruction(data, SSP_MSG2SSP_INST_CHANGE_DELAY, sensor_type: type, |
291 | send_buf: (u8 *)&to_send, length: sizeof(to_send)); |
292 | if (ret < 0) { |
293 | dev_err(&data->spi->dev, "Changing sensor delay failed\n" ); |
294 | return ret; |
295 | } |
296 | |
297 | data->delay_buf[type] = delay; |
298 | |
299 | return 0; |
300 | } |
301 | EXPORT_SYMBOL_NS(ssp_change_delay, IIO_SSP_SENSORS); |
302 | |
303 | /** |
304 | * ssp_disable_sensor() - disables sensor |
305 | * |
306 | * @data: sensorhub structure |
307 | * @type: SSP sensor type |
308 | * |
309 | * Returns 0 or negative value in case of error |
310 | */ |
311 | int ssp_disable_sensor(struct ssp_data *data, enum ssp_sensor_type type) |
312 | { |
313 | int ret; |
314 | __le32 command; |
315 | |
316 | if (data->sensor_enable & BIT(type)) { |
317 | command = cpu_to_le32(data->delay_buf[type]); |
318 | |
319 | ret = ssp_send_instruction(data, |
320 | SSP_MSG2SSP_INST_BYPASS_SENSOR_RM, |
321 | sensor_type: type, send_buf: (u8 *)&command, |
322 | length: sizeof(command)); |
323 | if (ret < 0) { |
324 | dev_err(&data->spi->dev, "Remove sensor fail\n" ); |
325 | return ret; |
326 | } |
327 | |
328 | data->sensor_enable &= ~BIT(type); |
329 | } |
330 | |
331 | data->check_status[type] = SSP_ADD_SENSOR_STATE; |
332 | |
333 | if (atomic_dec_and_test(v: &data->enable_refcount)) |
334 | ssp_disable_wdt_timer(data); |
335 | |
336 | return 0; |
337 | } |
338 | EXPORT_SYMBOL_NS(ssp_disable_sensor, IIO_SSP_SENSORS); |
339 | |
340 | static irqreturn_t ssp_irq_thread_fn(int irq, void *dev_id) |
341 | { |
342 | struct ssp_data *data = dev_id; |
343 | |
344 | /* |
345 | * This wrapper is done to preserve error path for ssp_irq_msg, also |
346 | * it is defined in different file. |
347 | */ |
348 | ssp_irq_msg(data); |
349 | |
350 | return IRQ_HANDLED; |
351 | } |
352 | |
353 | static int ssp_initialize_mcu(struct ssp_data *data) |
354 | { |
355 | int ret; |
356 | |
357 | ssp_clean_pending_list(data); |
358 | |
359 | ret = ssp_get_chipid(data); |
360 | if (ret != SSP_DEVICE_ID) { |
361 | dev_err(&data->spi->dev, "%s - MCU %s ret = %d\n" , __func__, |
362 | ret < 0 ? "is not working" : "identification failed" , |
363 | ret); |
364 | return ret < 0 ? ret : -ENODEV; |
365 | } |
366 | |
367 | dev_info(&data->spi->dev, "MCU device ID = %d\n" , ret); |
368 | |
369 | /* |
370 | * needs clarification, for now do not want to export all transfer |
371 | * methods to sensors' drivers |
372 | */ |
373 | ret = ssp_set_magnetic_matrix(data); |
374 | if (ret < 0) { |
375 | dev_err(&data->spi->dev, |
376 | "%s - ssp_set_magnetic_matrix failed\n" , __func__); |
377 | return ret; |
378 | } |
379 | |
380 | data->available_sensors = ssp_get_sensor_scanning_info(data); |
381 | if (data->available_sensors == 0) { |
382 | dev_err(&data->spi->dev, |
383 | "%s - ssp_get_sensor_scanning_info failed\n" , __func__); |
384 | return -EIO; |
385 | } |
386 | |
387 | data->cur_firm_rev = ssp_get_firmware_rev(data); |
388 | dev_info(&data->spi->dev, "MCU Firm Rev : New = %8u\n" , |
389 | data->cur_firm_rev); |
390 | |
391 | return ssp_command(data, SSP_MSG2SSP_AP_MCU_DUMP_CHECK, arg: 0); |
392 | } |
393 | |
394 | /* |
395 | * sensorhub can request its reinitialization as some brutal and rare error |
396 | * handling. It can be requested from the MCU. |
397 | */ |
398 | static void ssp_refresh_task(struct work_struct *work) |
399 | { |
400 | struct ssp_data *data = container_of((struct delayed_work *)work, |
401 | struct ssp_data, work_refresh); |
402 | |
403 | dev_info(&data->spi->dev, "refreshing\n" ); |
404 | |
405 | data->reset_cnt++; |
406 | |
407 | if (ssp_initialize_mcu(data) >= 0) { |
408 | ssp_sync_available_sensors(data); |
409 | if (data->last_ap_state != 0) |
410 | ssp_command(data, command: data->last_ap_state, arg: 0); |
411 | |
412 | if (data->last_resume_state != 0) |
413 | ssp_command(data, command: data->last_resume_state, arg: 0); |
414 | |
415 | data->timeout_cnt = 0; |
416 | data->com_fail_cnt = 0; |
417 | } |
418 | } |
419 | |
420 | int ssp_queue_ssp_refresh_task(struct ssp_data *data, unsigned int delay) |
421 | { |
422 | cancel_delayed_work_sync(dwork: &data->work_refresh); |
423 | |
424 | return queue_delayed_work(wq: system_power_efficient_wq, |
425 | dwork: &data->work_refresh, |
426 | delay: msecs_to_jiffies(m: delay)); |
427 | } |
428 | |
429 | static const struct of_device_id ssp_of_match[] = { |
430 | { |
431 | .compatible = "samsung,sensorhub-rinato" , |
432 | .data = &ssp_rinato_info, |
433 | }, { |
434 | .compatible = "samsung,sensorhub-thermostat" , |
435 | .data = &ssp_thermostat_info, |
436 | }, |
437 | {}, |
438 | }; |
439 | MODULE_DEVICE_TABLE(of, ssp_of_match); |
440 | |
441 | static struct ssp_data *ssp_parse_dt(struct device *dev) |
442 | { |
443 | struct ssp_data *data; |
444 | |
445 | data = devm_kzalloc(dev, size: sizeof(*data), GFP_KERNEL); |
446 | if (!data) |
447 | return NULL; |
448 | |
449 | data->mcu_ap_gpiod = devm_gpiod_get(dev, con_id: "mcu-ap" , flags: GPIOD_IN); |
450 | if (IS_ERR(ptr: data->mcu_ap_gpiod)) |
451 | return NULL; |
452 | |
453 | data->ap_mcu_gpiod = devm_gpiod_get(dev, con_id: "ap-mcu" , flags: GPIOD_OUT_HIGH); |
454 | if (IS_ERR(ptr: data->ap_mcu_gpiod)) |
455 | return NULL; |
456 | |
457 | data->mcu_reset_gpiod = devm_gpiod_get(dev, con_id: "mcu-reset" , |
458 | flags: GPIOD_OUT_HIGH); |
459 | if (IS_ERR(ptr: data->mcu_reset_gpiod)) |
460 | return NULL; |
461 | |
462 | data->sensorhub_info = device_get_match_data(dev); |
463 | |
464 | dev_set_drvdata(dev, data); |
465 | |
466 | return data; |
467 | } |
468 | |
469 | /** |
470 | * ssp_register_consumer() - registers iio consumer in ssp framework |
471 | * |
472 | * @indio_dev: consumer iio device |
473 | * @type: ssp sensor type |
474 | */ |
475 | void ssp_register_consumer(struct iio_dev *indio_dev, enum ssp_sensor_type type) |
476 | { |
477 | struct ssp_data *data = dev_get_drvdata(dev: indio_dev->dev.parent->parent); |
478 | |
479 | data->sensor_devs[type] = indio_dev; |
480 | } |
481 | EXPORT_SYMBOL_NS(ssp_register_consumer, IIO_SSP_SENSORS); |
482 | |
483 | static int ssp_probe(struct spi_device *spi) |
484 | { |
485 | int ret, i; |
486 | struct ssp_data *data; |
487 | |
488 | data = ssp_parse_dt(dev: &spi->dev); |
489 | if (!data) { |
490 | dev_err(&spi->dev, "Failed to find platform data\n" ); |
491 | return -ENODEV; |
492 | } |
493 | |
494 | ret = mfd_add_devices(parent: &spi->dev, PLATFORM_DEVID_NONE, |
495 | cells: sensorhub_sensor_devs, |
496 | ARRAY_SIZE(sensorhub_sensor_devs), NULL, irq_base: 0, NULL); |
497 | if (ret < 0) { |
498 | dev_err(&spi->dev, "mfd add devices fail\n" ); |
499 | return ret; |
500 | } |
501 | |
502 | spi->mode = SPI_MODE_1; |
503 | ret = spi_setup(spi); |
504 | if (ret < 0) { |
505 | dev_err(&spi->dev, "Failed to setup spi\n" ); |
506 | return ret; |
507 | } |
508 | |
509 | data->fw_dl_state = SSP_FW_DL_STATE_NONE; |
510 | data->spi = spi; |
511 | spi_set_drvdata(spi, data); |
512 | |
513 | mutex_init(&data->comm_lock); |
514 | |
515 | for (i = 0; i < SSP_SENSOR_MAX; ++i) { |
516 | data->delay_buf[i] = SSP_DEFAULT_POLLING_DELAY; |
517 | data->batch_latency_buf[i] = 0; |
518 | data->batch_opt_buf[i] = 0; |
519 | data->check_status[i] = SSP_INITIALIZATION_STATE; |
520 | } |
521 | |
522 | data->delay_buf[SSP_BIO_HRM_LIB] = 100; |
523 | |
524 | data->time_syncing = true; |
525 | |
526 | mutex_init(&data->pending_lock); |
527 | INIT_LIST_HEAD(list: &data->pending_list); |
528 | |
529 | atomic_set(v: &data->enable_refcount, i: 0); |
530 | |
531 | INIT_WORK(&data->work_wdt, ssp_wdt_work_func); |
532 | INIT_DELAYED_WORK(&data->work_refresh, ssp_refresh_task); |
533 | |
534 | timer_setup(&data->wdt_timer, ssp_wdt_timer_func, 0); |
535 | |
536 | ret = request_threaded_irq(irq: data->spi->irq, NULL, |
537 | thread_fn: ssp_irq_thread_fn, |
538 | IRQF_TRIGGER_FALLING | IRQF_ONESHOT, |
539 | name: "SSP_Int" , dev: data); |
540 | if (ret < 0) { |
541 | dev_err(&spi->dev, "Irq request fail\n" ); |
542 | goto err_setup_irq; |
543 | } |
544 | |
545 | /* Let's start with enabled one so irq balance could be ok */ |
546 | data->shut_down = false; |
547 | |
548 | /* just to avoid unbalanced irq set wake up */ |
549 | enable_irq_wake(irq: data->spi->irq); |
550 | |
551 | data->fw_dl_state = ssp_check_fwbl(data); |
552 | if (data->fw_dl_state == SSP_FW_DL_STATE_NONE) { |
553 | ret = ssp_initialize_mcu(data); |
554 | if (ret < 0) { |
555 | dev_err(&spi->dev, "Initialize_mcu failed\n" ); |
556 | goto err_read_reg; |
557 | } |
558 | } else { |
559 | dev_err(&spi->dev, "Firmware version not supported\n" ); |
560 | ret = -EPERM; |
561 | goto err_read_reg; |
562 | } |
563 | |
564 | return 0; |
565 | |
566 | err_read_reg: |
567 | free_irq(data->spi->irq, data); |
568 | err_setup_irq: |
569 | mutex_destroy(lock: &data->pending_lock); |
570 | mutex_destroy(lock: &data->comm_lock); |
571 | |
572 | dev_err(&spi->dev, "Probe failed!\n" ); |
573 | |
574 | return ret; |
575 | } |
576 | |
577 | static void ssp_remove(struct spi_device *spi) |
578 | { |
579 | struct ssp_data *data = spi_get_drvdata(spi); |
580 | |
581 | if (ssp_command(data, SSP_MSG2SSP_AP_STATUS_SHUTDOWN, arg: 0) < 0) |
582 | dev_err(&data->spi->dev, |
583 | "SSP_MSG2SSP_AP_STATUS_SHUTDOWN failed\n" ); |
584 | |
585 | ssp_enable_mcu(data, enable: false); |
586 | ssp_disable_wdt_timer(data); |
587 | |
588 | ssp_clean_pending_list(data); |
589 | |
590 | free_irq(data->spi->irq, data); |
591 | |
592 | del_timer_sync(timer: &data->wdt_timer); |
593 | cancel_work_sync(work: &data->work_wdt); |
594 | |
595 | mutex_destroy(lock: &data->comm_lock); |
596 | mutex_destroy(lock: &data->pending_lock); |
597 | |
598 | mfd_remove_devices(parent: &spi->dev); |
599 | } |
600 | |
601 | static int ssp_suspend(struct device *dev) |
602 | { |
603 | int ret; |
604 | struct ssp_data *data = spi_get_drvdata(spi: to_spi_device(dev)); |
605 | |
606 | data->last_resume_state = SSP_MSG2SSP_AP_STATUS_SUSPEND; |
607 | |
608 | if (atomic_read(v: &data->enable_refcount) > 0) |
609 | ssp_disable_wdt_timer(data); |
610 | |
611 | ret = ssp_command(data, SSP_MSG2SSP_AP_STATUS_SUSPEND, arg: 0); |
612 | if (ret < 0) { |
613 | dev_err(&data->spi->dev, |
614 | "%s SSP_MSG2SSP_AP_STATUS_SUSPEND failed\n" , __func__); |
615 | |
616 | ssp_enable_wdt_timer(data); |
617 | return ret; |
618 | } |
619 | |
620 | data->time_syncing = false; |
621 | disable_irq(irq: data->spi->irq); |
622 | |
623 | return 0; |
624 | } |
625 | |
626 | static int ssp_resume(struct device *dev) |
627 | { |
628 | int ret; |
629 | struct ssp_data *data = spi_get_drvdata(spi: to_spi_device(dev)); |
630 | |
631 | enable_irq(irq: data->spi->irq); |
632 | |
633 | if (atomic_read(v: &data->enable_refcount) > 0) |
634 | ssp_enable_wdt_timer(data); |
635 | |
636 | ret = ssp_command(data, SSP_MSG2SSP_AP_STATUS_RESUME, arg: 0); |
637 | if (ret < 0) { |
638 | dev_err(&data->spi->dev, |
639 | "%s SSP_MSG2SSP_AP_STATUS_RESUME failed\n" , __func__); |
640 | ssp_disable_wdt_timer(data); |
641 | return ret; |
642 | } |
643 | |
644 | /* timesyncing is set by MCU */ |
645 | data->last_resume_state = SSP_MSG2SSP_AP_STATUS_RESUME; |
646 | |
647 | return 0; |
648 | } |
649 | |
650 | static DEFINE_SIMPLE_DEV_PM_OPS(ssp_pm_ops, ssp_suspend, ssp_resume); |
651 | |
652 | static struct spi_driver ssp_driver = { |
653 | .probe = ssp_probe, |
654 | .remove = ssp_remove, |
655 | .driver = { |
656 | .pm = pm_sleep_ptr(&ssp_pm_ops), |
657 | .of_match_table = ssp_of_match, |
658 | .name = "sensorhub" |
659 | }, |
660 | }; |
661 | |
662 | module_spi_driver(ssp_driver); |
663 | |
664 | MODULE_DESCRIPTION("ssp sensorhub driver" ); |
665 | MODULE_AUTHOR("Samsung Electronics" ); |
666 | MODULE_LICENSE("GPL" ); |
667 | |