Commit | Line | Data |
---|---|---|
1cac41cb MB |
1 | /* abc_common.c |
2 | * | |
3 | * Abnormal Behavior Catcher Common Driver | |
4 | * | |
5 | * Copyright (C) 2017 Samsung Electronics | |
6 | * | |
7 | * Hyeokseon Yu <hyeokseon.yu@samsung.com> | |
8 | * | |
9 | * This program is free software; you can redistribute it and/or modify | |
10 | * it under the terms of the GNU General Public License as published by | |
11 | * the Free Software Foundation; either version 2 of the License, or | |
12 | * (at your option) any later version. | |
13 | * | |
14 | * This program is distributed in the hope that it will be useful, | |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
17 | * GNU General Public License for more details. | |
18 | * | |
19 | */ | |
20 | #include <linux/sti/abc_common.h> | |
21 | ||
22 | #define DEBUG_ABC | |
23 | // #define ABC_WARNING_REPORT | |
24 | ||
25 | static struct device *sec_abc; | |
26 | static int abc_enabled; | |
27 | static int abc_init; | |
28 | ||
29 | #define ABC_PRINT(format, ...) pr_info("[sec_abc] " format, ##__VA_ARGS__) | |
30 | ||
31 | #ifdef CONFIG_OF | |
32 | static int parse_gpu_data(struct device *dev, | |
33 | struct abc_platform_data *pdata, | |
34 | struct device_node *np) | |
35 | { | |
36 | struct abc_qdata *cgpu; | |
37 | ||
38 | cgpu = pdata->gpu_items; | |
39 | cgpu->desc = of_get_property(np, "gpu,label", NULL); | |
40 | ||
41 | if (of_property_read_u32(np, "gpu,threshold_count", &cgpu->threshold_cnt)) { | |
42 | dev_err(dev, "Failed to get gpu threshold count: node not exist\n"); | |
43 | return -EINVAL; | |
44 | } | |
45 | ||
46 | if (of_property_read_u32(np, "gpu,threshold_time", &cgpu->threshold_time)) { | |
47 | dev_err(dev, "Failed to get gpu threshold time: node not exist\n"); | |
48 | return -EINVAL; | |
49 | } | |
50 | ||
51 | cgpu->buffer.abc_element = kzalloc(sizeof(cgpu->buffer.abc_element[0]) * (cgpu->threshold_cnt + 1), GFP_KERNEL); | |
52 | ||
53 | if (!cgpu->buffer.abc_element) | |
54 | return -ENOMEM; | |
55 | ||
56 | cgpu->buffer.size = cgpu->threshold_cnt + 1; | |
57 | cgpu->buffer.rear = 0; | |
58 | cgpu->buffer.front = 0; | |
59 | cgpu->fail_cnt = 0; | |
60 | ||
61 | return 0; | |
62 | } | |
63 | ||
64 | static int parse_aicl_data(struct device *dev, | |
65 | struct abc_platform_data *pdata, | |
66 | struct device_node *np) | |
67 | { | |
68 | struct abc_qdata *caicl; | |
69 | ||
70 | caicl = pdata->aicl_items; | |
71 | caicl->desc = of_get_property(np, "aicl,label", NULL); | |
72 | ||
73 | if (of_property_read_u32(np, "aicl,threshold_count", &caicl->threshold_cnt)) { | |
74 | dev_err(dev, "Failed to get aicl threshold count: node not exist\n"); | |
75 | return -EINVAL; | |
76 | } | |
77 | ||
78 | if (of_property_read_u32(np, "aicl,threshold_time", &caicl->threshold_time)) { | |
79 | dev_err(dev, "Failed to get aicl threshold time: node not exist\n"); | |
80 | return -EINVAL; | |
81 | } | |
82 | ||
83 | caicl->buffer.abc_element = kzalloc(sizeof(caicl->buffer.abc_element[0]) * | |
84 | (caicl->threshold_cnt + 1), GFP_KERNEL); | |
85 | ||
86 | if (!caicl->buffer.abc_element) | |
87 | return -ENOMEM; | |
88 | ||
89 | caicl->buffer.size = caicl->threshold_cnt + 1; | |
90 | caicl->buffer.rear = 0; | |
91 | caicl->buffer.front = 0; | |
92 | caicl->fail_cnt = 0; | |
93 | ||
94 | return 0; | |
95 | } | |
96 | ||
97 | static int abc_parse_dt(struct device *dev) | |
98 | { | |
99 | struct abc_platform_data *pdata = dev->platform_data; | |
100 | struct device_node *np; | |
101 | struct device_node *gpu_np; | |
102 | struct device_node *aicl_np; | |
103 | ||
104 | np = dev->of_node; | |
105 | pdata->nItem = of_get_child_count(np); | |
106 | if (!pdata->nItem) { | |
107 | dev_err(dev, "There are no items\n"); | |
108 | return -ENODEV; | |
109 | } | |
110 | ||
111 | gpu_np = of_find_node_by_name(np, "gpu"); | |
112 | pdata->nGpu = of_get_child_count(gpu_np); | |
113 | pdata->gpu_items = devm_kzalloc(dev, | |
114 | sizeof(struct abc_qdata), GFP_KERNEL); | |
115 | ||
116 | if (!pdata->gpu_items) { | |
117 | dev_err(dev, "Failed to allocate GPU memory\n"); | |
118 | return -ENOMEM; | |
119 | } | |
120 | ||
121 | if (gpu_np) | |
122 | parse_gpu_data(dev, pdata, gpu_np); | |
123 | ||
124 | aicl_np = of_find_node_by_name(np, "aicl"); | |
125 | pdata->nAicl = of_get_child_count(aicl_np); | |
126 | pdata->aicl_items = devm_kzalloc(dev, | |
127 | sizeof(struct abc_qdata), GFP_KERNEL); | |
128 | ||
129 | if (!pdata->aicl_items) { | |
130 | dev_err(dev, "Failed to allocate AICL memory\n"); | |
131 | return -ENOMEM; | |
132 | } | |
133 | ||
134 | if (aicl_np) | |
135 | parse_aicl_data(dev, pdata, aicl_np); | |
136 | ||
137 | return 0; | |
138 | } | |
139 | #endif | |
140 | ||
141 | #ifdef CONFIG_OF | |
142 | static const struct of_device_id sec_abc_dt_match[] = { | |
143 | { .compatible = "samsung,sec_abc" }, | |
144 | { } | |
145 | }; | |
146 | #endif | |
147 | ||
148 | static int sec_abc_resume(struct device *dev) | |
149 | { | |
150 | return 0; | |
151 | } | |
152 | ||
153 | static int sec_abc_remove(struct platform_device *pdev) | |
154 | { | |
155 | return 0; | |
156 | } | |
157 | ||
158 | static const struct dev_pm_ops sec_abc_pm = { | |
159 | .resume = sec_abc_resume, | |
160 | }; | |
161 | ||
162 | static void sec_abc_reset_gpu_buffer(void) | |
163 | { | |
164 | struct abc_info *pinfo = dev_get_drvdata(sec_abc); | |
165 | ||
166 | pinfo->pdata->gpu_items->buffer.rear = 0; | |
167 | pinfo->pdata->gpu_items->buffer.front = 0; | |
168 | pinfo->pdata->gpu_items->fail_cnt = 0; | |
169 | } | |
170 | ||
171 | static void sec_abc_reset_aicl_buffer(void) | |
172 | { | |
173 | struct abc_info *pinfo = dev_get_drvdata(sec_abc); | |
174 | ||
175 | pinfo->pdata->aicl_items->buffer.rear = 0; | |
176 | pinfo->pdata->aicl_items->buffer.front = 0; | |
177 | pinfo->pdata->aicl_items->fail_cnt = 0; | |
178 | } | |
179 | ||
180 | static ssize_t store_abc_enabled(struct device *dev, | |
181 | struct device_attribute *attr, | |
182 | const char *buf, size_t count) | |
183 | { | |
184 | struct abc_info *pinfo = dev_get_drvdata(sec_abc); | |
185 | ||
186 | if (!strncmp(buf, "1", 1)) { | |
187 | ABC_PRINT("ABC driver enabled.\n"); | |
188 | abc_enabled = ABC_TYPE1_ENABLED; | |
189 | complete(&pinfo->enable_done); | |
190 | } else if (!strncmp(buf, "2", 1)) { | |
191 | ABC_PRINT("Common driver enabled.\n"); | |
192 | abc_enabled = ABC_TYPE2_ENABLED; | |
193 | complete(&pinfo->enable_done); | |
194 | } else if (!strncmp(buf, "0", 1)) { | |
195 | ABC_PRINT("ABC/Common driver disabled.\n"); | |
196 | if (abc_enabled == ABC_TYPE1_ENABLED) { | |
197 | sec_abc_reset_gpu_buffer(); | |
198 | sec_abc_reset_aicl_buffer(); | |
199 | } | |
200 | ||
201 | abc_enabled = ABC_DISABLED; | |
202 | } | |
203 | return count; | |
204 | } | |
205 | ||
206 | static ssize_t show_abc_enabled(struct device *dev, | |
207 | struct device_attribute *attr, | |
208 | char *buf) | |
209 | { | |
210 | return sprintf(buf, "%d\n", abc_enabled); | |
211 | } | |
212 | static DEVICE_ATTR(enabled, 0644, show_abc_enabled, store_abc_enabled); | |
213 | ||
214 | /* reset abc log_list */ | |
215 | static ssize_t store_abc_log(struct device *dev, | |
216 | struct device_attribute *attr, | |
217 | const char *buf, size_t count) | |
218 | { | |
219 | struct abc_info *pinfo = dev_get_drvdata(sec_abc); | |
220 | struct abc_log_entry *abc_log; | |
221 | ||
5a068558 MB |
222 | mutex_lock(&pinfo->log_mutex); |
223 | ||
1cac41cb MB |
224 | pinfo->log_list_cnt = 0; |
225 | ||
226 | while (!list_empty(&pinfo->log_list)) { | |
227 | abc_log = list_first_entry(&pinfo->log_list, struct abc_log_entry, node); | |
228 | list_del(&abc_log->node); | |
229 | kfree(abc_log); | |
230 | } | |
231 | ||
5a068558 MB |
232 | mutex_unlock(&pinfo->log_mutex); |
233 | ||
1cac41cb MB |
234 | return count; |
235 | } | |
236 | ||
237 | /* read abc log_list */ | |
238 | static ssize_t show_abc_log(struct device *dev, | |
239 | struct device_attribute *attr, | |
240 | char *buf) | |
241 | { | |
242 | struct abc_info *pinfo = dev_get_drvdata(sec_abc); | |
243 | struct abc_log_entry *abc_log; | |
244 | int count = 0; | |
245 | ||
5a068558 MB |
246 | mutex_lock(&pinfo->log_mutex); |
247 | ||
1cac41cb | 248 | list_for_each_entry(abc_log, &pinfo->log_list, node) { |
5a068558 | 249 | count += snprintf(buf + count, PAGE_SIZE - count, "%s\n", abc_log->abc_log_str); |
1cac41cb MB |
250 | } |
251 | ||
5a068558 MB |
252 | mutex_unlock(&pinfo->log_mutex); |
253 | ||
1cac41cb MB |
254 | return count; |
255 | } | |
256 | static DEVICE_ATTR(log, 0644, show_abc_log, store_abc_log); | |
257 | ||
258 | static int sec_abc_is_full(struct abc_buffer *buffer) | |
259 | { | |
260 | if ((buffer->rear + 1) % buffer->size == buffer->front) | |
261 | return 1; | |
262 | else | |
263 | return 0; | |
264 | } | |
265 | ||
266 | static int sec_abc_is_empty(struct abc_buffer *buffer) | |
267 | { | |
268 | if (buffer->front == buffer->rear) | |
269 | return 1; | |
270 | else | |
271 | return 0; | |
272 | } | |
273 | ||
274 | static void sec_abc_enqueue(struct abc_buffer *buffer, struct abc_fault_info in) | |
275 | { | |
276 | if (sec_abc_is_full(buffer)) { | |
277 | ABC_PRINT("queue is full.\n"); | |
278 | } else { | |
279 | buffer->rear = (buffer->rear + 1) % buffer->size; | |
280 | buffer->abc_element[buffer->rear] = in; | |
281 | } | |
282 | } | |
283 | ||
284 | static void sec_abc_dequeue(struct abc_buffer *buffer, struct abc_fault_info *out) | |
285 | { | |
286 | if (sec_abc_is_empty(buffer)) { | |
287 | ABC_PRINT("queue is empty.\n"); | |
288 | } else { | |
289 | buffer->front = (buffer->front + 1) % buffer->size; | |
290 | *out = buffer->abc_element[buffer->front]; | |
291 | } | |
292 | } | |
293 | ||
294 | static int sec_abc_get_diff_time(struct abc_buffer *buffer) | |
295 | { | |
296 | int front_time, rear_time; | |
297 | ||
298 | front_time = buffer->abc_element[(buffer->front + 1) % buffer->size].cur_time; | |
299 | rear_time = buffer->abc_element[buffer->rear].cur_time; | |
300 | ||
301 | ABC_PRINT("front time : %d(%d) rear_time %d(%d) diff : %d\n", | |
302 | front_time, | |
303 | buffer->front + 1, | |
304 | rear_time, | |
305 | buffer->rear, | |
306 | rear_time - front_time); | |
307 | ||
308 | return rear_time - front_time; | |
309 | } | |
310 | ||
311 | int sec_abc_get_enabled(void) | |
312 | { | |
313 | return abc_enabled; | |
314 | } | |
315 | EXPORT_SYMBOL(sec_abc_get_enabled); | |
316 | ||
317 | static void sec_abc_work_func(struct work_struct *work) | |
318 | { | |
319 | struct abc_info *pinfo = container_of(work, struct abc_info, work); | |
320 | struct abc_qdata *pgpu, *paicl; | |
321 | struct abc_fault_info in, out; | |
322 | struct abc_log_entry *abc_log; | |
323 | ||
324 | struct timespec ts; | |
325 | struct rtc_time tm; | |
326 | unsigned long local_time; | |
327 | ||
328 | char *c, *p; | |
329 | char *uevent_str[ABC_UEVENT_MAX] = {0,}; | |
330 | char temp[ABC_BUFFER_MAX], timestamp[ABC_BUFFER_MAX], event_type[ABC_BUFFER_MAX]; | |
331 | int idx = 0; | |
332 | u64 ktime; | |
333 | unsigned long ktime_ms; | |
334 | int ktime_rem; | |
335 | ||
336 | strcpy(temp, pinfo->abc_str); | |
337 | p = &temp[0]; | |
338 | ||
339 | /* Caculate current kernel time */ | |
340 | ktime = local_clock(); | |
341 | ktime_ms = ktime / NSEC_PER_MSEC; | |
342 | ktime_rem = do_div(ktime, NSEC_PER_SEC); | |
343 | ||
344 | /* Caculate current local time */ | |
345 | getnstimeofday(&ts); | |
346 | local_time = (u32)(ts.tv_sec - (sys_tz.tz_minuteswest * 60)); | |
347 | rtc_time_to_tm(local_time, &tm); | |
348 | ||
349 | /* Parse uevent string */ | |
350 | while ((c = strsep(&p, "@")) != NULL) { | |
351 | uevent_str[idx] = c; | |
352 | idx++; | |
353 | } | |
354 | sprintf(timestamp, "TIMESTAMP=%lu", ktime_ms); | |
355 | uevent_str[idx++] = ×tamp[0]; | |
356 | uevent_str[idx] = '\0'; | |
357 | strlcpy(event_type, uevent_str[1] + 6, sizeof(event_type)); | |
358 | ||
359 | ABC_PRINT("event type : %s\n", event_type); | |
360 | ||
361 | #if defined(DEBUG_ABC) | |
362 | idx = 0; | |
363 | while ((c = uevent_str[idx]) != '\0') { | |
364 | ABC_PRINT("%s\n", uevent_str[idx]); | |
365 | idx++; | |
366 | } | |
367 | #endif | |
368 | ||
369 | /* Add abc log_list */ | |
5a068558 MB |
370 | mutex_lock(&pinfo->log_mutex); |
371 | ||
1cac41cb MB |
372 | abc_log = kzalloc(sizeof(*abc_log), GFP_KERNEL); |
373 | if (abc_log) { | |
374 | snprintf(abc_log->abc_log_str, ABC_LOG_STR_LEN, "[%5lu.%03d][%02d:%02d:%02d.%03lu]%s_%s", | |
375 | (unsigned long)ktime, (int)(ktime_rem / NSEC_PER_MSEC), tm.tm_hour, tm.tm_min, | |
376 | tm.tm_sec, ts.tv_nsec / 1000000, uevent_str[0] + 7, uevent_str[1] + 6); | |
377 | if (pinfo->log_list_cnt < ABC_LOG_MAX) { | |
378 | list_add_tail(&abc_log->node, &pinfo->log_list); | |
379 | pinfo->log_list_cnt++; | |
380 | } else { | |
381 | list_add_tail(&abc_log->node, &pinfo->log_list); | |
382 | abc_log = list_first_entry(&pinfo->log_list, struct abc_log_entry, node); | |
383 | list_del(&abc_log->node); | |
384 | kfree(abc_log); | |
385 | } | |
386 | } else { | |
387 | ABC_PRINT("failed to allocate abc_log\n"); | |
388 | } | |
389 | ||
5a068558 MB |
390 | mutex_unlock(&pinfo->log_mutex); |
391 | ||
1cac41cb MB |
392 | if (abc_enabled == ABC_TYPE1_ENABLED) { |
393 | pgpu = pinfo->pdata->gpu_items; | |
394 | paicl = pinfo->pdata->aicl_items; | |
395 | ||
396 | /* GPU fault */ | |
397 | if (!strncasecmp(event_type, "gpu_fault", 9)) { | |
398 | in.cur_time = (unsigned long)ktime / USEC_PER_SEC; | |
399 | in.cur_cnt = pgpu->fail_cnt++; | |
400 | ||
401 | ABC_PRINT("gpu fail count : %d\n", pgpu->fail_cnt); | |
402 | sec_abc_enqueue(&pgpu->buffer, in); | |
403 | ||
404 | /* Check gpu fault */ | |
405 | /* Case 1 : Over threshold count */ | |
406 | if (pgpu->fail_cnt >= pgpu->threshold_cnt) { | |
407 | if (sec_abc_get_diff_time(&pgpu->buffer) < pgpu->threshold_time) { | |
408 | ABC_PRINT("GPU fault occurred. Send uevent.\n"); | |
409 | kobject_uevent_env(&sec_abc->kobj, KOBJ_CHANGE, uevent_str); | |
410 | } | |
411 | pgpu->fail_cnt = 0; | |
412 | sec_abc_dequeue(&pgpu->buffer, &out); | |
413 | ABC_PRINT("cur_time : %lu cur_cnt : %d\n", out.cur_time, out.cur_cnt); | |
414 | /* Case 2 : Check front and rear node in queue. Because it's occurred within max count */ | |
415 | } else if (sec_abc_is_full(&pgpu->buffer)) { | |
416 | if (sec_abc_get_diff_time(&pgpu->buffer) < pgpu->threshold_time) { | |
417 | ABC_PRINT("GPU fault occurred. Send uevent.\n"); | |
418 | kobject_uevent_env(&sec_abc->kobj, KOBJ_CHANGE, uevent_str); | |
419 | } | |
420 | sec_abc_dequeue(&pgpu->buffer, &out); | |
421 | ABC_PRINT("cur_time : %lu cur_cnt : %d\n", out.cur_time, out.cur_cnt); | |
422 | } | |
423 | ||
424 | #ifdef ABC_WARNING_REPORT | |
425 | /* Send GPU fault warning */ | |
426 | strcat(uevent_str[1], "_w"); | |
427 | kobject_uevent_env(&sec_abc->kobj, KOBJ_CHANGE, uevent_str); | |
428 | #endif | |
429 | } else if (!strncasecmp(event_type, "aicl", 4)) { /* AICL fault */ | |
430 | in.cur_time = (unsigned long)ktime / USEC_PER_SEC; | |
431 | in.cur_cnt = paicl->fail_cnt++; | |
432 | ||
433 | ABC_PRINT("aicl fail count : %d\n", paicl->fail_cnt); | |
434 | sec_abc_enqueue(&paicl->buffer, in); | |
435 | ||
436 | /* Check aicl fault */ | |
437 | if (paicl->fail_cnt >= paicl->threshold_cnt) { | |
438 | if (sec_abc_get_diff_time(&paicl->buffer) < paicl->threshold_time) { | |
439 | ABC_PRINT("AICL fault occurred. Send uevent.\n"); | |
440 | kobject_uevent_env(&sec_abc->kobj, KOBJ_CHANGE, uevent_str); | |
441 | while (!sec_abc_is_empty(&paicl->buffer)) | |
442 | sec_abc_dequeue(&paicl->buffer, &out); | |
443 | paicl->fail_cnt = 0; | |
444 | } else { | |
445 | paicl->fail_cnt--; | |
446 | sec_abc_dequeue(&paicl->buffer, &out); | |
447 | ABC_PRINT("cur_time : %lu cur_cnt : %d\n", out.cur_time, out.cur_cnt); | |
448 | } | |
449 | } | |
450 | } else { | |
451 | /* Others */ | |
452 | kobject_uevent_env(&sec_abc->kobj, KOBJ_CHANGE, uevent_str); | |
453 | ABC_PRINT("Send uevent.\n"); | |
454 | } | |
455 | } else { /* ABC_TYPE2_ENABLED */ | |
456 | kobject_uevent_env(&sec_abc->kobj, KOBJ_CHANGE, uevent_str); | |
457 | ABC_PRINT("Send uevent.\n"); | |
458 | } | |
459 | } | |
460 | ||
461 | /* event string format | |
462 | * | |
463 | * ex) MODULE=tsp@ERROR=power_status_mismatch | |
464 | * MODULE=tsp@ERROR=power_status_mismatch@EXT_LOG=fw_ver(0108) | |
465 | * | |
466 | */ | |
467 | void sec_abc_send_event(char *str) | |
468 | { | |
469 | struct abc_info *pinfo; | |
470 | ||
471 | if (!abc_init) { | |
472 | ABC_PRINT("ABC driver is not initialized!\n"); | |
473 | return; | |
474 | } | |
475 | ||
476 | if (abc_enabled == ABC_DISABLED) { | |
477 | ABC_PRINT("ABC is disabled!\n"); | |
478 | return; | |
479 | } | |
480 | ||
481 | pinfo = dev_get_drvdata(sec_abc); | |
482 | ||
483 | strlcpy(pinfo->abc_str, str, sizeof(pinfo->abc_str)); | |
484 | queue_work(pinfo->workqueue, &pinfo->work); | |
485 | } | |
486 | EXPORT_SYMBOL(sec_abc_send_event); | |
487 | ||
488 | /** | |
489 | * sec_abc_wait_enable() - wait for abc enable done | |
490 | * Return : 0 for success, -1 for fail(timeout or abc not initialized) | |
491 | */ | |
492 | int sec_abc_wait_enabled(void) | |
493 | { | |
494 | struct abc_info *pinfo; | |
495 | unsigned long timeout; | |
496 | ||
497 | if (!abc_init) { | |
498 | ABC_PRINT("ABC driver is not initialized!\n"); | |
499 | return -1; | |
500 | } | |
501 | ||
502 | if (abc_enabled) | |
503 | return 0; | |
504 | ||
505 | pinfo = dev_get_drvdata(sec_abc); | |
506 | ||
507 | reinit_completion(&pinfo->enable_done); | |
508 | ||
509 | timeout = wait_for_completion_timeout(&pinfo->enable_done, | |
510 | msecs_to_jiffies(ABC_WAIT_ENABLE_TIMEOUT)); | |
511 | ||
512 | if (timeout == 0) { | |
513 | ABC_PRINT("%s : timeout!\n", __func__); | |
514 | return -1; | |
515 | } | |
516 | ||
517 | return 0; | |
518 | } | |
519 | EXPORT_SYMBOL(sec_abc_wait_enabled); | |
520 | ||
521 | static int sec_abc_probe(struct platform_device *pdev) | |
522 | { | |
523 | struct abc_platform_data *pdata; | |
524 | struct abc_info *pinfo; | |
525 | int ret = 0; | |
526 | ||
527 | ABC_PRINT("%s\n", __func__); | |
528 | ||
529 | abc_init = false; | |
530 | ||
531 | if (pdev->dev.of_node) { | |
532 | pdata = devm_kzalloc(&pdev->dev, | |
533 | sizeof(struct abc_platform_data), GFP_KERNEL); | |
534 | ||
535 | if (!pdata) { | |
536 | dev_err(&pdev->dev, "Failed to allocate platform data\n"); | |
537 | return -ENOMEM; | |
538 | } | |
539 | ||
540 | pdev->dev.platform_data = pdata; | |
541 | ret = abc_parse_dt(&pdev->dev); | |
542 | if (ret) { | |
543 | dev_err(&pdev->dev, "Failed to parse dt data\n"); | |
544 | return ret; | |
545 | } | |
546 | ||
547 | pr_info("%s: parse dt done\n", __func__); | |
548 | } else { | |
549 | pdata = pdev->dev.platform_data; | |
550 | } | |
551 | ||
552 | if (!pdata) { | |
553 | dev_err(&pdev->dev, "There are no platform data\n"); | |
554 | return -EINVAL; | |
555 | } | |
556 | ||
557 | pinfo = kzalloc(sizeof(*pinfo), GFP_KERNEL); | |
558 | ||
559 | if (!pinfo) | |
560 | return -ENOMEM; | |
561 | ||
562 | pinfo->dev = sec_device_create(pinfo, "sec_abc"); | |
563 | if (IS_ERR(pinfo->dev)) { | |
564 | pr_err("%s Failed to create device(sec_abc)!\n", __func__); | |
565 | ret = -ENODEV; | |
566 | goto out; | |
567 | } | |
568 | ||
569 | ret = device_create_file(pinfo->dev, &dev_attr_enabled); | |
570 | if (ret) { | |
571 | pr_err("%s: Failed to create device enabled file\n", __func__); | |
572 | goto err_create_abc_enabled_sysfs; | |
573 | } | |
574 | ||
575 | ret = device_create_file(pinfo->dev, &dev_attr_log); | |
576 | if (ret) { | |
577 | pr_err("%s: Failed to create device log file\n", __func__); | |
578 | goto err_create_abc_log_sysfs; | |
579 | } | |
580 | ||
581 | INIT_WORK(&pinfo->work, sec_abc_work_func); | |
582 | ||
583 | pinfo->workqueue = create_singlethread_workqueue("sec_abc_wq"); | |
584 | if (!pinfo->workqueue) | |
585 | goto err_create_abc_wq; | |
586 | ||
587 | INIT_LIST_HEAD(&pinfo->log_list); | |
588 | pinfo->log_list_cnt = 0; | |
589 | ||
590 | init_completion(&pinfo->enable_done); | |
591 | ||
5a068558 MB |
592 | mutex_init(&pinfo->log_mutex); |
593 | ||
1cac41cb MB |
594 | sec_abc = pinfo->dev; |
595 | pinfo->pdata = pdata; | |
596 | ||
597 | platform_set_drvdata(pdev, pinfo); | |
598 | ||
599 | abc_init = true; | |
600 | return ret; | |
601 | err_create_abc_wq: | |
602 | device_remove_file(pinfo->dev, &dev_attr_log); | |
603 | err_create_abc_log_sysfs: | |
604 | device_remove_file(pinfo->dev, &dev_attr_enabled); | |
605 | err_create_abc_enabled_sysfs: | |
606 | sec_device_destroy(sec_abc->devt); | |
607 | out: | |
608 | kfree(pinfo); | |
609 | kfree(pdata); | |
610 | ||
611 | return ret; | |
612 | } | |
613 | ||
614 | static struct platform_driver sec_abc_driver = { | |
615 | .probe = sec_abc_probe, | |
616 | .remove = sec_abc_remove, | |
617 | .driver = { | |
618 | .name = "sec_abc", | |
619 | .owner = THIS_MODULE, | |
620 | #if defined(CONFIG_PM) | |
621 | .pm = &sec_abc_pm, | |
622 | #endif | |
623 | #if CONFIG_OF | |
624 | .of_match_table = of_match_ptr(sec_abc_dt_match), | |
625 | #endif | |
626 | }, | |
627 | }; | |
628 | ||
629 | static int __init sec_abc_init(void) | |
630 | { | |
631 | ABC_PRINT("%s\n", __func__); | |
632 | ||
633 | return platform_driver_register(&sec_abc_driver); | |
634 | } | |
635 | ||
636 | static void __exit sec_abc_exit(void) | |
637 | { | |
638 | return platform_driver_unregister(&sec_abc_driver); | |
639 | } | |
640 | ||
641 | module_init(sec_abc_init); | |
642 | module_exit(sec_abc_exit); | |
643 | ||
644 | MODULE_DESCRIPTION("Samsung ABC Driver"); | |
645 | MODULE_AUTHOR("Samsung Electronics"); | |
646 | MODULE_LICENSE("GPL"); |