Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0-or-later
0002 /*
0003  * The Netronix embedded controller is a microcontroller found in some
0004  * e-book readers designed by the original design manufacturer Netronix, Inc.
0005  * It contains RTC, battery monitoring, system power management, and PWM
0006  * functionality.
0007  *
0008  * This driver implements PWM output.
0009  *
0010  * Copyright 2020 Jonathan Neuschäfer <j.neuschaefer@gmx.net>
0011  *
0012  * Limitations:
0013  * - The get_state callback is not implemented, because the current state of
0014  *   the PWM output can't be read back from the hardware.
0015  * - The hardware can only generate normal polarity output.
0016  * - The period and duty cycle can't be changed together in one atomic action.
0017  */
0018 
0019 #include <linux/mfd/ntxec.h>
0020 #include <linux/module.h>
0021 #include <linux/platform_device.h>
0022 #include <linux/pwm.h>
0023 #include <linux/regmap.h>
0024 #include <linux/types.h>
0025 
0026 struct ntxec_pwm {
0027     struct device *dev;
0028     struct ntxec *ec;
0029     struct pwm_chip chip;
0030 };
0031 
0032 static struct ntxec_pwm *ntxec_pwm_from_chip(struct pwm_chip *chip)
0033 {
0034     return container_of(chip, struct ntxec_pwm, chip);
0035 }
0036 
0037 #define NTXEC_REG_AUTO_OFF_HI   0xa1
0038 #define NTXEC_REG_AUTO_OFF_LO   0xa2
0039 #define NTXEC_REG_ENABLE    0xa3
0040 #define NTXEC_REG_PERIOD_LOW    0xa4
0041 #define NTXEC_REG_PERIOD_HIGH   0xa5
0042 #define NTXEC_REG_DUTY_LOW  0xa6
0043 #define NTXEC_REG_DUTY_HIGH 0xa7
0044 
0045 /*
0046  * The time base used in the EC is 8MHz, or 125ns. Period and duty cycle are
0047  * measured in this unit.
0048  */
0049 #define TIME_BASE_NS 125
0050 
0051 /*
0052  * The maximum input value (in nanoseconds) is determined by the time base and
0053  * the range of the hardware registers that hold the converted value.
0054  * It fits into 32 bits, so we can do our calculations in 32 bits as well.
0055  */
0056 #define MAX_PERIOD_NS (TIME_BASE_NS * 0xffff)
0057 
0058 static int ntxec_pwm_set_raw_period_and_duty_cycle(struct pwm_chip *chip,
0059                            int period, int duty)
0060 {
0061     struct ntxec_pwm *priv = ntxec_pwm_from_chip(chip);
0062 
0063     /*
0064      * Changes to the period and duty cycle take effect as soon as the
0065      * corresponding low byte is written, so the hardware may be configured
0066      * to an inconsistent state after the period is written and before the
0067      * duty cycle is fully written. If, in such a case, the old duty cycle
0068      * is longer than the new period, the EC may output 100% for a moment.
0069      *
0070      * To minimize the time between the changes to period and duty cycle
0071      * taking effect, the writes are interleaved.
0072      */
0073 
0074     struct reg_sequence regs[] = {
0075         { NTXEC_REG_PERIOD_HIGH, ntxec_reg8(period >> 8) },
0076         { NTXEC_REG_DUTY_HIGH, ntxec_reg8(duty >> 8) },
0077         { NTXEC_REG_PERIOD_LOW, ntxec_reg8(period) },
0078         { NTXEC_REG_DUTY_LOW, ntxec_reg8(duty) },
0079     };
0080 
0081     return regmap_multi_reg_write(priv->ec->regmap, regs, ARRAY_SIZE(regs));
0082 }
0083 
0084 static int ntxec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm_dev,
0085                const struct pwm_state *state)
0086 {
0087     struct ntxec_pwm *priv = ntxec_pwm_from_chip(chip);
0088     unsigned int period, duty;
0089     int res;
0090 
0091     if (state->polarity != PWM_POLARITY_NORMAL)
0092         return -EINVAL;
0093 
0094     period = min_t(u64, state->period, MAX_PERIOD_NS);
0095     duty   = min_t(u64, state->duty_cycle, period);
0096 
0097     period /= TIME_BASE_NS;
0098     duty   /= TIME_BASE_NS;
0099 
0100     /*
0101      * Writing a duty cycle of zero puts the device into a state where
0102      * writing a higher duty cycle doesn't result in the brightness that it
0103      * usually results in. This can be fixed by cycling the ENABLE register.
0104      *
0105      * As a workaround, write ENABLE=0 when the duty cycle is zero.
0106      * The case that something has previously set the duty cycle to zero
0107      * but ENABLE=1, is not handled.
0108      */
0109     if (state->enabled && duty != 0) {
0110         res = ntxec_pwm_set_raw_period_and_duty_cycle(chip, period, duty);
0111         if (res)
0112             return res;
0113 
0114         res = regmap_write(priv->ec->regmap, NTXEC_REG_ENABLE, ntxec_reg8(1));
0115         if (res)
0116             return res;
0117 
0118         /* Disable the auto-off timer */
0119         res = regmap_write(priv->ec->regmap, NTXEC_REG_AUTO_OFF_HI, ntxec_reg8(0xff));
0120         if (res)
0121             return res;
0122 
0123         return regmap_write(priv->ec->regmap, NTXEC_REG_AUTO_OFF_LO, ntxec_reg8(0xff));
0124     } else {
0125         return regmap_write(priv->ec->regmap, NTXEC_REG_ENABLE, ntxec_reg8(0));
0126     }
0127 }
0128 
0129 static const struct pwm_ops ntxec_pwm_ops = {
0130     .owner = THIS_MODULE,
0131     .apply = ntxec_pwm_apply,
0132     /*
0133      * No .get_state callback, because the current state cannot be read
0134      * back from the hardware.
0135      */
0136 };
0137 
0138 static int ntxec_pwm_probe(struct platform_device *pdev)
0139 {
0140     struct ntxec *ec = dev_get_drvdata(pdev->dev.parent);
0141     struct ntxec_pwm *priv;
0142     struct pwm_chip *chip;
0143 
0144     pdev->dev.of_node = pdev->dev.parent->of_node;
0145 
0146     priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
0147     if (!priv)
0148         return -ENOMEM;
0149 
0150     priv->ec = ec;
0151     priv->dev = &pdev->dev;
0152 
0153     chip = &priv->chip;
0154     chip->dev = &pdev->dev;
0155     chip->ops = &ntxec_pwm_ops;
0156     chip->npwm = 1;
0157 
0158     return devm_pwmchip_add(&pdev->dev, chip);
0159 }
0160 
0161 static struct platform_driver ntxec_pwm_driver = {
0162     .driver = {
0163         .name = "ntxec-pwm",
0164     },
0165     .probe = ntxec_pwm_probe,
0166 };
0167 module_platform_driver(ntxec_pwm_driver);
0168 
0169 MODULE_AUTHOR("Jonathan Neuschäfer <j.neuschaefer@gmx.net>");
0170 MODULE_DESCRIPTION("PWM driver for Netronix EC");
0171 MODULE_LICENSE("GPL");
0172 MODULE_ALIAS("platform:ntxec-pwm");