Re: [PATCH v4 2/3] counter: add GPIO-based quadrature encoder driver
From: Wadim Mueller
Date: Sun May 24 2026 - 15:36:05 EST
On 2026-05-21 09:26, William Breathitt Gray wrote:
> From: Wadim Mueller <wafgo01@xxxxxxxxx>
>
> On Wed, May 20, 2026 at 01:45:20PM +0900, William Breathitt Gray wrote:
> > On Fri, May 15, 2026 at 05:36:15PM +0200, Wadim Mueller wrote:
> > > +static int gpio_qenc_a_delta(struct gpio_qenc_priv *priv, int a, int b,
> > > + int prev_a, int prev_b)
> > > +{
> > > + int state = CREATE_QE_STATE(prev_a, prev_b, a, b);
> > > +
> > > + switch (priv->function) {
> > > + case COUNTER_FUNCTION_QUADRATURE_X4:
> > > + return gpio_qenc_quad_x4_table[state];
> > > +
> > > + case COUNTER_FUNCTION_QUADRATURE_X2_A:
> > > + /* Both edges of A; sign comes from current A vs B. */
> > > + return (a == b) ? -1 : 1;
> > > +
> > > + case COUNTER_FUNCTION_QUADRATURE_X1_A:
> > > + /* Rising edge of A only. */
> > > + if (!prev_a && a)
> > > + return b ? -1 : 1;
> > > + return 0;
> >
> > Quadrature X1 count modes trigger on the falling edge when the direction
> > is backward. This isn't simply a requirement by definition, but
> > necessary for the proper interpretation of the quadrature encoding.
> >
> > Let's evaluate an incremental encoder used in a positioning application
> > as typical use case.[^1] These are commonly implemented using a rotating
> > shaft with a quadrature-offset pattern; aligned sensors detect the
> > physical A/B pattern as the shaft rotates.[^2] As the shaft rotates a
> > quadrature encoding emerges whose A-B phase difference allows us to
> > determine direction: forward when rising edge of signal A leads B, and
> > backward when it trails.[^3]
> >
> > Now consider what happens to the signals when the rotation changes
> > direction: there is a phase change between Signals A and B.[^4] The A/B
> > pattern on the shaft is physically present so it has not changed; rather
> > the pattern is now fed backwards to the sensors due to the direction
> > reversal. The key point is the physical boundaries of the pattern are
> > located in the same shaft positions they have always been, yet the
> > signal edges representing those boundaries have flipped as a result of
> > the direction change: positions marked by rising edges now appear as
> > falling edges.
> >
> > In Quadrature X4 and X2, the pattern reversal doesn't affect positioning
> > because we count on both edges, so swapping rising and falling edges
> > nets the same position count. Quadrature X1 presents a problem because
> > we count on a single edge type, so a phase-difference in the encoding
> > results in a physical shift in real-life position. The way to account
> > for that phase shift is to swap counting to the other edge type when the
> > direction changes. That's how dedicated quadrature encoder devices solve
> > this problem.
> >
> > I'm not sure of the best way to solve the Quadrature X1 problem in this
> > driver. Right now we fire off interrupts on both edges, so perhaps
> > there's a way for us to determine whether we're firing on a rising edge
> > or falling edge and evaluate accordingly. Does the GPIO subsystem
> > provide an indication for which edge triggered the interrupt? Or would
> > it make sense to provide two interrupt service routines (one on rising
> > edge and one on falling edge) and handle it that way?
>
> The simplest method might be to evaluate the current GPIO level to
> determine the edge polarity. Because we trigger on both edges, we can
> assume a high level means a low-high transition (rising edge) and a low
> level means a high-low transition (falling edge).
>
> Using that assumption, we can implement the Quadrature X1 case by
> checking the current state and direction, and adjusting the counting
> accordingly when applicable: count up if rising edge and forward
> direction, and count down if falling edge and backward direction.
>
Implemented in following v5 as suggested in both signal-A and signal-B ISRs. One
caveat I called out in the source: in pure X1 mode the driver never
sees both edges of both signals, so direction is whatever the last
X4/X2 sample produced (or whatever userspace set via sysfs). In
practice X1 fits applications that already know the direction or
that have just calibrated in X4.
> Quadrature X1 A is handled by the signal A interrupt service routine:
>
> /* COUNTER_FUNCTION_QUADRATURE_X1_A */
> if (ca)
> if (direction == COUNTER_COUNT_DIRECTION_FORWARD)
> count++;
> else
> if (direction == COUNTER_COUNT_DIRECTION_BACKWARD)
> count--;
>
> Quadrature X1 B is handled by the signal B interrupt service routine:
>
> /* COUNTER_FUNCTION_QUADRATURE_X1_B */
> if (cb)
> if (direction == COUNTER_COUNT_DIRECTION_FORWARD)
> count++;
> else
> if (direction == COUNTER_COUNT_DIRECTION_BACKWARD)
> count--;
>
> There's an obvious caveat that this method only works if we're able to
> check the GPIO level before the next edge arrives (we're limited to low
> frequencies), but that's a caveat present regardless for our software
> counter so I believe this is an acceptable solution.
>
> William Breathitt Gray