From: Christian Marangi <ansuelsmth@gmail.com>
To: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Cc: "Uwe Kleine-König" <ukleinek@kernel.org>,
linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
"Benjamin Larsson" <benjamin.larsson@genexis.eu>,
"AngeloGioacchino Del Regno"
<angelogioacchino.delregno@collabora.com>,
"Lorenzo Bianconi" <lorenzo@kernel.org>
Subject: Re: [PATCH v17] pwm: airoha: Add support for EN7581 SoC
Date: Fri, 27 Jun 2025 00:52:06 +0200 [thread overview]
Message-ID: <685dcf1b.050a0220.2cbe17.6342@mx.google.com> (raw)
In-Reply-To: <aF1RIVzVNcdsU1DB@smile.fi.intel.com>
On Thu, Jun 26, 2025 at 04:54:41PM +0300, Andy Shevchenko wrote:
> On Wed, Jun 25, 2025 at 09:49:01PM +0200, Christian Marangi wrote:
> > From: Benjamin Larsson <benjamin.larsson@genexis.eu>
> >
> > Introduce driver for PWM module available on EN7581 SoC.
>
> Almost from my perspective.
> If you address the below as suggested, feel free to add
> Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
>
> ...
>
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright 2022 Markus Gothe <markus.gothe@genexis.eu>
>
> On my calendar 2025 is. Also MODULE_AUTHOR() list disagrees with this
> (you should add yourself somewhere here).
>
> > + * Limitations:
> > + * - Only 8 concurrent waveform generators are available for 8 combinations of
> > + * duty_cycle and period. Waveform generators are shared between 16 GPIO
> > + * pins and 17 SIPO GPIO pins.
> > + * - Supports only normal polarity.
> > + * - On configuration the currently running period is completed.
> > + * - Minimum supported period is 4ms
> > + * - Maximum supported period is 1s
> > + */
>
> ...
>
> > +#define AIROHA_PWM_SGPIO_CLK_DIVR_32 FIELD_PREP_CONST(AIROHA_PWM_SGPIO_CLK_DIVR, 0x3)
> > +#define AIROHA_PWM_SGPIO_CLK_DIVR_16 FIELD_PREP_CONST(AIROHA_PWM_SGPIO_CLK_DIVR, 0x2)
> > +#define AIROHA_PWM_SGPIO_CLK_DIVR_8 FIELD_PREP_CONST(AIROHA_PWM_SGPIO_CLK_DIVR, 0x1)
> > +#define AIROHA_PWM_SGPIO_CLK_DIVR_4 FIELD_PREP_CONST(AIROHA_PWM_SGPIO_CLK_DIVR, 0x0)
>
> Remove 0x:s, they are just noise and moreover the list here is plain numbers,
> not bit combinations or so. In such cases we use decimal numbers.
>
> ...
>
> > +/* Cycle cfg handles 4 generators in one register */
>
> I am a bit lost in the meaning of "cycle cfg". Can you elaborate in
> the comment that it's readable in plain English?
>
> > +#define AIROHA_PWM_BUCKET_PER_CYCLE_CFG 4
>
> ...
>
> > +static u32 airoha_pwm_get_duty_ticks_from_ns(u32 period_ns, u32 duty_ns)
> > +{
> > + return mul_u64_u32_div(duty_ns, AIROHA_PWM_DUTY_FULL,
> > + period_ns);
>
> It's less than 80 if put on one line!
>
> > +}
>
> ...
>
> > +static int airoha_pwm_get_bucket(struct airoha_pwm *pc, int bucket,
> > + u64 *period_ns, u64 *duty_ns)
> > +{
> > + u32 period_tick, duty_tick;
> > + unsigned int offset;
> > + u32 shift, val;
> > + int ret;
> > +
> > + offset = bucket / AIROHA_PWM_BUCKET_PER_CYCLE_CFG;
> > + shift = bucket % AIROHA_PWM_BUCKET_PER_CYCLE_CFG;
> > + shift = AIROHA_PWM_REG_CYCLE_CFG_SHIFT(shift);
> > +
> > + ret = regmap_read(pc->regmap, AIROHA_PWM_REG_CYCLE_CFG_VALUE(offset),
> > + &val);
>
> You may shorten the line and the code (as below the same can be applied)
> by introducing
>
> struct regmap *map = pc->regmap;
>
> > + if (ret)
> > + return ret;
> > +
> > + period_tick = FIELD_GET(AIROHA_PWM_WAVE_GEN_CYCLE, val >> shift);
> > + *period_ns = period_tick * AIROHA_PWM_PERIOD_TICK_NS;
> > +
> > + offset = bucket / AIROHA_PWM_BUCKET_PER_FLASH_PROD;
> > + shift = bucket % AIROHA_PWM_BUCKET_PER_FLASH_PROD;
> > + shift = AIROHA_PWM_REG_GPIO_FLASH_PRD_SHIFT(shift);
> > +
> > + ret = regmap_read(pc->regmap, AIROHA_PWM_REG_GPIO_FLASH_PRD_SET(offset),
> > + &val);
> > + if (ret)
> > + return ret;
> > +
> > + duty_tick = FIELD_GET(AIROHA_PWM_GPIO_FLASH_PRD_HIGH, val >> shift);
> > + /*
> > + * Overflow can't occur in multiplication as duty_tick is just 8 bit
> > + * and period_ns is clamped to AIROHA_PWM_PERIOD_MAX_NS and fit in a
> > + * u64.
> > + */
> > + *duty_ns = DIV_U64_ROUND_UP(duty_tick * *period_ns, AIROHA_PWM_DUTY_FULL);
> > +
> > + return 0;
> > +}
>
> ...
>
> > +static int airoha_pwm_get_generator(struct airoha_pwm *pc, u32 duty_ticks,
> > + u32 period_ticks)
> > +{
> > + int best = -ENOENT, unused = -ENOENT;
> > + u32 best_period_ticks = 0;
> > + u32 best_duty_ticks = 0;
> > + unsigned int i;
> > +
> > + for (i = 0; i < ARRAY_SIZE(pc->buckets); i++) {
>
> You want array_size.h.
>
> > + struct airoha_pwm_bucket *bucket = &pc->buckets[i];
> > + u32 bucket_period_ticks = bucket->period_ticks;
> > + u32 bucket_duty_ticks = bucket->duty_ticks;
>
> > + /* If found, save an unused bucket to return it later */
> > + if (refcount_read(&bucket->used) == 0) {
> > + unused = i;
> > + continue;
> > + }
> > +
> > + /* We found a matching bucket, exit early */
> > + if (duty_ticks == bucket_duty_ticks &&
> > + period_ticks == bucket_period_ticks)
> > + return i;
> > +
> > + /*
> > + * Unlike duty cycle zero, which can be handled by
> > + * disabling PWM, a generator is needed for full duty
> > + * cycle but it can be reused regardless of period
> > + */
> > + if (duty_ticks == AIROHA_PWM_DUTY_FULL &&
> > + bucket_duty_ticks == AIROHA_PWM_DUTY_FULL)
> > + return i;
> > +
> > + /*
> > + * With an unused bucket available, skip searching for
> > + * a bucket to recycle (closer to the requested period/duty)
> > + */
> > + if (unused != -ENOENT)
>
> Can unused be negative and have different error code or so?
> If not, simplify here (and in return below) to
>
> if (unused >= 0)
>
> > + continue;
> > +
> > + /* Ignore bucket with invalid configs */
> > + if (bucket_period_ticks > period_ticks ||
> > + bucket_duty_ticks > duty_ticks)
> > + continue;
> > +
> > + /*
> > + * Search for a bucket closer to the requested period/duty
> > + * that has the maximal possible period that isn't bigger
> > + * than the requested period. For that period pick the maximal
> > + * duty_cycle that isn't bigger than the requested duty_cycle.
>
> duty cycle
>
> (with underscore I haven't found a variable in this function the comment refers to)
>
> > + */
> > + if (bucket_period_ticks > best_period_ticks ||
> > + (bucket_period_ticks == best_period_ticks &&
> > + bucket_duty_ticks > best_duty_ticks)) {
> > + best_period_ticks = bucket_period_ticks;
> > + best_duty_ticks = bucket_duty_ticks;
> > + best = i;
> > + }
> > + }
> > +
> > + /* With no unused bucket, return the best one found (if ever) */
> > + return unused == -ENOENT ? best : unused;
> > +}
>
> ...
>
> > +static int airoha_pwm_consume_generator(struct airoha_pwm *pc,
> > + u32 duty_ticks, u32 period_ticks,
> > + unsigned int hwpwm)
> > +{
> > + int bucket, ret;
> > +
> > + /*
> > + * Search for a bucket that already satisfies duty and period
> > + * or an unused one.
> > + * If not found, -ENOENT is returned.
> > + */
> > + bucket = airoha_pwm_get_generator(pc, duty_ticks, period_ticks);
> > + if (bucket < 0)
> > + return bucket;
> > +
> > + /* Release previous used bucket (if any) */
> > + airoha_pwm_release_bucket_config(pc, hwpwm);
>
> > + if (refcount_read(&pc->buckets[bucket].used) == 0) {
> > + pc->buckets[bucket].period_ticks = period_ticks;
> > + pc->buckets[bucket].duty_ticks = duty_ticks;
> > + ret = airoha_pwm_apply_bucket_config(pc, bucket,
> > + duty_ticks,
> > + period_ticks);
> > + if (ret)
> > + return ret;
> > +
> > + refcount_set(&pc->buckets[bucket].used, 1);
>
> What happens if refcount is updated in between? This is wrong use of atomics.
>
> > + } else {
> > + refcount_inc(&pc->buckets[bucket].used);
>
> Ditto.
>
> You probably wanted _inc_and_test() variant.
>
The main problem is that adding macro for refcount and atomic is that
normaly you expect to have parallel ASM code for it to have real aotmic
OP. Anyway I think I solved it with a simple mutex.
The usage of refcount here it's not really for atomic but for object
tracking but refcount doesn't love to initialize from 0 and incremented
so I have to use this _set and _inc thing.
> > + }
> > +
> > + return bucket;
> > +}
>
> ...
>
> > +static int airoha_pwm_config(struct airoha_pwm *pc, struct pwm_device *pwm,
> > + u32 duty_ticks, u32 period_ticks)
> > +{
> > + unsigned int hwpwm = pwm->hwpwm;
> > + int bucket, ret;
> > +
> > + bucket = airoha_pwm_consume_generator(pc, duty_ticks, period_ticks,
> > + hwpwm);
> > + if (bucket < 0)
> > + return bucket;
> > +
> > + ret = airoha_pwm_config_flash_map(pc, hwpwm, bucket);
> > + if (ret) {
> > + refcount_dec(&pc->buckets[bucket].used);
> > + return ret;
> > + }
>
> > + set_bit(hwpwm, pc->initialized);
>
> Does it need to be atomic? (Just asking) If not, use non-atomic variant, i.e.
> __set_bit().
>
Yes it needs to be atomic since the initialized bitmask is global.
> > + pc->channel_bucket[hwpwm] = bucket;
> > +
> > + /*
> > + * SIPO are special GPIO attached to a shift register chip. The handling
> > + * of this chip is internal to the SoC that takes care of applying the
> > + * values based on the flash map. To apply a new flash map, it's needed
> > + * to trigger a refresh on the shift register chip.
> > + * If a SIPO is getting configuring , always reinit the shift register
> > + * chip to make sure the correct flash map is applied.
> > + * Skip reconfiguring the shift register if the related hwpwm
> > + * is disabled (as it doesn't need to be mapped).
> > + */
> > + if (hwpwm >= AIROHA_PWM_NUM_GPIO) {
> > + ret = airoha_pwm_sipo_init(pc);
> > + if (ret) {
> > + airoha_pwm_release_bucket_config(pc, hwpwm);
> > + return ret;
> > + }
> > + }
> > +
> > + return 0;
> > +}
>
> ...
>
> > +static void airoha_pwm_disable(struct airoha_pwm *pc, struct pwm_device *pwm)
> > +{
> > + /* Disable PWM and release the bucket */
> > + airoha_pwm_config_flash_map(pc, pwm->hwpwm, -1);
> > + airoha_pwm_release_bucket_config(pc, pwm->hwpwm);
>
> > + clear_bit(pwm->hwpwm, pc->initialized);
>
> As per above.
>
> > + /* If no SIPO is used, disable the shift register chip */
> > + if (find_next_bit(pc->initialized, AIROHA_PWM_MAX_CHANNELS,
> > + AIROHA_PWM_NUM_GPIO) >= AIROHA_PWM_NUM_GPIO)
> > + regmap_clear_bits(pc->regmap, AIROHA_PWM_REG_SIPO_FLASH_MODE_CFG,
> > + AIROHA_PWM_SERIAL_GPIO_FLASH_MODE);
> > +}
>
> ...
>
> > +static int airoha_pwm_probe(struct platform_device *pdev)
> > +{
> > + struct device *dev = &pdev->dev;
> > + struct airoha_pwm *pc;
> > + struct pwm_chip *chip;
> > + unsigned int i;
> > + int ret;
> > +
> > + chip = devm_pwmchip_alloc(dev, AIROHA_PWM_MAX_CHANNELS, sizeof(*pc));
> > + if (IS_ERR(chip))
> > + return PTR_ERR(chip);
> > +
> > + chip->ops = &airoha_pwm_ops;
> > + pc = pwmchip_get_drvdata(chip);
> > +
> > + pc->regmap = device_node_to_regmap(dev->parent->of_node);
>
> Please, use dev_of_node(dev->parent).
>
> > + if (IS_ERR(pc->regmap))
> > + return dev_err_probe(dev, PTR_ERR(pc->regmap), "Failed to get PWM regmap\n");
> > +
> > + for (i = 0; i < ARRAY_SIZE(pc->buckets); i++)
> > + refcount_set(&pc->buckets[i].used, 0);
> > +
> > + ret = devm_pwmchip_add(&pdev->dev, chip);
>
> Seems you have dev, why not use it?
>
> > + if (ret)
> > + return dev_err_probe(dev, ret, "Failed to add PWM chip\n");
> > +
> > + return 0;
> > +}
>
> --
> With Best Regards,
> Andy Shevchenko
>
>
--
Ansuel
next prev parent reply other threads:[~2025-06-26 22:52 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-06-25 19:49 [PATCH v17] pwm: airoha: Add support for EN7581 SoC Christian Marangi
2025-06-26 13:54 ` Andy Shevchenko
2025-06-26 22:52 ` Christian Marangi [this message]
2025-06-27 4:15 ` Andy Shevchenko
2025-06-26 19:54 ` kernel test robot
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=685dcf1b.050a0220.2cbe17.6342@mx.google.com \
--to=ansuelsmth@gmail.com \
--cc=andriy.shevchenko@linux.intel.com \
--cc=angelogioacchino.delregno@collabora.com \
--cc=benjamin.larsson@genexis.eu \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pwm@vger.kernel.org \
--cc=lorenzo@kernel.org \
--cc=ukleinek@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.