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

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

 



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


[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Input]     [Linux Kernel]     [Linux SCSI]     [X.org]

  Powered by Linux