linux/drivers/pwm/pwm-ntxec.c
<<
>>
Prefs
   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 <j.neuschaefer@gmx.net>
  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
  19#include <linux/mfd/ntxec.h>
  20#include <linux/module.h>
  21#include <linux/platform_device.h>
  22#include <linux/pwm.h>
  23#include <linux/regmap.h>
  24#include <linux/types.h>
  25
  26struct ntxec_pwm {
  27        struct device *dev;
  28        struct ntxec *ec;
  29        struct pwm_chip chip;
  30};
  31
  32static struct ntxec_pwm *ntxec_pwm_from_chip(struct pwm_chip *chip)
  33{
  34        return container_of(chip, struct ntxec_pwm, chip);
  35}
  36
  37#define NTXEC_REG_AUTO_OFF_HI   0xa1
  38#define NTXEC_REG_AUTO_OFF_LO   0xa2
  39#define NTXEC_REG_ENABLE        0xa3
  40#define NTXEC_REG_PERIOD_LOW    0xa4
  41#define NTXEC_REG_PERIOD_HIGH   0xa5
  42#define NTXEC_REG_DUTY_LOW      0xa6
  43#define NTXEC_REG_DUTY_HIGH     0xa7
  44
  45/*
  46 * The time base used in the EC is 8MHz, or 125ns. Period and duty cycle are
  47 * measured in this unit.
  48 */
  49#define TIME_BASE_NS 125
  50
  51/*
  52 * The maximum input value (in nanoseconds) is determined by the time base and
  53 * the range of the hardware registers that hold the converted value.
  54 * It fits into 32 bits, so we can do our calculations in 32 bits as well.
  55 */
  56#define MAX_PERIOD_NS (TIME_BASE_NS * 0xffff)
  57
  58static int ntxec_pwm_set_raw_period_and_duty_cycle(struct pwm_chip *chip,
  59                                                   int period, int duty)
  60{
  61        struct ntxec_pwm *priv = ntxec_pwm_from_chip(chip);
  62
  63        /*
  64         * Changes to the period and duty cycle take effect as soon as the
  65         * corresponding low byte is written, so the hardware may be configured
  66         * to an inconsistent state after the period is written and before the
  67         * duty cycle is fully written. If, in such a case, the old duty cycle
  68         * is longer than the new period, the EC may output 100% for a moment.
  69         *
  70         * To minimize the time between the changes to period and duty cycle
  71         * taking effect, the writes are interleaved.
  72         */
  73
  74        struct reg_sequence regs[] = {
  75                { NTXEC_REG_PERIOD_HIGH, ntxec_reg8(period >> 8) },
  76                { NTXEC_REG_DUTY_HIGH, ntxec_reg8(duty >> 8) },
  77                { NTXEC_REG_PERIOD_LOW, ntxec_reg8(period) },
  78                { NTXEC_REG_DUTY_LOW, ntxec_reg8(duty) },
  79        };
  80
  81        return regmap_multi_reg_write(priv->ec->regmap, regs, ARRAY_SIZE(regs));
  82}
  83
  84static int ntxec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm_dev,
  85                           const struct pwm_state *state)
  86{
  87        struct ntxec_pwm *priv = ntxec_pwm_from_chip(chip);
  88        unsigned int period, duty;
  89        int res;
  90
  91        if (state->polarity != PWM_POLARITY_NORMAL)
  92                return -EINVAL;
  93
  94        period = min_t(u64, state->period, MAX_PERIOD_NS);
  95        duty   = min_t(u64, state->duty_cycle, period);
  96
  97        period /= TIME_BASE_NS;
  98        duty   /= TIME_BASE_NS;
  99
 100        /*
 101         * Writing a duty cycle of zero puts the device into a state where
 102         * writing a higher duty cycle doesn't result in the brightness that it
 103         * usually results in. This can be fixed by cycling the ENABLE register.
 104         *
 105         * As a workaround, write ENABLE=0 when the duty cycle is zero.
 106         * The case that something has previously set the duty cycle to zero
 107         * but ENABLE=1, is not handled.
 108         */
 109        if (state->enabled && duty != 0) {
 110                res = ntxec_pwm_set_raw_period_and_duty_cycle(chip, period, duty);
 111                if (res)
 112                        return res;
 113
 114                res = regmap_write(priv->ec->regmap, NTXEC_REG_ENABLE, ntxec_reg8(1));
 115                if (res)
 116                        return res;
 117
 118                /* Disable the auto-off timer */
 119                res = regmap_write(priv->ec->regmap, NTXEC_REG_AUTO_OFF_HI, ntxec_reg8(0xff));
 120                if (res)
 121                        return res;
 122
 123                return regmap_write(priv->ec->regmap, NTXEC_REG_AUTO_OFF_LO, ntxec_reg8(0xff));
 124        } else {
 125                return regmap_write(priv->ec->regmap, NTXEC_REG_ENABLE, ntxec_reg8(0));
 126        }
 127}
 128
 129static const struct pwm_ops ntxec_pwm_ops = {
 130        .owner = THIS_MODULE,
 131        .apply = ntxec_pwm_apply,
 132        /*
 133         * No .get_state callback, because the current state cannot be read
 134         * back from the hardware.
 135         */
 136};
 137
 138static int ntxec_pwm_probe(struct platform_device *pdev)
 139{
 140        struct ntxec *ec = dev_get_drvdata(pdev->dev.parent);
 141        struct ntxec_pwm *priv;
 142        struct pwm_chip *chip;
 143
 144        pdev->dev.of_node = pdev->dev.parent->of_node;
 145
 146        priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
 147        if (!priv)
 148                return -ENOMEM;
 149
 150        priv->ec = ec;
 151        priv->dev = &pdev->dev;
 152
 153        chip = &priv->chip;
 154        chip->dev = &pdev->dev;
 155        chip->ops = &ntxec_pwm_ops;
 156        chip->npwm = 1;
 157
 158        return devm_pwmchip_add(&pdev->dev, chip);
 159}
 160
 161static struct platform_driver ntxec_pwm_driver = {
 162        .driver = {
 163                .name = "ntxec-pwm",
 164        },
 165        .probe = ntxec_pwm_probe,
 166};
 167module_platform_driver(ntxec_pwm_driver);
 168
 169MODULE_AUTHOR("Jonathan Neuschäfer <j.neuschaefer@gmx.net>");
 170MODULE_DESCRIPTION("PWM driver for Netronix EC");
 171MODULE_LICENSE("GPL");
 172MODULE_ALIAS("platform:ntxec-pwm");
 173