All of lore.kernel.org
 help / color / mirror / Atom feed
From: Bjorn Andersson <bjorn.andersson@linaro.org>
To: Andy Shevchenko <andy.shevchenko@gmail.com>
Cc: Pavel Machek <pavel@ucw.cz>, Dan Murphy <dmurphy@ti.com>,
	Rob Herring <robh+dt@kernel.org>, Andy Gross <agross@kernel.org>,
	Thierry Reding <thierry.reding@gmail.com>,
	Uwe Kleine-K?nig <u.kleine-koenig@pengutronix.de>,
	Lee Jones <lee.jones@linaro.org>,
	Martin Botka <martin.botka1@gmail.com>,
	Linux LED Subsystem <linux-leds@vger.kernel.org>,
	devicetree <devicetree@vger.kernel.org>,
	Linux Kernel Mailing List <linux-kernel@vger.kernel.org>,
	linux-arm-msm@vger.kernel.org, linux-pwm@vger.kernel.org
Subject: Re: [PATCH v5 2/4] leds: Add driver for Qualcomm LPG
Date: Mon, 19 Oct 2020 23:24:03 -0500	[thread overview]
Message-ID: <20201020042403.GE6705@builder.lan> (raw)
In-Reply-To: <CAHp75VeVbK1Wx2BEPghtEbEghqDAF2jFFN9=ARLEw-rvTUZ3yw@mail.gmail.com>

On Sun 18 Oct 15:12 CDT 2020, Andy Shevchenko wrote:

> On Sat, Oct 17, 2020 at 8:41 AM Bjorn Andersson
> <bjorn.andersson@linaro.org> wrote:
> >
> > The Light Pulse Generator (LPG) is a PWM-block found in a wide range of
> > PMICs from Qualcomm. It can operate on fixed parameters or based on a
> > lookup-table, altering the duty cycle over time - which provides the
> > means for e.g. hardware assisted transitions of LED brightness.
> 
> > +config LEDS_QCOM_LPG
> > +       tristate "LED support for Qualcomm LPG"
> > +       depends on LEDS_CLASS_MULTICOLOR
> > +       depends on OF
> > +       depends on SPMI
> 
> 
> > +#include <linux/of.h>
> > +#include <linux/of_device.h>
> 
> ...
> 
> > +struct lpg {
> > +       struct device *dev;
> > +       struct regmap *map;
> 
> Can't you derive the former from the latter?
> 

No, because map->dev is actually the dev->parent.

> > +
> > +       struct pwm_chip pwm;
> > +
> > +       const struct lpg_data *data;
> > +
> > +       u32 lut_base;
> > +       u32 lut_size;
> > +       unsigned long *lut_bitmap;
> > +
> > +       u32 triled_base;
> > +       u32 triled_src;
> > +
> > +       struct lpg_channel *channels;
> > +       unsigned int num_channels;
> > +};
> 
> ...
> 
> > +static int lpg_lut_store(struct lpg *lpg, struct led_pattern *pattern,
> > +                        size_t len, unsigned int *lo_idx, unsigned int *hi_idx)
> > +{
> > +       unsigned int idx;
> > +       u8 val[2];
> 
> __be16 val;
> 
> > +       int i;
> > +
> > +       /* Hardware does not behave when LO_IDX == HI_IDX */
> > +       if (len == 1)
> > +               return -EINVAL;
> > +
> > +       idx = bitmap_find_next_zero_area(lpg->lut_bitmap, lpg->lut_size,
> > +                                        0, len, 0);
> > +       if (idx >= lpg->lut_size)
> > +               return -ENOMEM;
> > +
> > +       for (i = 0; i < len; i++) {
> > +               val[0] = pattern[i].brightness & 0xff;
> > +               val[1] = pattern[i].brightness >> 8;
> 
> cpu_to_be16();
> 

I like it, but isn't that a le16?

> > +
> > +               regmap_bulk_write(lpg->map,
> > +                                 lpg->lut_base + LPG_LUT_REG(idx + i), val, 2);
> > +       }
> > +
> > +       bitmap_set(lpg->lut_bitmap, idx, len);
> > +
> > +       *lo_idx = idx;
> > +       *hi_idx = idx + len - 1;
> > +
> > +       return 0;
> > +}
> 
> ...
> 
> > +static void lpg_calc_freq(struct lpg_channel *chan, unsigned int period_us)
> > +{
> > +       int             n, m, clk, div;
> > +       int             best_m, best_div, best_clk;
> > +       unsigned int    last_err, cur_err, min_err;
> > +       unsigned int    tmp_p, period_n;
> > +
> > +       if (period_us == chan->period_us)
> > +               return;
> > +
> > +       /* PWM Period / N */
> > +       if (period_us < ((unsigned int)(-1) / NSEC_PER_USEC)) {
> 
> Please, replace all these -1 with castings to unsigned types with
> corresponding limits, like
> UINT_MAX here.
> 

Sure thing.

> > +               period_n = (period_us * NSEC_PER_USEC) >> 6;
> > +               n = 6;
> > +       } else {
> > +               period_n = (period_us >> 9) * NSEC_PER_USEC;
> > +               n = 9;
> > +       }
> 
> Why inconsistency in branches? Can you rather derive n and calculate
> only once like
> 
>            period_n = (period_us >> n) * NSEC_PER_USEC;
> 
> ?

I inherited this piece from the downstream driver and I assume that the
purpose was to avoid loss of precision. I will review this and if
nothing else it seems like I would be able to cast period_us to more
bits, do the multiply and then shift - in both cases.

> 
> > +       min_err = (unsigned int)(-1);
> > +       last_err = (unsigned int)(-1);
> > +       best_m = 0;
> > +       best_clk = 0;
> > +       best_div = 0;
> > +       for (clk = 0; clk < NUM_PWM_CLK; clk++) {
> > +               for (div = 0; div < NUM_PWM_PREDIV; div++) {
> > +                       /* period_n = (PWM Period / N) */
> > +                       /* tmp_p = (Pre-divide * Clock Period) * 2^m */
> > +                       tmp_p = lpg_clk_table[div][clk];
> > +                       for (m = 0; m <= NUM_EXP; m++) {
> > +                               if (period_n > tmp_p)
> > +                                       cur_err = period_n - tmp_p;
> > +                               else
> > +                                       cur_err = tmp_p - period_n;
> > +
> > +                               if (cur_err < min_err) {
> > +                                       min_err = cur_err;
> > +                                       best_m = m;
> > +                                       best_clk = clk;
> > +                                       best_div = div;
> > +                               }
> > +
> > +                               if (m && cur_err > last_err)
> > +                                       /* Break for bigger cur_err */
> > +                                       break;
> > +
> > +                               last_err = cur_err;
> > +                               tmp_p <<= 1;
> > +                       }
> > +               }
> > +       }
> > +
> > +       /* Use higher resolution */
> > +       if (best_m >= 3 && n == 6) {
> > +               n += 3;
> > +               best_m -= 3;
> > +       }
> > +
> > +       chan->clk = best_clk;
> > +       chan->pre_div = best_div;
> > +       chan->pre_div_exp = best_m;
> > +       chan->pwm_size = n;
> > +
> > +       chan->period_us = period_us;
> > +}
> > +
> > +static void lpg_calc_duty(struct lpg_channel *chan, unsigned int duty_us)
> > +{
> > +       unsigned long max = (1 << chan->pwm_size) - 1;
> 
> BIT() ?
> 
> > +       unsigned long val;
> > +
> > +       /* Figure out pwm_value with overflow handling */
> 
> > +       if (duty_us < 1 << (sizeof(val) * 8 - chan->pwm_size))
> 
> BITS_PER_TYPE, but actually BITS_PER_LONG here.
> 
> BIT(BITS_PER_LONG - ...)
> 

Again, this seems to just be a question of avoiding overflow of the 32
bit duty_us. I'll double check the math here as well.

> > +               val = (duty_us << chan->pwm_size) / chan->period_us;
> > +       else
> > +               val = duty_us / (chan->period_us >> chan->pwm_size);
> > +
> > +       if (val > max)
> > +               val = max;
> > +
> > +       chan->pwm_value = val;
> > +}
> 
> ...
> 
> > +static void lpg_enable_glitch(struct lpg_channel *chan)
> > +{
> > +       struct lpg *lpg = chan->lpg;
> > +
> > +       regmap_update_bits(lpg->map, chan->base + PWM_TYPE_CONFIG_REG,
> > +                          LPG_ENABLE_GLITCH_REMOVAL, 0);
> > +}
> 
> Here and everywhere else when function declared as void there is no
> possibility to know if we do something useful or already screwed up
> the things.
> 

Ultimately these are all coming from led_classdev->brightness_set, which
is a void function. So there isn't much benefit of propagating this
error upwards, today.

> > +static void lpg_apply_pwm_value(struct lpg_channel *chan)
> > +{
> > +       u8 val[] = { chan->pwm_value & 0xff, chan->pwm_value >> 8 };
> 
> 
> __le16 and cpu_to_le16()
> 

Sounds good.

> > +       struct lpg *lpg = chan->lpg;
> > +
> > +       if (!chan->enabled)
> > +               return;
> > +
> > +       regmap_bulk_write(lpg->map, chan->base + PWM_VALUE_REG, val, 2);
> > +}
> 
> > +#define LPG_PATTERN_CONFIG_LO_TO_HI    BIT(4)
> > +#define LPG_PATTERN_CONFIG_REPEAT      BIT(3)
> > +#define LPG_PATTERN_CONFIG_TOGGLE      BIT(2)
> > +#define LPG_PATTERN_CONFIG_PAUSE_HI    BIT(1)
> > +#define LPG_PATTERN_CONFIG_PAUSE_LO    BIT(0)
> 
> Did I miss bits.h inclusion at the beginning of the file?
> 

Will add this.

> ...
> 
> > +static int lpg_blink_set(struct lpg_led *led,
> > +                        unsigned long delay_on, unsigned long delay_off)
> > +{
> > +       struct lpg_channel *chan;
> > +       unsigned int period_us;
> > +       unsigned int duty_us;
> > +       int i;
> > +
> > +       if (!delay_on && !delay_off) {
> > +               delay_on = 500;
> > +               delay_off = 500;
> > +       }
> 
> Homegrown duty cycle?
> I mean, why simply not to pass the duty cycle in percentage in the first place?
> 

Can you explain what you're saying here.

> > +       duty_us = delay_on * USEC_PER_MSEC;
> > +       period_us = (delay_on + delay_off) * USEC_PER_MSEC;
> > +
> > +       for (i = 0; i < led->num_channels; i++) {
> > +               chan = led->channels[i];
> > +
> > +               lpg_calc_freq(chan, period_us);
> > +               lpg_calc_duty(chan, duty_us);
> > +
> > +               chan->enabled = true;
> > +               chan->ramp_enabled = false;
> > +
> > +               lpg_apply(chan);
> > +       }
> > +
> > +       return 0;
> > +}
> 
> > +#define interpolate(x1, y1, x2, y2, x) \
> > +       ((y1) + ((y2) - (y1)) * ((x) - (x1)) / ((x2) - (x1)))
> 
> Can you rather create a generic one under lib/ or start include/linux/math.h ?
> 

Forgot about this, but I've seen one on LKML, will find it and work on
getting that accepted.

> https://elixir.bootlin.com/linux/latest/A/ident/interpolate
> 
> ...
> 
> > +out:
> 
> Useless label.
> 
> > +       return ret;
> 
> ...
> 
> > +       ret = of_property_read_u32(np, "color", &color);
> > +       if (ret < 0 && ret != -EINVAL)
> 
> This check is fishy. Either you have optional property or not, in the
> latter case return any error code.
> 

There's three possible outcomes here:
1) We found _one_ integer in the property, color is assigned and 0 is
returned.
2) We found no property named "color", -EINVAL is returned without color
being modified.
3) We found a property but it wasn't a single u32 value so a negative
error (not EINVAL) is returned.

> > +               return ret;
> > +
> > +       chan->color = color;
> 
> So, it may be -EINVAL?!
> 

So color will either be the value or the property color, or if omitted
LED_COLOR_ID_GREEN.

> > +       ret = of_property_read_u32_array(np, "qcom,dtest", dtest, 2);
> > +       if (ret < 0 && ret != -EINVAL) {
> > +               dev_err(lpg->dev, "malformed qcom,dtest of %pOFn\n", np);
> > +               return ret;
> > +       } else if (!ret) {
> > +               chan->dtest_line = dtest[0];
> > +               chan->dtest_value = dtest[1];
> > +       }
> 
> Ditto.
> 

We're in !ret and as such dtest is initialized.

> ...
> 
> > +       ret = of_property_read_u32(np, "color", &color);
> > +       if (ret < 0 && ret != -EINVAL)
> > +               return ret;
> 
> Ditto.
> 

As above, if no property color is specified, color remains 0 here which
is not LED_COLOR_ID_MULTI and this is a single channel LED without its
color specified.

> ...
> 
> > +       size = sizeof(*led) + num_channels * sizeof(struct lpg_channel *);
> 
> Use respective helpers from overflow.h.
> 

Will do, thanks.

> > +       led = devm_kzalloc(lpg->dev, size, GFP_KERNEL);
> > +       if (!led)
> > +               return -ENOMEM;
> 
> ...
> 
> > +static const struct of_device_id lpg_of_table[] = {
> > +       { .compatible = "qcom,pm8916-pwm", .data = &pm8916_pwm_data },
> > +       { .compatible = "qcom,pm8941-lpg", .data = &pm8941_lpg_data },
> > +       { .compatible = "qcom,pm8994-lpg", .data = &pm8994_lpg_data },
> > +       { .compatible = "qcom,pmi8994-lpg", .data = &pmi8994_lpg_data },
> > +       { .compatible = "qcom,pmi8998-lpg", .data = &pmi8998_lpg_data },
> 
> > +       {},
> 
> No comma needed for terminator lines.
> 

Will drop.

> > +};

Thank you Andy for the review!

Regards,
Bjorn

  reply	other threads:[~2020-10-20  4:29 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-10-17  5:20 [PATCH v5 0/4] Qualcomm Light Pulse Generator Bjorn Andersson
2020-10-17  5:20 ` [PATCH v5 1/4] dt-bindings: leds: Add Qualcomm Light Pulse Generator binding Bjorn Andersson
2020-10-17  5:20 ` [PATCH v5 2/4] leds: Add driver for Qualcomm LPG Bjorn Andersson
2020-10-17 15:37   ` Martin Botka
2020-10-26  8:27     ` Pavel Machek
2020-10-26  9:02       ` Martin Botka
2020-10-26 15:11         ` Pavel Machek
2020-10-26 15:19           ` Martin Botka
2020-10-18 20:12   ` Andy Shevchenko
2020-10-20  4:24     ` Bjorn Andersson [this message]
2020-10-29 12:14       ` Andy Shevchenko
2020-10-17  5:20 ` [PATCH v5 3/4] arm64: dts: qcom: pm(i)8994: Add mpp and lpg blocks Bjorn Andersson
2020-10-17  5:20 ` [PATCH v5 4/4] arm64: dts: qcom: Add user LEDs on db820c Bjorn Andersson

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=20201020042403.GE6705@builder.lan \
    --to=bjorn.andersson@linaro.org \
    --cc=agross@kernel.org \
    --cc=andy.shevchenko@gmail.com \
    --cc=devicetree@vger.kernel.org \
    --cc=dmurphy@ti.com \
    --cc=lee.jones@linaro.org \
    --cc=linux-arm-msm@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-leds@vger.kernel.org \
    --cc=linux-pwm@vger.kernel.org \
    --cc=martin.botka1@gmail.com \
    --cc=pavel@ucw.cz \
    --cc=robh+dt@kernel.org \
    --cc=thierry.reding@gmail.com \
    --cc=u.kleine-koenig@pengutronix.de \
    /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.