Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
samsung-keypad.c
Go to the documentation of this file.
1 /*
2  * Samsung keypad driver
3  *
4  * Copyright (C) 2010 Samsung Electronics Co.Ltd
5  * Author: Joonyoung Shim <[email protected]>
6  * Author: Donghwa Lee <[email protected]>
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms of the GNU General Public License as published by the
10  * Free Software Foundation; either version 2 of the License, or (at your
11  * option) any later version.
12  */
13 
14 #include <linux/clk.h>
15 #include <linux/delay.h>
16 #include <linux/err.h>
17 #include <linux/init.h>
18 #include <linux/input.h>
19 #include <linux/interrupt.h>
20 #include <linux/io.h>
21 #include <linux/module.h>
22 #include <linux/platform_device.h>
23 #include <linux/pm.h>
24 #include <linux/pm_runtime.h>
25 #include <linux/slab.h>
26 #include <linux/of.h>
27 #include <linux/of_gpio.h>
28 #include <linux/sched.h>
30 
31 #define SAMSUNG_KEYIFCON 0x00
32 #define SAMSUNG_KEYIFSTSCLR 0x04
33 #define SAMSUNG_KEYIFCOL 0x08
34 #define SAMSUNG_KEYIFROW 0x0c
35 #define SAMSUNG_KEYIFFC 0x10
36 
37 /* SAMSUNG_KEYIFCON */
38 #define SAMSUNG_KEYIFCON_INT_F_EN (1 << 0)
39 #define SAMSUNG_KEYIFCON_INT_R_EN (1 << 1)
40 #define SAMSUNG_KEYIFCON_DF_EN (1 << 2)
41 #define SAMSUNG_KEYIFCON_FC_EN (1 << 3)
42 #define SAMSUNG_KEYIFCON_WAKEUPEN (1 << 4)
43 
44 /* SAMSUNG_KEYIFSTSCLR */
45 #define SAMSUNG_KEYIFSTSCLR_P_INT_MASK (0xff << 0)
46 #define SAMSUNG_KEYIFSTSCLR_R_INT_MASK (0xff << 8)
47 #define SAMSUNG_KEYIFSTSCLR_R_INT_OFFSET 8
48 #define S5PV210_KEYIFSTSCLR_P_INT_MASK (0x3fff << 0)
49 #define S5PV210_KEYIFSTSCLR_R_INT_MASK (0x3fff << 16)
50 #define S5PV210_KEYIFSTSCLR_R_INT_OFFSET 16
51 
52 /* SAMSUNG_KEYIFCOL */
53 #define SAMSUNG_KEYIFCOL_MASK (0xff << 0)
54 #define S5PV210_KEYIFCOLEN_MASK (0xff << 8)
55 
56 /* SAMSUNG_KEYIFROW */
57 #define SAMSUNG_KEYIFROW_MASK (0xff << 0)
58 #define S5PV210_KEYIFROW_MASK (0x3fff << 0)
59 
60 /* SAMSUNG_KEYIFFC */
61 #define SAMSUNG_KEYIFFC_MASK (0x3ff << 0)
62 
66 };
67 
71  struct clk *clk;
72  void __iomem *base;
74  bool stopped;
76  int irq;
78  unsigned int row_shift;
79  unsigned int rows;
80  unsigned int cols;
81  unsigned int row_state[SAMSUNG_MAX_COLS];
82 #ifdef CONFIG_OF
83  int row_gpios[SAMSUNG_MAX_ROWS];
84  int col_gpios[SAMSUNG_MAX_COLS];
85 #endif
86  unsigned short keycodes[];
87 };
88 
89 static void samsung_keypad_scan(struct samsung_keypad *keypad,
90  unsigned int *row_state)
91 {
92  unsigned int col;
93  unsigned int val;
94 
95  for (col = 0; col < keypad->cols; col++) {
96  if (keypad->type == KEYPAD_TYPE_S5PV210) {
98  val &= ~(1 << col) << 8;
99  } else {
100  val = SAMSUNG_KEYIFCOL_MASK;
101  val &= ~(1 << col);
102  }
103 
104  writel(val, keypad->base + SAMSUNG_KEYIFCOL);
105  mdelay(1);
106 
107  val = readl(keypad->base + SAMSUNG_KEYIFROW);
108  row_state[col] = ~val & ((1 << keypad->rows) - 1);
109  }
110 
111  /* KEYIFCOL reg clear */
112  writel(0, keypad->base + SAMSUNG_KEYIFCOL);
113 }
114 
115 static bool samsung_keypad_report(struct samsung_keypad *keypad,
116  unsigned int *row_state)
117 {
118  struct input_dev *input_dev = keypad->input_dev;
119  unsigned int changed;
120  unsigned int pressed;
121  unsigned int key_down = 0;
122  unsigned int val;
123  unsigned int col, row;
124 
125  for (col = 0; col < keypad->cols; col++) {
126  changed = row_state[col] ^ keypad->row_state[col];
127  key_down |= row_state[col];
128  if (!changed)
129  continue;
130 
131  for (row = 0; row < keypad->rows; row++) {
132  if (!(changed & (1 << row)))
133  continue;
134 
135  pressed = row_state[col] & (1 << row);
136 
137  dev_dbg(&keypad->input_dev->dev,
138  "key %s, row: %d, col: %d\n",
139  pressed ? "pressed" : "released", row, col);
140 
141  val = MATRIX_SCAN_CODE(row, col, keypad->row_shift);
142 
143  input_event(input_dev, EV_MSC, MSC_SCAN, val);
144  input_report_key(input_dev,
145  keypad->keycodes[val], pressed);
146  }
147  input_sync(keypad->input_dev);
148  }
149 
150  memcpy(keypad->row_state, row_state, sizeof(keypad->row_state));
151 
152  return key_down;
153 }
154 
155 static irqreturn_t samsung_keypad_irq(int irq, void *dev_id)
156 {
157  struct samsung_keypad *keypad = dev_id;
158  unsigned int row_state[SAMSUNG_MAX_COLS];
159  unsigned int val;
160  bool key_down;
161 
162  pm_runtime_get_sync(&keypad->pdev->dev);
163 
164  do {
165  val = readl(keypad->base + SAMSUNG_KEYIFSTSCLR);
166  /* Clear interrupt. */
167  writel(~0x0, keypad->base + SAMSUNG_KEYIFSTSCLR);
168 
169  samsung_keypad_scan(keypad, row_state);
170 
171  key_down = samsung_keypad_report(keypad, row_state);
172  if (key_down)
173  wait_event_timeout(keypad->wait, keypad->stopped,
174  msecs_to_jiffies(50));
175 
176  } while (key_down && !keypad->stopped);
177 
178  pm_runtime_put(&keypad->pdev->dev);
179 
180  return IRQ_HANDLED;
181 }
182 
183 static void samsung_keypad_start(struct samsung_keypad *keypad)
184 {
185  unsigned int val;
186 
187  pm_runtime_get_sync(&keypad->pdev->dev);
188 
189  /* Tell IRQ thread that it may poll the device. */
190  keypad->stopped = false;
191 
192  clk_enable(keypad->clk);
193 
194  /* Enable interrupt bits. */
195  val = readl(keypad->base + SAMSUNG_KEYIFCON);
197  writel(val, keypad->base + SAMSUNG_KEYIFCON);
198 
199  /* KEYIFCOL reg clear. */
200  writel(0, keypad->base + SAMSUNG_KEYIFCOL);
201 
202  pm_runtime_put(&keypad->pdev->dev);
203 }
204 
205 static void samsung_keypad_stop(struct samsung_keypad *keypad)
206 {
207  unsigned int val;
208 
209  pm_runtime_get_sync(&keypad->pdev->dev);
210 
211  /* Signal IRQ thread to stop polling and disable the handler. */
212  keypad->stopped = true;
213  wake_up(&keypad->wait);
214  disable_irq(keypad->irq);
215 
216  /* Clear interrupt. */
217  writel(~0x0, keypad->base + SAMSUNG_KEYIFSTSCLR);
218 
219  /* Disable interrupt bits. */
220  val = readl(keypad->base + SAMSUNG_KEYIFCON);
222  writel(val, keypad->base + SAMSUNG_KEYIFCON);
223 
224  clk_disable(keypad->clk);
225 
226  /*
227  * Now that chip should not generate interrupts we can safely
228  * re-enable the handler.
229  */
230  enable_irq(keypad->irq);
231 
232  pm_runtime_put(&keypad->pdev->dev);
233 }
234 
235 static int samsung_keypad_open(struct input_dev *input_dev)
236 {
237  struct samsung_keypad *keypad = input_get_drvdata(input_dev);
238 
239  samsung_keypad_start(keypad);
240 
241  return 0;
242 }
243 
244 static void samsung_keypad_close(struct input_dev *input_dev)
245 {
246  struct samsung_keypad *keypad = input_get_drvdata(input_dev);
247 
248  samsung_keypad_stop(keypad);
249 }
250 
251 #ifdef CONFIG_OF
252 static struct samsung_keypad_platdata *samsung_keypad_parse_dt(
253  struct device *dev)
254 {
256  struct matrix_keymap_data *keymap_data;
257  uint32_t *keymap, num_rows = 0, num_cols = 0;
258  struct device_node *np = dev->of_node, *key_np;
259  unsigned int key_count;
260 
261  pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
262  if (!pdata) {
263  dev_err(dev, "could not allocate memory for platform data\n");
264  return NULL;
265  }
266 
267  of_property_read_u32(np, "samsung,keypad-num-rows", &num_rows);
268  of_property_read_u32(np, "samsung,keypad-num-columns", &num_cols);
269  if (!num_rows || !num_cols) {
270  dev_err(dev, "number of keypad rows/columns not specified\n");
271  return NULL;
272  }
273  pdata->rows = num_rows;
274  pdata->cols = num_cols;
275 
276  keymap_data = devm_kzalloc(dev, sizeof(*keymap_data), GFP_KERNEL);
277  if (!keymap_data) {
278  dev_err(dev, "could not allocate memory for keymap data\n");
279  return NULL;
280  }
281  pdata->keymap_data = keymap_data;
282 
283  key_count = of_get_child_count(np);
284  keymap_data->keymap_size = key_count;
285  keymap = devm_kzalloc(dev, sizeof(uint32_t) * key_count, GFP_KERNEL);
286  if (!keymap) {
287  dev_err(dev, "could not allocate memory for keymap\n");
288  return NULL;
289  }
290  keymap_data->keymap = keymap;
291 
292  for_each_child_of_node(np, key_np) {
293  u32 row, col, key_code;
294  of_property_read_u32(key_np, "keypad,row", &row);
295  of_property_read_u32(key_np, "keypad,column", &col);
296  of_property_read_u32(key_np, "linux,code", &key_code);
297  *keymap++ = KEY(row, col, key_code);
298  }
299 
300  if (of_get_property(np, "linux,input-no-autorepeat", NULL))
301  pdata->no_autorepeat = true;
302  if (of_get_property(np, "linux,input-wakeup", NULL))
303  pdata->wakeup = true;
304 
305  return pdata;
306 }
307 
308 static void samsung_keypad_parse_dt_gpio(struct device *dev,
309  struct samsung_keypad *keypad)
310 {
311  struct device_node *np = dev->of_node;
312  int gpio, ret, row, col;
313 
314  for (row = 0; row < keypad->rows; row++) {
315  gpio = of_get_named_gpio(np, "row-gpios", row);
316  keypad->row_gpios[row] = gpio;
317  if (!gpio_is_valid(gpio)) {
318  dev_err(dev, "keypad row[%d]: invalid gpio %d\n",
319  row, gpio);
320  continue;
321  }
322 
323  ret = gpio_request(gpio, "keypad-row");
324  if (ret)
325  dev_err(dev, "keypad row[%d] gpio request failed\n",
326  row);
327  }
328 
329  for (col = 0; col < keypad->cols; col++) {
330  gpio = of_get_named_gpio(np, "col-gpios", col);
331  keypad->col_gpios[col] = gpio;
332  if (!gpio_is_valid(gpio)) {
333  dev_err(dev, "keypad column[%d]: invalid gpio %d\n",
334  col, gpio);
335  continue;
336  }
337 
338  ret = gpio_request(gpio, "keypad-col");
339  if (ret)
340  dev_err(dev, "keypad column[%d] gpio request failed\n",
341  col);
342  }
343 }
344 
345 static void samsung_keypad_dt_gpio_free(struct samsung_keypad *keypad)
346 {
347  int cnt;
348 
349  for (cnt = 0; cnt < keypad->rows; cnt++)
350  if (gpio_is_valid(keypad->row_gpios[cnt]))
351  gpio_free(keypad->row_gpios[cnt]);
352 
353  for (cnt = 0; cnt < keypad->cols; cnt++)
354  if (gpio_is_valid(keypad->col_gpios[cnt]))
355  gpio_free(keypad->col_gpios[cnt]);
356 }
357 #else
358 static
359 struct samsung_keypad_platdata *samsung_keypad_parse_dt(struct device *dev)
360 {
361  return NULL;
362 }
363 
364 static void samsung_keypad_dt_gpio_free(struct samsung_keypad *keypad)
365 {
366 }
367 #endif
368 
369 static int __devinit samsung_keypad_probe(struct platform_device *pdev)
370 {
371  const struct samsung_keypad_platdata *pdata;
372  const struct matrix_keymap_data *keymap_data;
373  struct samsung_keypad *keypad;
374  struct resource *res;
375  struct input_dev *input_dev;
376  unsigned int row_shift;
377  unsigned int keymap_size;
378  int error;
379 
380  if (pdev->dev.of_node)
381  pdata = samsung_keypad_parse_dt(&pdev->dev);
382  else
383  pdata = pdev->dev.platform_data;
384  if (!pdata) {
385  dev_err(&pdev->dev, "no platform data defined\n");
386  return -EINVAL;
387  }
388 
389  keymap_data = pdata->keymap_data;
390  if (!keymap_data) {
391  dev_err(&pdev->dev, "no keymap data defined\n");
392  return -EINVAL;
393  }
394 
395  if (!pdata->rows || pdata->rows > SAMSUNG_MAX_ROWS)
396  return -EINVAL;
397 
398  if (!pdata->cols || pdata->cols > SAMSUNG_MAX_COLS)
399  return -EINVAL;
400 
401  /* initialize the gpio */
402  if (pdata->cfg_gpio)
403  pdata->cfg_gpio(pdata->rows, pdata->cols);
404 
405  row_shift = get_count_order(pdata->cols);
406  keymap_size = (pdata->rows << row_shift) * sizeof(keypad->keycodes[0]);
407 
408  keypad = kzalloc(sizeof(*keypad) + keymap_size, GFP_KERNEL);
409  input_dev = input_allocate_device();
410  if (!keypad || !input_dev) {
411  error = -ENOMEM;
412  goto err_free_mem;
413  }
414 
415  res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
416  if (!res) {
417  error = -ENODEV;
418  goto err_free_mem;
419  }
420 
421  keypad->base = ioremap(res->start, resource_size(res));
422  if (!keypad->base) {
423  error = -EBUSY;
424  goto err_free_mem;
425  }
426 
427  keypad->clk = clk_get(&pdev->dev, "keypad");
428  if (IS_ERR(keypad->clk)) {
429  dev_err(&pdev->dev, "failed to get keypad clk\n");
430  error = PTR_ERR(keypad->clk);
431  goto err_unmap_base;
432  }
433 
434  error = clk_prepare(keypad->clk);
435  if (error) {
436  dev_err(&pdev->dev, "keypad clock prepare failed\n");
437  goto err_put_clk;
438  }
439 
440  keypad->input_dev = input_dev;
441  keypad->pdev = pdev;
442  keypad->row_shift = row_shift;
443  keypad->rows = pdata->rows;
444  keypad->cols = pdata->cols;
445  keypad->stopped = true;
446  init_waitqueue_head(&keypad->wait);
447 
448  if (pdev->dev.of_node) {
449 #ifdef CONFIG_OF
450  samsung_keypad_parse_dt_gpio(&pdev->dev, keypad);
451  keypad->type = of_device_is_compatible(pdev->dev.of_node,
452  "samsung,s5pv210-keypad");
453 #endif
454  } else {
455  keypad->type = platform_get_device_id(pdev)->driver_data;
456  }
457 
458  input_dev->name = pdev->name;
459  input_dev->id.bustype = BUS_HOST;
460  input_dev->dev.parent = &pdev->dev;
461 
462  input_dev->open = samsung_keypad_open;
463  input_dev->close = samsung_keypad_close;
464 
465  error = matrix_keypad_build_keymap(keymap_data, NULL,
466  pdata->rows, pdata->cols,
467  keypad->keycodes, input_dev);
468  if (error) {
469  dev_err(&pdev->dev, "failed to build keymap\n");
470  goto err_unprepare_clk;
471  }
472 
473  input_set_capability(input_dev, EV_MSC, MSC_SCAN);
474  if (!pdata->no_autorepeat)
475  __set_bit(EV_REP, input_dev->evbit);
476 
477  input_set_drvdata(input_dev, keypad);
478 
479  keypad->irq = platform_get_irq(pdev, 0);
480  if (keypad->irq < 0) {
481  error = keypad->irq;
482  goto err_put_clk;
483  }
484 
485  error = request_threaded_irq(keypad->irq, NULL, samsung_keypad_irq,
486  IRQF_ONESHOT, dev_name(&pdev->dev), keypad);
487  if (error) {
488  dev_err(&pdev->dev, "failed to register keypad interrupt\n");
489  goto err_put_clk;
490  }
491 
492  device_init_wakeup(&pdev->dev, pdata->wakeup);
493  platform_set_drvdata(pdev, keypad);
494  pm_runtime_enable(&pdev->dev);
495 
496  error = input_register_device(keypad->input_dev);
497  if (error)
498  goto err_free_irq;
499 
500  if (pdev->dev.of_node) {
501  devm_kfree(&pdev->dev, (void *)pdata->keymap_data->keymap);
502  devm_kfree(&pdev->dev, (void *)pdata->keymap_data);
503  devm_kfree(&pdev->dev, (void *)pdata);
504  }
505  return 0;
506 
507 err_free_irq:
508  free_irq(keypad->irq, keypad);
509  pm_runtime_disable(&pdev->dev);
510  device_init_wakeup(&pdev->dev, 0);
511  platform_set_drvdata(pdev, NULL);
512 err_unprepare_clk:
513  clk_unprepare(keypad->clk);
514 err_put_clk:
515  clk_put(keypad->clk);
516  samsung_keypad_dt_gpio_free(keypad);
517 err_unmap_base:
518  iounmap(keypad->base);
519 err_free_mem:
520  input_free_device(input_dev);
521  kfree(keypad);
522 
523  return error;
524 }
525 
526 static int __devexit samsung_keypad_remove(struct platform_device *pdev)
527 {
528  struct samsung_keypad *keypad = platform_get_drvdata(pdev);
529 
530  pm_runtime_disable(&pdev->dev);
531  device_init_wakeup(&pdev->dev, 0);
532  platform_set_drvdata(pdev, NULL);
533 
534  input_unregister_device(keypad->input_dev);
535 
536  /*
537  * It is safe to free IRQ after unregistering device because
538  * samsung_keypad_close will shut off interrupts.
539  */
540  free_irq(keypad->irq, keypad);
541 
542  clk_unprepare(keypad->clk);
543  clk_put(keypad->clk);
544  samsung_keypad_dt_gpio_free(keypad);
545 
546  iounmap(keypad->base);
547  kfree(keypad);
548 
549  return 0;
550 }
551 
552 #ifdef CONFIG_PM_RUNTIME
553 static int samsung_keypad_runtime_suspend(struct device *dev)
554 {
555  struct platform_device *pdev = to_platform_device(dev);
556  struct samsung_keypad *keypad = platform_get_drvdata(pdev);
557  unsigned int val;
558  int error;
559 
560  if (keypad->stopped)
561  return 0;
562 
563  /* This may fail on some SoCs due to lack of controller support */
564  error = enable_irq_wake(keypad->irq);
565  if (!error)
566  keypad->wake_enabled = true;
567 
568  val = readl(keypad->base + SAMSUNG_KEYIFCON);
570  writel(val, keypad->base + SAMSUNG_KEYIFCON);
571 
572  clk_disable(keypad->clk);
573 
574  return 0;
575 }
576 
577 static int samsung_keypad_runtime_resume(struct device *dev)
578 {
579  struct platform_device *pdev = to_platform_device(dev);
580  struct samsung_keypad *keypad = platform_get_drvdata(pdev);
581  unsigned int val;
582 
583  if (keypad->stopped)
584  return 0;
585 
586  clk_enable(keypad->clk);
587 
588  val = readl(keypad->base + SAMSUNG_KEYIFCON);
590  writel(val, keypad->base + SAMSUNG_KEYIFCON);
591 
592  if (keypad->wake_enabled)
593  disable_irq_wake(keypad->irq);
594 
595  return 0;
596 }
597 #endif
598 
599 #ifdef CONFIG_PM_SLEEP
600 static void samsung_keypad_toggle_wakeup(struct samsung_keypad *keypad,
601  bool enable)
602 {
603  unsigned int val;
604 
605  clk_enable(keypad->clk);
606 
607  val = readl(keypad->base + SAMSUNG_KEYIFCON);
608  if (enable) {
610  if (device_may_wakeup(&keypad->pdev->dev))
611  enable_irq_wake(keypad->irq);
612  } else {
614  if (device_may_wakeup(&keypad->pdev->dev))
615  disable_irq_wake(keypad->irq);
616  }
617  writel(val, keypad->base + SAMSUNG_KEYIFCON);
618 
619  clk_disable(keypad->clk);
620 }
621 
622 static int samsung_keypad_suspend(struct device *dev)
623 {
624  struct platform_device *pdev = to_platform_device(dev);
625  struct samsung_keypad *keypad = platform_get_drvdata(pdev);
626  struct input_dev *input_dev = keypad->input_dev;
627 
628  mutex_lock(&input_dev->mutex);
629 
630  if (input_dev->users)
631  samsung_keypad_stop(keypad);
632 
633  samsung_keypad_toggle_wakeup(keypad, true);
634 
635  mutex_unlock(&input_dev->mutex);
636 
637  return 0;
638 }
639 
640 static int samsung_keypad_resume(struct device *dev)
641 {
642  struct platform_device *pdev = to_platform_device(dev);
643  struct samsung_keypad *keypad = platform_get_drvdata(pdev);
644  struct input_dev *input_dev = keypad->input_dev;
645 
646  mutex_lock(&input_dev->mutex);
647 
648  samsung_keypad_toggle_wakeup(keypad, false);
649 
650  if (input_dev->users)
651  samsung_keypad_start(keypad);
652 
653  mutex_unlock(&input_dev->mutex);
654 
655  return 0;
656 }
657 #endif
658 
659 static const struct dev_pm_ops samsung_keypad_pm_ops = {
660  SET_SYSTEM_SLEEP_PM_OPS(samsung_keypad_suspend, samsung_keypad_resume)
661  SET_RUNTIME_PM_OPS(samsung_keypad_runtime_suspend,
662  samsung_keypad_runtime_resume, NULL)
663 };
664 
665 #ifdef CONFIG_OF
666 static const struct of_device_id samsung_keypad_dt_match[] = {
667  { .compatible = "samsung,s3c6410-keypad" },
668  { .compatible = "samsung,s5pv210-keypad" },
669  {},
670 };
671 MODULE_DEVICE_TABLE(of, samsung_keypad_dt_match);
672 #endif
673 
674 static struct platform_device_id samsung_keypad_driver_ids[] = {
675  {
676  .name = "samsung-keypad",
677  .driver_data = KEYPAD_TYPE_SAMSUNG,
678  }, {
679  .name = "s5pv210-keypad",
680  .driver_data = KEYPAD_TYPE_S5PV210,
681  },
682  { },
683 };
684 MODULE_DEVICE_TABLE(platform, samsung_keypad_driver_ids);
685 
686 static struct platform_driver samsung_keypad_driver = {
687  .probe = samsung_keypad_probe,
688  .remove = __devexit_p(samsung_keypad_remove),
689  .driver = {
690  .name = "samsung-keypad",
691  .owner = THIS_MODULE,
692  .of_match_table = of_match_ptr(samsung_keypad_dt_match),
693  .pm = &samsung_keypad_pm_ops,
694  },
695  .id_table = samsung_keypad_driver_ids,
696 };
697 module_platform_driver(samsung_keypad_driver);
698 
699 MODULE_DESCRIPTION("Samsung keypad driver");
700 MODULE_AUTHOR("Joonyoung Shim <[email protected]>");
701 MODULE_AUTHOR("Donghwa Lee <[email protected]>");
702 MODULE_LICENSE("GPL");