leds-asic3.c 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /*
  3. * Copyright (C) 2011 Paul Parsons <lost.distance@yahoo.com>
  4. */
  5. #include <linux/kernel.h>
  6. #include <linux/platform_device.h>
  7. #include <linux/leds.h>
  8. #include <linux/slab.h>
  9. #include <linux/mfd/asic3.h>
  10. #include <linux/mfd/core.h>
  11. #include <linux/module.h>
  12. /*
  13. * The HTC ASIC3 LED GPIOs are inputs, not outputs.
  14. * Hence we turn the LEDs on/off via the TimeBase register.
  15. */
  16. /*
  17. * When TimeBase is 4 the clock resolution is about 32Hz.
  18. * This driver supports hardware blinking with an on+off
  19. * period from 62ms (2 clocks) to 125s (4000 clocks).
  20. */
  21. #define MS_TO_CLK(ms) DIV_ROUND_CLOSEST(((ms)*1024), 32000)
  22. #define CLK_TO_MS(clk) (((clk)*32000)/1024)
  23. #define MAX_CLK 4000 /* Fits into 12-bit Time registers */
  24. #define MAX_MS CLK_TO_MS(MAX_CLK)
  25. static const unsigned int led_n_base[ASIC3_NUM_LEDS] = {
  26. [0] = ASIC3_LED_0_Base,
  27. [1] = ASIC3_LED_1_Base,
  28. [2] = ASIC3_LED_2_Base,
  29. };
  30. static void brightness_set(struct led_classdev *cdev,
  31. enum led_brightness value)
  32. {
  33. struct platform_device *pdev = to_platform_device(cdev->dev->parent);
  34. const struct mfd_cell *cell = mfd_get_cell(pdev);
  35. struct asic3 *asic = dev_get_drvdata(pdev->dev.parent);
  36. u32 timebase;
  37. unsigned int base;
  38. timebase = (value == LED_OFF) ? 0 : (LED_EN|0x4);
  39. base = led_n_base[cell->id];
  40. asic3_write_register(asic, (base + ASIC3_LED_PeriodTime), 32);
  41. asic3_write_register(asic, (base + ASIC3_LED_DutyTime), 32);
  42. asic3_write_register(asic, (base + ASIC3_LED_AutoStopCount), 0);
  43. asic3_write_register(asic, (base + ASIC3_LED_TimeBase), timebase);
  44. }
  45. static int blink_set(struct led_classdev *cdev,
  46. unsigned long *delay_on,
  47. unsigned long *delay_off)
  48. {
  49. struct platform_device *pdev = to_platform_device(cdev->dev->parent);
  50. const struct mfd_cell *cell = mfd_get_cell(pdev);
  51. struct asic3 *asic = dev_get_drvdata(pdev->dev.parent);
  52. u32 on;
  53. u32 off;
  54. unsigned int base;
  55. if (*delay_on > MAX_MS || *delay_off > MAX_MS)
  56. return -EINVAL;
  57. if (*delay_on == 0 && *delay_off == 0) {
  58. /* If both are zero then a sensible default should be chosen */
  59. on = MS_TO_CLK(500);
  60. off = MS_TO_CLK(500);
  61. } else {
  62. on = MS_TO_CLK(*delay_on);
  63. off = MS_TO_CLK(*delay_off);
  64. if ((on + off) > MAX_CLK)
  65. return -EINVAL;
  66. }
  67. base = led_n_base[cell->id];
  68. asic3_write_register(asic, (base + ASIC3_LED_PeriodTime), (on + off));
  69. asic3_write_register(asic, (base + ASIC3_LED_DutyTime), on);
  70. asic3_write_register(asic, (base + ASIC3_LED_AutoStopCount), 0);
  71. asic3_write_register(asic, (base + ASIC3_LED_TimeBase), (LED_EN|0x4));
  72. *delay_on = CLK_TO_MS(on);
  73. *delay_off = CLK_TO_MS(off);
  74. return 0;
  75. }
  76. static int asic3_led_probe(struct platform_device *pdev)
  77. {
  78. struct asic3_led *led = dev_get_platdata(&pdev->dev);
  79. int ret;
  80. ret = mfd_cell_enable(pdev);
  81. if (ret < 0)
  82. return ret;
  83. led->cdev = devm_kzalloc(&pdev->dev, sizeof(struct led_classdev),
  84. GFP_KERNEL);
  85. if (!led->cdev) {
  86. ret = -ENOMEM;
  87. goto out;
  88. }
  89. led->cdev->name = led->name;
  90. led->cdev->flags = LED_CORE_SUSPENDRESUME;
  91. led->cdev->brightness_set = brightness_set;
  92. led->cdev->blink_set = blink_set;
  93. led->cdev->default_trigger = led->default_trigger;
  94. ret = led_classdev_register(&pdev->dev, led->cdev);
  95. if (ret < 0)
  96. goto out;
  97. return 0;
  98. out:
  99. (void) mfd_cell_disable(pdev);
  100. return ret;
  101. }
  102. static int asic3_led_remove(struct platform_device *pdev)
  103. {
  104. struct asic3_led *led = dev_get_platdata(&pdev->dev);
  105. led_classdev_unregister(led->cdev);
  106. return mfd_cell_disable(pdev);
  107. }
  108. #ifdef CONFIG_PM_SLEEP
  109. static int asic3_led_suspend(struct device *dev)
  110. {
  111. struct platform_device *pdev = to_platform_device(dev);
  112. const struct mfd_cell *cell = mfd_get_cell(pdev);
  113. int ret;
  114. ret = 0;
  115. if (cell->suspend)
  116. ret = (*cell->suspend)(pdev);
  117. return ret;
  118. }
  119. static int asic3_led_resume(struct device *dev)
  120. {
  121. struct platform_device *pdev = to_platform_device(dev);
  122. const struct mfd_cell *cell = mfd_get_cell(pdev);
  123. int ret;
  124. ret = 0;
  125. if (cell->resume)
  126. ret = (*cell->resume)(pdev);
  127. return ret;
  128. }
  129. #endif
  130. static SIMPLE_DEV_PM_OPS(asic3_led_pm_ops, asic3_led_suspend, asic3_led_resume);
  131. static struct platform_driver asic3_led_driver = {
  132. .probe = asic3_led_probe,
  133. .remove = asic3_led_remove,
  134. .driver = {
  135. .name = "leds-asic3",
  136. .pm = &asic3_led_pm_ops,
  137. },
  138. };
  139. module_platform_driver(asic3_led_driver);
  140. MODULE_AUTHOR("Paul Parsons <lost.distance@yahoo.com>");
  141. MODULE_DESCRIPTION("HTC ASIC3 LED driver");
  142. MODULE_LICENSE("GPL");
  143. MODULE_ALIAS("platform:leds-asic3");