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