162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * The Netronix embedded controller is a microcontroller found in some
462306a36Sopenharmony_ci * e-book readers designed by the original design manufacturer Netronix, Inc.
562306a36Sopenharmony_ci * It contains RTC, battery monitoring, system power management, and PWM
662306a36Sopenharmony_ci * functionality.
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * This driver implements PWM output.
962306a36Sopenharmony_ci *
1062306a36Sopenharmony_ci * Copyright 2020 Jonathan Neuschäfer <j.neuschaefer@gmx.net>
1162306a36Sopenharmony_ci *
1262306a36Sopenharmony_ci * Limitations:
1362306a36Sopenharmony_ci * - The get_state callback is not implemented, because the current state of
1462306a36Sopenharmony_ci *   the PWM output can't be read back from the hardware.
1562306a36Sopenharmony_ci * - The hardware can only generate normal polarity output.
1662306a36Sopenharmony_ci * - The period and duty cycle can't be changed together in one atomic action.
1762306a36Sopenharmony_ci */
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci#include <linux/mfd/ntxec.h>
2062306a36Sopenharmony_ci#include <linux/module.h>
2162306a36Sopenharmony_ci#include <linux/platform_device.h>
2262306a36Sopenharmony_ci#include <linux/pwm.h>
2362306a36Sopenharmony_ci#include <linux/regmap.h>
2462306a36Sopenharmony_ci#include <linux/types.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_cistruct ntxec_pwm {
2762306a36Sopenharmony_ci	struct ntxec *ec;
2862306a36Sopenharmony_ci	struct pwm_chip chip;
2962306a36Sopenharmony_ci};
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic struct ntxec_pwm *ntxec_pwm_from_chip(struct pwm_chip *chip)
3262306a36Sopenharmony_ci{
3362306a36Sopenharmony_ci	return container_of(chip, struct ntxec_pwm, chip);
3462306a36Sopenharmony_ci}
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_ci#define NTXEC_REG_AUTO_OFF_HI	0xa1
3762306a36Sopenharmony_ci#define NTXEC_REG_AUTO_OFF_LO	0xa2
3862306a36Sopenharmony_ci#define NTXEC_REG_ENABLE	0xa3
3962306a36Sopenharmony_ci#define NTXEC_REG_PERIOD_LOW	0xa4
4062306a36Sopenharmony_ci#define NTXEC_REG_PERIOD_HIGH	0xa5
4162306a36Sopenharmony_ci#define NTXEC_REG_DUTY_LOW	0xa6
4262306a36Sopenharmony_ci#define NTXEC_REG_DUTY_HIGH	0xa7
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci/*
4562306a36Sopenharmony_ci * The time base used in the EC is 8MHz, or 125ns. Period and duty cycle are
4662306a36Sopenharmony_ci * measured in this unit.
4762306a36Sopenharmony_ci */
4862306a36Sopenharmony_ci#define TIME_BASE_NS 125
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_ci/*
5162306a36Sopenharmony_ci * The maximum input value (in nanoseconds) is determined by the time base and
5262306a36Sopenharmony_ci * the range of the hardware registers that hold the converted value.
5362306a36Sopenharmony_ci * It fits into 32 bits, so we can do our calculations in 32 bits as well.
5462306a36Sopenharmony_ci */
5562306a36Sopenharmony_ci#define MAX_PERIOD_NS (TIME_BASE_NS * 0xffff)
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_cistatic int ntxec_pwm_set_raw_period_and_duty_cycle(struct pwm_chip *chip,
5862306a36Sopenharmony_ci						   int period, int duty)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	struct ntxec_pwm *priv = ntxec_pwm_from_chip(chip);
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci	/*
6362306a36Sopenharmony_ci	 * Changes to the period and duty cycle take effect as soon as the
6462306a36Sopenharmony_ci	 * corresponding low byte is written, so the hardware may be configured
6562306a36Sopenharmony_ci	 * to an inconsistent state after the period is written and before the
6662306a36Sopenharmony_ci	 * duty cycle is fully written. If, in such a case, the old duty cycle
6762306a36Sopenharmony_ci	 * is longer than the new period, the EC may output 100% for a moment.
6862306a36Sopenharmony_ci	 *
6962306a36Sopenharmony_ci	 * To minimize the time between the changes to period and duty cycle
7062306a36Sopenharmony_ci	 * taking effect, the writes are interleaved.
7162306a36Sopenharmony_ci	 */
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	struct reg_sequence regs[] = {
7462306a36Sopenharmony_ci		{ NTXEC_REG_PERIOD_HIGH, ntxec_reg8(period >> 8) },
7562306a36Sopenharmony_ci		{ NTXEC_REG_DUTY_HIGH, ntxec_reg8(duty >> 8) },
7662306a36Sopenharmony_ci		{ NTXEC_REG_PERIOD_LOW, ntxec_reg8(period) },
7762306a36Sopenharmony_ci		{ NTXEC_REG_DUTY_LOW, ntxec_reg8(duty) },
7862306a36Sopenharmony_ci	};
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	return regmap_multi_reg_write(priv->ec->regmap, regs, ARRAY_SIZE(regs));
8162306a36Sopenharmony_ci}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic int ntxec_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm_dev,
8462306a36Sopenharmony_ci			   const struct pwm_state *state)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	struct ntxec_pwm *priv = ntxec_pwm_from_chip(chip);
8762306a36Sopenharmony_ci	unsigned int period, duty;
8862306a36Sopenharmony_ci	int res;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	if (state->polarity != PWM_POLARITY_NORMAL)
9162306a36Sopenharmony_ci		return -EINVAL;
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	period = min_t(u64, state->period, MAX_PERIOD_NS);
9462306a36Sopenharmony_ci	duty   = min_t(u64, state->duty_cycle, period);
9562306a36Sopenharmony_ci
9662306a36Sopenharmony_ci	period /= TIME_BASE_NS;
9762306a36Sopenharmony_ci	duty   /= TIME_BASE_NS;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	/*
10062306a36Sopenharmony_ci	 * Writing a duty cycle of zero puts the device into a state where
10162306a36Sopenharmony_ci	 * writing a higher duty cycle doesn't result in the brightness that it
10262306a36Sopenharmony_ci	 * usually results in. This can be fixed by cycling the ENABLE register.
10362306a36Sopenharmony_ci	 *
10462306a36Sopenharmony_ci	 * As a workaround, write ENABLE=0 when the duty cycle is zero.
10562306a36Sopenharmony_ci	 * The case that something has previously set the duty cycle to zero
10662306a36Sopenharmony_ci	 * but ENABLE=1, is not handled.
10762306a36Sopenharmony_ci	 */
10862306a36Sopenharmony_ci	if (state->enabled && duty != 0) {
10962306a36Sopenharmony_ci		res = ntxec_pwm_set_raw_period_and_duty_cycle(chip, period, duty);
11062306a36Sopenharmony_ci		if (res)
11162306a36Sopenharmony_ci			return res;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci		res = regmap_write(priv->ec->regmap, NTXEC_REG_ENABLE, ntxec_reg8(1));
11462306a36Sopenharmony_ci		if (res)
11562306a36Sopenharmony_ci			return res;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci		/* Disable the auto-off timer */
11862306a36Sopenharmony_ci		res = regmap_write(priv->ec->regmap, NTXEC_REG_AUTO_OFF_HI, ntxec_reg8(0xff));
11962306a36Sopenharmony_ci		if (res)
12062306a36Sopenharmony_ci			return res;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci		return regmap_write(priv->ec->regmap, NTXEC_REG_AUTO_OFF_LO, ntxec_reg8(0xff));
12362306a36Sopenharmony_ci	} else {
12462306a36Sopenharmony_ci		return regmap_write(priv->ec->regmap, NTXEC_REG_ENABLE, ntxec_reg8(0));
12562306a36Sopenharmony_ci	}
12662306a36Sopenharmony_ci}
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_cistatic const struct pwm_ops ntxec_pwm_ops = {
12962306a36Sopenharmony_ci	.owner = THIS_MODULE,
13062306a36Sopenharmony_ci	.apply = ntxec_pwm_apply,
13162306a36Sopenharmony_ci	/*
13262306a36Sopenharmony_ci	 * No .get_state callback, because the current state cannot be read
13362306a36Sopenharmony_ci	 * back from the hardware.
13462306a36Sopenharmony_ci	 */
13562306a36Sopenharmony_ci};
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_cistatic int ntxec_pwm_probe(struct platform_device *pdev)
13862306a36Sopenharmony_ci{
13962306a36Sopenharmony_ci	struct ntxec *ec = dev_get_drvdata(pdev->dev.parent);
14062306a36Sopenharmony_ci	struct ntxec_pwm *priv;
14162306a36Sopenharmony_ci	struct pwm_chip *chip;
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci	device_set_of_node_from_dev(&pdev->dev, pdev->dev.parent);
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
14662306a36Sopenharmony_ci	if (!priv)
14762306a36Sopenharmony_ci		return -ENOMEM;
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci	priv->ec = ec;
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	chip = &priv->chip;
15262306a36Sopenharmony_ci	chip->dev = &pdev->dev;
15362306a36Sopenharmony_ci	chip->ops = &ntxec_pwm_ops;
15462306a36Sopenharmony_ci	chip->npwm = 1;
15562306a36Sopenharmony_ci
15662306a36Sopenharmony_ci	return devm_pwmchip_add(&pdev->dev, chip);
15762306a36Sopenharmony_ci}
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_cistatic struct platform_driver ntxec_pwm_driver = {
16062306a36Sopenharmony_ci	.driver = {
16162306a36Sopenharmony_ci		.name = "ntxec-pwm",
16262306a36Sopenharmony_ci	},
16362306a36Sopenharmony_ci	.probe = ntxec_pwm_probe,
16462306a36Sopenharmony_ci};
16562306a36Sopenharmony_cimodule_platform_driver(ntxec_pwm_driver);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ciMODULE_AUTHOR("Jonathan Neuschäfer <j.neuschaefer@gmx.net>");
16862306a36Sopenharmony_ciMODULE_DESCRIPTION("PWM driver for Netronix EC");
16962306a36Sopenharmony_ciMODULE_LICENSE("GPL");
17062306a36Sopenharmony_ciMODULE_ALIAS("platform:ntxec-pwm");
171