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