Re: [PATCH v5 4/4] pwm: adp5585: Add Analog Devices ADP5585 support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello Laurent,

thanks for your reiteration of the series.

Just a few questions and minor suggestions left; see below.

On Fri, Jul 19, 2024 at 11:39:46PM +0300, Laurent Pinchart wrote:
> From: Clark Wang <xiaoning.wang@xxxxxxx>
> 
> The ADP5585 is a 10/11 input/output port expander with a built in keypad
> matrix decoder, programmable logic, reset generator, and PWM generator.
> This driver supports the PWM function using the platform device
> registered by the core MFD driver.
> 
> The driver is derived from an initial implementation from NXP, available
> in commit 113113742208 ("MLK-25922-1 pwm: adp5585: add adp5585 PWM
> support") in their BSP kernel tree. It has been extensively rewritten.
> 
> Signed-off-by: Clark Wang <xiaoning.wang@xxxxxxx>
> Signed-off-by: Laurent Pinchart <laurent.pinchart@xxxxxxxxxxxxxxxx>

Would your changes justify a Co-developed-by:?

> diff --git a/drivers/pwm/pwm-adp5585.c b/drivers/pwm/pwm-adp5585.c
> new file mode 100644
> index 000000000000..472a4c20b7a9
> --- /dev/null
> +++ b/drivers/pwm/pwm-adp5585.c
> @@ -0,0 +1,189 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Analog Devices ADP5585 PWM driver
> + *
> + * Copyright 2022 NXP
> + * Copyright 2024 Ideas on Board Oy
> + *
> + * Limitations:
> + * - The .apply() operation executes atomically, but may not wait for the
> + *   period to complete (this is not documented and would need to be tested).

So writing to ADP5585_PWM_OFFT and ADP5585_PWM_ONT is shadowed until
what happens?

> + * - Disabling the PWM drives the output pin to a low level immediately.
> + * - The hardware can only generate normal polarity output.
> + */
> +
> +#include <asm/byteorder.h>
> +
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/math64.h>
> +#include <linux/mfd/adp5585.h>
> +#include <linux/minmax.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/regmap.h>
> +#include <linux/time.h>
> +#include <linux/types.h>
> +
> +#define ADP5585_PWM_CHAN_NUM		1
> +
> +#define ADP5585_PWM_OSC_FREQ_HZ		1000000U
> +#define ADP5585_PWM_MIN_PERIOD_NS	(2ULL * NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ)
> +#define ADP5585_PWM_MAX_PERIOD_NS	(2ULL * 0xffff * NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ)
> +
> +static int pwm_adp5585_request(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	struct regmap *regmap = pwmchip_get_drvdata(chip);
> +	int ret;
> +
> +	ret = regmap_update_bits(regmap, ADP5585_PIN_CONFIG_C,
> +				 ADP5585_R3_EXTEND_CFG_MASK,
> +				 ADP5585_R3_EXTEND_CFG_PWM_OUT);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_update_bits(regmap, ADP5585_GENERAL_CFG,
> +				  ADP5585_OSC_EN, ADP5585_OSC_EN);

The purpose of this function is pinmuxing and oscillator enabling,
right? Would it make sense to enable the oscillator only in .apply() with
.enabled = true to save some power?

> +}
> +
> +static void pwm_adp5585_free(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	struct regmap *regmap = pwmchip_get_drvdata(chip);
> +
> +	regmap_update_bits(regmap, ADP5585_PIN_CONFIG_C,
> +			   ADP5585_R3_EXTEND_CFG_MASK,
> +			   ADP5585_R3_EXTEND_CFG_GPIO4);
> +	regmap_update_bits(regmap, ADP5585_GENERAL_CFG,
> +			   ADP5585_OSC_EN, 0);
> +}
> +
> +static int pwm_adp5585_apply(struct pwm_chip *chip,
> +			     struct pwm_device *pwm,
> +			     const struct pwm_state *state)
> +{
> +	struct regmap *regmap = pwmchip_get_drvdata(chip);
> +	u64 period, duty_cycle;
> +	u32 on, off;
> +	__le16 val;
> +	int ret;
> +
> +	if (!state->enabled)
> +		return regmap_update_bits(regmap, ADP5585_PWM_CFG,
> +					  ADP5585_PWM_EN, 0);
> +
> +	if (state->polarity != PWM_POLARITY_NORMAL)
> +		return -EINVAL;
> +
> +	if (state->period < ADP5585_PWM_MIN_PERIOD_NS)
> +		return -EINVAL;
> +
> +	period = min(state->period, ADP5585_PWM_MAX_PERIOD_NS);
> +	duty_cycle = min(state->duty_cycle, period);
> +
> +	/*
> +	 * Compute the on and off time. As the internal oscillator frequency is
> +	 * 1MHz, the calculation can be simplified without loss of precision.
> +	 */
> +	on = div_u64(duty_cycle, NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ);
> +	off = div_u64(period, NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ) - on;
> +
> +	val = cpu_to_le16(off);
> +	ret = regmap_bulk_write(regmap, ADP5585_PWM_OFFT_LOW, &val, 2);
> +	if (ret)
> +		return ret;
> +
> +	val = cpu_to_le16(on);
> +	ret = regmap_bulk_write(regmap, ADP5585_PWM_ONT_LOW, &val, 2);
> +	if (ret)
> +		return ret;
> +
> +	/* Enable PWM in continuous mode and no external AND'ing. */
> +	ret = regmap_update_bits(regmap, ADP5585_PWM_CFG,
> +				 ADP5585_PWM_IN_AND | ADP5585_PWM_MODE |
> +				 ADP5585_PWM_EN, ADP5585_PWM_EN);
> +	if (ret)
> +		return ret;
> +
> +	return 0;

This could be simplified to just:

	return regmap_update_bits(...);

(but some people feel strong here, so just a suggestion)

> +}
> +
> +static int pwm_adp5585_get_state(struct pwm_chip *chip,
> +				 struct pwm_device *pwm,
> +				 struct pwm_state *state)
> +{
> +	struct regmap *regmap = pwmchip_get_drvdata(chip);
> +	unsigned int on, off;
> +	unsigned int val;
> +	__le16 on_off;
> +	int ret;
> +
> +	ret = regmap_bulk_read(regmap, ADP5585_PWM_OFFT_LOW, &on_off, 2);
> +	if (ret)
> +		return ret;
> +	off = le16_to_cpu(on_off);
> +
> +	ret = regmap_bulk_read(regmap, ADP5585_PWM_ONT_LOW, &on_off, 2);
> +	if (ret)
> +		return ret;
> +	on = le16_to_cpu(on_off);
> +
> +	state->duty_cycle = on * (NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ);
> +	state->period = (on + off) * (NSEC_PER_SEC / ADP5585_PWM_OSC_FREQ_HZ);
> +
> +	state->polarity = PWM_POLARITY_NORMAL;
> +
> +	regmap_read(regmap, ADP5585_PWM_CFG, &val);
> +	state->enabled = !!(val & ADP5585_PWM_EN);
> +
> +	return 0;
> +}
> +
> +static const struct pwm_ops adp5585_pwm_ops = {
> +	.request = pwm_adp5585_request,
> +	.free = pwm_adp5585_free,
> +	.apply = pwm_adp5585_apply,
> +	.get_state = pwm_adp5585_get_state,
> +};
> +
> +static int adp5585_pwm_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct adp5585_dev *adp5585 = dev_get_drvdata(dev->parent);
> +	struct pwm_chip *chip;
> +	int ret;
> +
> +	chip = devm_pwmchip_alloc(dev, ADP5585_PWM_CHAN_NUM, 0);

ADP5585_PWM_CHAN_NUM is only used once. I would prefer passing a plain 1
here, as this makes the output of $(grep devm_pwmchip_alloc) a bit more
useful.

> +	if (IS_ERR(chip))
> +		return PTR_ERR(chip);
> +
> +	device_set_of_node_from_dev(dev, dev->parent);
> +
> +	pwmchip_set_drvdata(chip, adp5585->regmap);
> +	chip->ops = &adp5585_pwm_ops;
> +
> +	ret = devm_pwmchip_add(dev, chip);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to add PWM chip\n");
> +
> +	return 0;
> +}
> +
> +static const struct platform_device_id adp5585_pwm_id_table[] = {
> +	{ "adp5585-pwm" },
> +	{ /* Sentinel */ },

The trailing comma should be dropped here.

> +};
> +MODULE_DEVICE_TABLE(platform, adp5585_pwm_id_table);
> +
> +static struct platform_driver adp5585_pwm_driver = {
> +	.driver	= {
> +		.name = "adp5585-pwm",
> +	},
> +	.probe = adp5585_pwm_probe,
> +	.id_table = adp5585_pwm_id_table,
> +};
> +module_platform_driver(adp5585_pwm_driver);
> +
> +MODULE_AUTHOR("Xiaoning Wang <xiaoning.wang@xxxxxxx>");
> +MODULE_DESCRIPTION("ADP5585 PWM Driver");
> +MODULE_LICENSE("GPL");

Thanks
Uwe

Attachment: signature.asc
Description: PGP signature


[Index of Archives]     [Device Tree Compilter]     [Device Tree Spec]     [Linux Driver Backports]     [Video for Linux]     [Linux USB Devel]     [Linux PCI Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Yosemite Backpacking]


  Powered by Linux