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

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

 



Hi Uwe,

On Sun, Jul 21, 2024 at 09:09:07AM +0200, Uwe Kleine-König wrote:
> 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:?

Sounds like a good idea.

> > 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?

The datasheet only tells that the PWM times are latched when the
ADP5585_PWM_ONT_HIGH register is written. Whether that will affect the
timings immediately, or wait until the end of the current period, I
don't know.

> > + * - 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?

I'll do that. Note that the OSC_EN bit also affects the GPI scan and the
key scan functions, which the driver doesn't support yet. When support
for those functions will be added, we will need to move handling of the
OSC_EN bit to the MFD driver, and reference-count the oscillator
enable/disable calls.

> > +}
> > +
> > +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)

I don't have a strong preference in this case so I'll apply your
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.

I think the macro makes the code more readable, it clearly shows that
the second argument is the number of channels, while

	chip = devm_pwmchip_alloc(dev, 1, 0);

is harder to read. If you insist, I can change it. I don't have a
sentimental attachment to this driver, I just want to get it upstream to
avoid carrying it locally.

> > +	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.

OK.

> > +};
> > +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");

-- 
Regards,

Laurent Pinchart




[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