Re: [PATCH v7 3/4] counter: ti-ecap-capture: capture driver support for ECAP

From: William Breathitt Gray
Date: Wed Sep 21 2022 - 22:08:06 EST


On Wed, Sep 21, 2022 at 12:06:26PM +0200, Julien Panis wrote:
> ECAP hardware on TI AM62x SoC supports capture feature. It can be used
> to timestamp events (falling/rising edges) detected on input signal.
>
> This commit adds capture driver support for ECAP hardware on AM62x SoC.
>
> In the ECAP hardware, capture pin can also be configured to be in
> PWM mode. Current implementation only supports capture operating mode.
> Hardware also supports timebase sync between multiple instances, but
> this driver supports simple independent capture functionality.
>
> Signed-off-by: Julien Panis <jpanis@xxxxxxxxxxxx>

Hi Julien,

This driver is almost there; some comments follow below.

> +static u8 ecap_cnt_capture_get_evmode(struct counter_device *counter)
> +{
> + struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
> + u8 ev_mode = 0;
> + unsigned int regval;
> + int i;
> +
> + pm_runtime_get_sync(counter->parent);
> + regmap_read(ecap_dev->regmap, ECAP_ECCTL_REG, &regval);
> + pm_runtime_put_sync(counter->parent);
> +
> + for (i = 0 ; i < ECAP_NB_CEVT ; i++) {
> + if (regval & ECAP_CAPPOL_BIT(i))
> + ev_mode |= ECAP_EV_MODE_BIT(i);
> + }

Looks like this for loop is just remapping the set bits in regval to
ev_mode. You can use bitmap_remap() here instead to simplify this
section of code.

> +static void ecap_cnt_capture_set_evmode(struct counter_device *counter, u8 ev_mode)
> +{
> + struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
> + unsigned int regval = 0;
> + int i;
> +
> + for (i = 0 ; i < ECAP_NB_CEVT ; i++) {
> + if (ev_mode & ECAP_EV_MODE_BIT(i))
> + regval |= ECAP_CAPPOL_BIT(i);
> + }

Use bitmap_remap() here as well (just in the reverse direction).

> +static int ecap_cnt_count_write(struct counter_device *counter,
> + struct counter_count *count, u64 val)
> +{
> + struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
> +
> + if (ecap_dev->enabled)
> + return -EBUSY;
> + if (val > 0)
> + return -EINVAL;

The ECAP_TSCNT_REG can be set to an arbitrary count value so there's no
need to restrict val here to 0. Instead, check that val is within the
ceiling value (<= U32_MAX) and return -ERANGE if it is not.

> +static int ecap_cnt_watch_validate(struct counter_device *counter,
> + const struct counter_watch *watch)
> +{
> + if ((watch->channel <= ECAP_CEVT_LAST && watch->event == COUNTER_EVENT_CAPTURE) ||
> + (watch->channel == ECAP_CNTOVF && watch->event == COUNTER_EVENT_OVERFLOW))
> + return 0;
> +
> + return -EINVAL;

COUNTER_EVENT_OVERFLOW shouldn't be on a separate channel; I'll explain
why later below in the interrupt handler review.

For this callback, you can separate the channel and event type checks to
their own blocks:

if (watch->channel > ECAP_CEVT_LAST)
return -EINVAL;

switch (watch->event) {
case COUNTER_EVENT_CAPTURE:
case COUNTER_EVENT_OVERFLOW:
return 0;
default:
return -EINVAL;
}

> +static int ecap_cnt_pol_read(struct counter_device *counter,
> + struct counter_signal *signal,
> + size_t idx, enum counter_signal_polarity *pol)
> +{
> + struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
> +
> + pm_runtime_get_sync(counter->parent);
> + *pol = regmap_test_bits(ecap_dev->regmap, ECAP_ECCTL_REG, ECAP_CAPPOL_BIT(idx)) ?
> + COUNTER_SIGNAL_POLARITY_NEGATIVE :
> + COUNTER_SIGNAL_POLARITY_POSITIVE;

This single line is doing a lot of things so I would rather see it
broken down. Save the regmap_test_bits() to a temporary local variable
first before evaluating to set *pol. This allows you to move the *pol
set operation to outside of the pm runtime syncs, possibly giving you a
marginal improvement in latency as well.

> +static inline int ecap_cnt_cap_read(struct counter_device *counter,
> + struct counter_count *count,
> + size_t idx, u64 *cap)
> +{
> + return ecap_cnt_count_get_val(counter, ECAP_CAP_REG(idx), cap);
> +}

I don't remember if we've discussed this before, but if these capture
registers can be set then it'll be useful to provide a corresponding
ecap_cnt_cap_write() function for them as well.

> +static int ecap_cnt_nb_ovf_write(struct counter_device *counter,
> + struct counter_count *count, u64 val)
> +{
> + struct ecap_cnt_dev *ecap_dev = counter_priv(counter);
> +
> + if (ecap_dev->enabled)
> + return -EBUSY;
> + if (val > 0)
> + return -EINVAL;

Similar to the count_write() callback, check that val is <= U32_MAX and
return -ERANGE otherwise.

> +static DEFINE_COUNTER_ARRAY_U64(ecap_cnt_cap_array, ECAP_NB_CEVT);
> +
> +static struct counter_comp ecap_cnt_count_ext[] = {
> + COUNTER_COMP_COUNT_ARRAY_U64("capture", ecap_cnt_cap_read, NULL, ecap_cnt_cap_array),

Use the DEFINE_COUNTER_ARRAY_CAPTURE() and COUNTER_COMP_ARRAY_CAPTURE()
macros.

> +static irqreturn_t ecap_cnt_isr(int irq, void *dev_id)
> +{
> + struct counter_device *counter_dev = dev_id;
> + struct ecap_cnt_dev *ecap_dev = counter_priv(counter_dev);
> + unsigned int clr = 0;
> + unsigned int flg;
> + int i;
> +
> + regmap_read(ecap_dev->regmap, ECAP_ECINT_EN_FLG_REG, &flg);
> +
> + /* Check capture events */
> + for (i = 0 ; i < ECAP_NB_CEVT ; i++) {
> + if (flg & ECAP_EVT_FLG_BIT(i)) {
> + counter_push_event(counter_dev, COUNTER_EVENT_CAPTURE, i);
> + clr |= ECAP_EVT_CLR_BIT(i);
> + }
> + }
> +
> + /* Check counter overflow */
> + if (flg & ECAP_EVT_FLG_BIT(ECAP_CNTOVF)) {
> + atomic_inc(&ecap_dev->nb_ovf);
> + counter_push_event(counter_dev, COUNTER_EVENT_OVERFLOW, ECAP_CNTOVF);

COUNTER_EVENT_OVERFLOW doesn't conflict with COUNTER_EVENT_CAPTURE
(they're different event types) so they can both be pushed on the same
channel; in general, events of different type can share the same event
channels. In this case, you should push COUNTER_EVENT_OVERFLOW to all
four channels whenever you detect an overflow, so that users can receive
those events regardless of which channels they are watching:

counter_push_event(counter_dev, COUNTER_EVENT_OVERFLOW, 0);
counter_push_event(counter_dev, COUNTER_EVENT_OVERFLOW, 1);
counter_push_event(counter_dev, COUNTER_EVENT_OVERFLOW, 2);
counter_push_event(counter_dev, COUNTER_EVENT_OVERFLOW, 3);

There's no additional cost to pushing to these channels because events
get dropped by the Counter chrdev code if the user is not watching the
particular event on a channel.

> +static int ecap_cnt_suspend(struct device *dev)
> +{
> + struct counter_device *counter_dev = dev_get_drvdata(dev);
> + struct ecap_cnt_dev *ecap_dev = counter_priv(counter_dev);
> +
> + /* If eCAP is running, stop capture then save timestamp counter */
> + if (ecap_dev->enabled) {
> + /*
> + * Disabling capture has the following effects:
> + * - interrupts are disabled
> + * - loading of capture registers is disabled
> + * - timebase counter is stopped
> + */
> + ecap_cnt_capture_disable(counter_dev);
> + ecap_cnt_count_get_val(counter_dev, ECAP_TSCNT_REG, &ecap_dev->pm_ctx.time_cntr);

I see time_cntr define as u64, but if count value is always <= U32_MAX
you could redefine both ecap_cnt_count_get_val()'s val and time_cntr to
u32 instead.

> +static int ecap_cnt_resume(struct device *dev)
> +{
> + struct counter_device *counter_dev = dev_get_drvdata(dev);
> + struct ecap_cnt_dev *ecap_dev = counter_priv(counter_dev);
> +
> + clk_enable(ecap_dev->clk);
> +
> + ecap_cnt_capture_set_evmode(counter_dev, ecap_dev->pm_ctx.ev_mode);
> +
> + /* If eCAP was running, restore timestamp counter then run capture */
> + if (ecap_dev->enabled) {
> + ecap_cnt_count_set_val(counter_dev, ECAP_TSCNT_REG, ecap_dev->pm_ctx.time_cntr);

Same as above would apply for ecap_cnt_count_set_val() too I think.

William Breathitt Gray

Attachment: signature.asc
Description: PGP signature