pwm-ntxec.c 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. // SPDX-License-Identifier: GPL-2.0-or-later
  2. /*
  3. * The Netronix embedded controller is a microcontroller found in some
  4. * e-book readers designed by the original design manufacturer Netronix, Inc.
  5. * It contains RTC, battery monitoring, system power management, and PWM
  6. * functionality.
  7. *
  8. * This driver implements PWM output.
  9. *
  10. * Copyright 2020 Jonathan Neuschäfer <[email protected]>
  11. *
  12. * Limitations:
  13. * - The get_state callback is not implemented, because the current state of
  14. * the PWM output can't be read back from the hardware.
  15. * - The hardware can only generate normal polarity output.
  16. * - The period and duty cycle can't be changed together in one atomic action.
  17. */
  18. #include <linux/mfd/ntxec.h>
  19. #include <linux/module.h>
  20. #include <linux/platform_device.h>
  21. #include <linux/pwm.h>
  22. #include <linux/regmap.h>
  23. #include <linux/types.h>
  24. struct ntxec_pwm {
  25. struct device *dev;
  26. struct ntxec *ec;
  27. struct pwm_chip chip;
  28. };
  29. static struct ntxec_pwm *ntxec_pwm_from_chip(struct pwm_chip *chip)
  30. {
  31. return container_of(chip, struct ntxec_pwm, chip);
  32. }
  33. #define NTXEC_REG_AUTO_OFF_HI 0xa1
  34. #define NTXEC_REG_AUTO_OFF_LO 0xa2
  35. #define NTXEC_REG_ENABLE 0xa3
  36. #define NTXEC_REG_PERIOD_LOW 0xa4
  37. #define NTXEC_REG_PERIOD_HIGH 0xa5
  38. #define NTXEC_REG_DUTY_LOW 0xa6
  39. #define NTXEC_REG_DUTY_HIGH 0xa7
  40. /*
  41. * The time base used in the EC is 8MHz, or 125ns. Period and duty cycle are
  42. * measured in this unit.
  43. */
  44. #define TIME_BASE_NS 125
  45. /*
  46. * The maximum input value (in nanoseconds) is determined by the time base and
  47. * the range of the hardware registers that hold the converted value.
  48. * It fits into 32 bits, so we can do our calculations in 32 bits as well.
  49. */
  50. #define MAX_PERIOD_NS (TIME_BASE_NS * 0xffff)
  51. static int ntxec_pwm_set_raw_period_and_duty_cycle(struct pwm_chip *chip,
  52. int period, int duty)
  53. {
  54. struct ntxec_pwm *priv = ntxec_pwm_from_chip(chip);
  55. /*
  56. * Changes to the period and duty cycle take effect as soon as the
  57. * corresponding low byte is written, so the hardware may be configured
  58. * to an inconsistent state after the period is written and before the
  59. * duty cycle is fully written. If, in such a case, the old duty cycle
  60. * is longer than the new period, the EC may output 100% for a moment.
  61. *
  62. * To minimize the time between the changes to period and duty cycle
  63. * taking effect, the writes are interleaved.
  64. */
  65. struct reg_sequence regs[] = {
  66. { NTXEC_REG_PERIOD_HIGH, ntxec_reg8(period >> 8) },
  67. { NTXEC_REG_DUTY_HIGH, ntxec_reg8(duty >> 8) },
  68. { NTXEC_REG_PERIOD_LOW, ntxec_reg8(period) },
  69. { NTXEC_REG_DUTY_LOW, ntxec_reg8(duty) },
  70. };
  71. return regmap_multi_reg_write(priv->ec->regmap, regs, ARRAY_SIZE(regs));
  72. }
  73. static int ntxec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm_dev,
  74. const struct pwm_state *state)
  75. {
  76. struct ntxec_pwm *priv = ntxec_pwm_from_chip(chip);
  77. unsigned int period, duty;
  78. int res;
  79. if (state->polarity != PWM_POLARITY_NORMAL)
  80. return -EINVAL;
  81. period = min_t(u64, state->period, MAX_PERIOD_NS);
  82. duty = min_t(u64, state->duty_cycle, period);
  83. period /= TIME_BASE_NS;
  84. duty /= TIME_BASE_NS;
  85. /*
  86. * Writing a duty cycle of zero puts the device into a state where
  87. * writing a higher duty cycle doesn't result in the brightness that it
  88. * usually results in. This can be fixed by cycling the ENABLE register.
  89. *
  90. * As a workaround, write ENABLE=0 when the duty cycle is zero.
  91. * The case that something has previously set the duty cycle to zero
  92. * but ENABLE=1, is not handled.
  93. */
  94. if (state->enabled && duty != 0) {
  95. res = ntxec_pwm_set_raw_period_and_duty_cycle(chip, period, duty);
  96. if (res)
  97. return res;
  98. res = regmap_write(priv->ec->regmap, NTXEC_REG_ENABLE, ntxec_reg8(1));
  99. if (res)
  100. return res;
  101. /* Disable the auto-off timer */
  102. res = regmap_write(priv->ec->regmap, NTXEC_REG_AUTO_OFF_HI, ntxec_reg8(0xff));
  103. if (res)
  104. return res;
  105. return regmap_write(priv->ec->regmap, NTXEC_REG_AUTO_OFF_LO, ntxec_reg8(0xff));
  106. } else {
  107. return regmap_write(priv->ec->regmap, NTXEC_REG_ENABLE, ntxec_reg8(0));
  108. }
  109. }
  110. static const struct pwm_ops ntxec_pwm_ops = {
  111. .owner = THIS_MODULE,
  112. .apply = ntxec_pwm_apply,
  113. /*
  114. * No .get_state callback, because the current state cannot be read
  115. * back from the hardware.
  116. */
  117. };
  118. static int ntxec_pwm_probe(struct platform_device *pdev)
  119. {
  120. struct ntxec *ec = dev_get_drvdata(pdev->dev.parent);
  121. struct ntxec_pwm *priv;
  122. struct pwm_chip *chip;
  123. pdev->dev.of_node = pdev->dev.parent->of_node;
  124. priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
  125. if (!priv)
  126. return -ENOMEM;
  127. priv->ec = ec;
  128. priv->dev = &pdev->dev;
  129. chip = &priv->chip;
  130. chip->dev = &pdev->dev;
  131. chip->ops = &ntxec_pwm_ops;
  132. chip->npwm = 1;
  133. return devm_pwmchip_add(&pdev->dev, chip);
  134. }
  135. static struct platform_driver ntxec_pwm_driver = {
  136. .driver = {
  137. .name = "ntxec-pwm",
  138. },
  139. .probe = ntxec_pwm_probe,
  140. };
  141. module_platform_driver(ntxec_pwm_driver);
  142. MODULE_AUTHOR("Jonathan Neuschäfer <[email protected]>");
  143. MODULE_DESCRIPTION("PWM driver for Netronix EC");
  144. MODULE_LICENSE("GPL");
  145. MODULE_ALIAS("platform:ntxec-pwm");