cgbc_bl.c 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. // SPDX-License-Identifier: GPL-2.0-only
  2. /*
  3. * Congatec Board Controller (CGBC) Backlight Driver
  4. *
  5. * This driver provides backlight control for LCD displays connected to
  6. * Congatec boards via the CGBC (Congatec Board Controller). It integrates
  7. * with the Linux backlight subsystem and communicates with hardware through
  8. * the cgbc-core module.
  9. *
  10. * Copyright (C) 2025 Novatron Oy
  11. *
  12. * Author: Petri Karhula <petri.karhula@novatron.fi>
  13. */
  14. #include <linux/backlight.h>
  15. #include <linux/bitfield.h>
  16. #include <linux/bits.h>
  17. #include <linux/mfd/cgbc.h>
  18. #include <linux/module.h>
  19. #include <linux/platform_device.h>
  20. #define BLT_PWM_DUTY_MASK GENMASK(6, 0)
  21. /* CGBC command for PWM brightness control*/
  22. #define CGBC_CMD_BLT0_PWM 0x75
  23. #define CGBC_BL_MAX_BRIGHTNESS 100
  24. /**
  25. * CGBC backlight driver data
  26. * @dev: Pointer to the platform device
  27. * @cgbc: Pointer to the parent CGBC device data
  28. * @current_brightness: Current brightness level (0-100)
  29. */
  30. struct cgbc_bl_data {
  31. struct device *dev;
  32. struct cgbc_device_data *cgbc;
  33. unsigned int current_brightness;
  34. };
  35. static int cgbc_bl_read_brightness(struct cgbc_bl_data *bl_data)
  36. {
  37. u8 cmd_buf[4] = { CGBC_CMD_BLT0_PWM };
  38. u8 reply_buf[3];
  39. int ret;
  40. ret = cgbc_command(bl_data->cgbc, cmd_buf, sizeof(cmd_buf),
  41. reply_buf, sizeof(reply_buf), NULL);
  42. if (ret < 0)
  43. return ret;
  44. /*
  45. * Get only PWM duty factor percentage,
  46. * ignore polarity inversion bit (bit 7)
  47. */
  48. bl_data->current_brightness = FIELD_GET(BLT_PWM_DUTY_MASK, reply_buf[0]);
  49. return 0;
  50. }
  51. static int cgbc_bl_update_status(struct backlight_device *bl)
  52. {
  53. struct cgbc_bl_data *bl_data = bl_get_data(bl);
  54. u8 cmd_buf[4] = { CGBC_CMD_BLT0_PWM };
  55. u8 reply_buf[3];
  56. u8 brightness;
  57. int ret;
  58. brightness = backlight_get_brightness(bl);
  59. if (brightness != bl_data->current_brightness) {
  60. /* Read the current values */
  61. ret = cgbc_command(bl_data->cgbc, cmd_buf, sizeof(cmd_buf), reply_buf,
  62. sizeof(reply_buf), NULL);
  63. if (ret < 0) {
  64. dev_err(bl_data->dev, "Failed to read PWM settings: %d\n", ret);
  65. return ret;
  66. }
  67. /*
  68. * Prepare command buffer for writing new settings. Only 2nd byte is changed
  69. * to set new brightness (PWM duty cycle %). Other values (polarity, frequency)
  70. * are preserved from the read values.
  71. */
  72. cmd_buf[1] = (reply_buf[0] & ~BLT_PWM_DUTY_MASK) |
  73. FIELD_PREP(BLT_PWM_DUTY_MASK, brightness);
  74. cmd_buf[2] = reply_buf[1];
  75. cmd_buf[3] = reply_buf[2];
  76. ret = cgbc_command(bl_data->cgbc, cmd_buf, sizeof(cmd_buf), reply_buf,
  77. sizeof(reply_buf), NULL);
  78. if (ret < 0) {
  79. dev_err(bl_data->dev, "Failed to set brightness: %d\n", ret);
  80. return ret;
  81. }
  82. bl_data->current_brightness = reply_buf[0] & BLT_PWM_DUTY_MASK;
  83. /* Verify the setting was applied correctly */
  84. if (bl_data->current_brightness != brightness) {
  85. dev_err(bl_data->dev,
  86. "Brightness setting verification failed (got %u, expected %u)\n",
  87. bl_data->current_brightness, (unsigned int)brightness);
  88. return -EIO;
  89. }
  90. }
  91. return 0;
  92. }
  93. static int cgbc_bl_get_brightness(struct backlight_device *bl)
  94. {
  95. struct cgbc_bl_data *bl_data = bl_get_data(bl);
  96. int ret;
  97. ret = cgbc_bl_read_brightness(bl_data);
  98. if (ret < 0) {
  99. dev_err(bl_data->dev, "Failed to read brightness: %d\n", ret);
  100. return ret;
  101. }
  102. return bl_data->current_brightness;
  103. }
  104. static const struct backlight_ops cgbc_bl_ops = {
  105. .options = BL_CORE_SUSPENDRESUME,
  106. .update_status = cgbc_bl_update_status,
  107. .get_brightness = cgbc_bl_get_brightness,
  108. };
  109. static int cgbc_bl_probe(struct platform_device *pdev)
  110. {
  111. struct cgbc_device_data *cgbc = dev_get_drvdata(pdev->dev.parent);
  112. struct backlight_properties props = { };
  113. struct backlight_device *bl_dev;
  114. struct cgbc_bl_data *bl_data;
  115. int ret;
  116. bl_data = devm_kzalloc(&pdev->dev, sizeof(*bl_data), GFP_KERNEL);
  117. if (!bl_data)
  118. return -ENOMEM;
  119. bl_data->dev = &pdev->dev;
  120. bl_data->cgbc = cgbc;
  121. ret = cgbc_bl_read_brightness(bl_data);
  122. if (ret < 0)
  123. return dev_err_probe(&pdev->dev, ret,
  124. "Failed to read initial brightness\n");
  125. props.type = BACKLIGHT_PLATFORM;
  126. props.max_brightness = CGBC_BL_MAX_BRIGHTNESS;
  127. props.brightness = bl_data->current_brightness;
  128. props.scale = BACKLIGHT_SCALE_LINEAR;
  129. bl_dev = devm_backlight_device_register(&pdev->dev, "cgbc-backlight",
  130. &pdev->dev, bl_data,
  131. &cgbc_bl_ops, &props);
  132. if (IS_ERR(bl_dev))
  133. return dev_err_probe(&pdev->dev, PTR_ERR(bl_dev),
  134. "Failed to register backlight device\n");
  135. platform_set_drvdata(pdev, bl_data);
  136. return 0;
  137. }
  138. static struct platform_driver cgbc_bl_driver = {
  139. .driver = {
  140. .name = "cgbc-backlight",
  141. },
  142. .probe = cgbc_bl_probe,
  143. };
  144. module_platform_driver(cgbc_bl_driver);
  145. MODULE_AUTHOR("Petri Karhula <petri.karhula@novatron.fi>");
  146. MODULE_DESCRIPTION("Congatec Board Controller (CGBC) Backlight Driver");
  147. MODULE_LICENSE("GPL");
  148. MODULE_ALIAS("platform:cgbc-backlight");