// SPDX-License-Identifier: GPL-2.0-only /* * Copyright 2025 Bootlin * * Author: Kamel BOUHARA * Author: Mathieu Dubois-Briand * * PWM functionality of the MAX7360 multi-function device. * https://www.analog.com/media/en/technical-documentation/data-sheets/MAX7360.pdf * * Limitations: * - Only supports normal polarity. * - The period is fixed to 2 ms. * - Only the duty cycle can be changed, new values are applied at the beginning * of the next cycle. * - When disabled, the output is put in Hi-Z immediately. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #define MAX7360_NUM_PWMS 8 #define MAX7360_PWM_MAX 255 #define MAX7360_PWM_STEPS 256 #define MAX7360_PWM_PERIOD_NS (2 * NSEC_PER_MSEC) struct max7360_pwm_waveform { u8 duty_steps; bool enabled; }; static int max7360_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) { struct regmap *regmap = pwmchip_get_drvdata(chip); /* * Make sure we use the individual PWM configuration register and not * the global one. * We never need to use the global one, so there is no need to revert * that in the .free() callback. */ return regmap_write_bits(regmap, MAX7360_REG_PWMCFG(pwm->hwpwm), MAX7360_PORT_CFG_COMMON_PWM, 0); } static int max7360_pwm_round_waveform_tohw(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_waveform *wf, void *_wfhw) { struct max7360_pwm_waveform *wfhw = _wfhw; u64 duty_steps; /* * Ignore user provided values for period_length_ns and duty_offset_ns: * we only support fixed period of MAX7360_PWM_PERIOD_NS and offset of 0. * Values from 0 to 254 as duty_steps will provide duty cycles of 0/256 * to 254/256, while value 255 will provide a duty cycle of 100%. */ if (wf->duty_length_ns >= MAX7360_PWM_PERIOD_NS) { duty_steps = MAX7360_PWM_MAX; } else { duty_steps = (u32)wf->duty_length_ns * MAX7360_PWM_STEPS / MAX7360_PWM_PERIOD_NS; if (duty_steps == MAX7360_PWM_MAX) duty_steps = MAX7360_PWM_MAX - 1; } wfhw->duty_steps = min(MAX7360_PWM_MAX, duty_steps); wfhw->enabled = !!wf->period_length_ns; if (wf->period_length_ns && wf->period_length_ns < MAX7360_PWM_PERIOD_NS) return 1; else return 0; } static int max7360_pwm_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm, const void *_wfhw, struct pwm_waveform *wf) { const struct max7360_pwm_waveform *wfhw = _wfhw; wf->period_length_ns = wfhw->enabled ? MAX7360_PWM_PERIOD_NS : 0; wf->duty_offset_ns = 0; if (wfhw->enabled) { if (wfhw->duty_steps == MAX7360_PWM_MAX) wf->duty_length_ns = MAX7360_PWM_PERIOD_NS; else wf->duty_length_ns = DIV_ROUND_UP(wfhw->duty_steps * MAX7360_PWM_PERIOD_NS, MAX7360_PWM_STEPS); } else { wf->duty_length_ns = 0; } return 0; } static int max7360_pwm_write_waveform(struct pwm_chip *chip, struct pwm_device *pwm, const void *_wfhw) { struct regmap *regmap = pwmchip_get_drvdata(chip); const struct max7360_pwm_waveform *wfhw = _wfhw; unsigned int val; int ret; if (wfhw->enabled) { ret = regmap_write(regmap, MAX7360_REG_PWM(pwm->hwpwm), wfhw->duty_steps); if (ret) return ret; } val = wfhw->enabled ? BIT(pwm->hwpwm) : 0; return regmap_write_bits(regmap, MAX7360_REG_GPIOCTRL, BIT(pwm->hwpwm), val); } static int max7360_pwm_read_waveform(struct pwm_chip *chip, struct pwm_device *pwm, void *_wfhw) { struct regmap *regmap = pwmchip_get_drvdata(chip); struct max7360_pwm_waveform *wfhw = _wfhw; unsigned int val; int ret; ret = regmap_read(regmap, MAX7360_REG_GPIOCTRL, &val); if (ret) return ret; if (val & BIT(pwm->hwpwm)) { wfhw->enabled = true; ret = regmap_read(regmap, MAX7360_REG_PWM(pwm->hwpwm), &val); if (ret) return ret; wfhw->duty_steps = val; } else { wfhw->enabled = false; wfhw->duty_steps = 0; } return 0; } static const struct pwm_ops max7360_pwm_ops = { .request = max7360_pwm_request, .round_waveform_tohw = max7360_pwm_round_waveform_tohw, .round_waveform_fromhw = max7360_pwm_round_waveform_fromhw, .read_waveform = max7360_pwm_read_waveform, .write_waveform = max7360_pwm_write_waveform, }; static int max7360_pwm_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct pwm_chip *chip; struct regmap *regmap; int ret; regmap = dev_get_regmap(dev->parent, NULL); if (!regmap) return dev_err_probe(dev, -ENODEV, "Could not get parent regmap\n"); /* * This MFD sub-device does not have any associated device tree node: * properties are stored in the device node of the parent (MFD) device * and this same node is used in phandles of client devices. * Reuse this device tree node here, as otherwise the PWM subsystem * would be confused by this topology. */ device_set_of_node_from_dev(dev, dev->parent); chip = devm_pwmchip_alloc(dev, MAX7360_NUM_PWMS, 0); if (IS_ERR(chip)) return PTR_ERR(chip); chip->ops = &max7360_pwm_ops; pwmchip_set_drvdata(chip, regmap); ret = devm_pwmchip_add(dev, chip); if (ret) return dev_err_probe(dev, ret, "Failed to add PWM chip\n"); return 0; } static struct platform_driver max7360_pwm_driver = { .driver = { .name = "max7360-pwm", .probe_type = PROBE_PREFER_ASYNCHRONOUS, }, .probe = max7360_pwm_probe, }; module_platform_driver(max7360_pwm_driver); MODULE_DESCRIPTION("MAX7360 PWM driver"); MODULE_AUTHOR("Kamel BOUHARA "); MODULE_AUTHOR("Mathieu Dubois-Briand "); MODULE_LICENSE("GPL");