Re: [RFC PATCH v4 3/3] iio: position: add Rust driver for ams AS5600

Jonathan Cameron <[email protected]> Mon, 3 Aug 2026 02:13:04 +0100
Newsgroups org.kernel.vger.linux-iio,org.kernel.vger.linux-i2c,org.kernel.vger.linux-kernel
Message-ID <20260803021304.60316f02@jic23-huawei>
On Tue,  7 Jul 2026 22:15:42 +0700
Muchamad Coirul Anwar <[email protected]> wrote:

> Add a Rust driver for the ams AS5600 12-bit magnetic rotary position
> sensor. The driver exposes in_angl_raw and in_angl_scale via the IIO
> sysfs interface.
> 
> Features:
> - ARef<I2cClient> for safe refcounted I2C client access
> - Mutex-serialized status + angle read sequence
> - Static channel spec (module-level const)
> - No magnet validation at probe (deferred to read_raw per IIO convention)
> - Error propagation via ? operator (no recovery state machine)
> 
> The byte order for the AS5600's big-endian registers is handled via
> swap_bytes() in-driver. This is equivalent to C's
> i2c_smbus_read_word_swapped(). The long-term solution is regmap-rs
> where endianness is configured once at the transport level.
> 
> Tested on BeagleBone Black (AM335x) with AS5600 on i2c-2 (0x36).
> 
> Signed-off-by: Muchamad Coirul Anwar <[email protected]>

Sadly my rust remains very limited :(  So you both end up at the back
of my review queue and I can't provide much useful review!

> ---
>  drivers/iio/position/Kconfig   |  14 +++
>  drivers/iio/position/Makefile  |   1 +
>  drivers/iio/position/as5600.rs | 181 +++++++++++++++++++++++++++++++++
>  3 files changed, 196 insertions(+)
>  create mode 100644 drivers/iio/position/as5600.rs
> 
> diff --git a/drivers/iio/position/Kconfig b/drivers/iio/position/Kconfig
> index 1576a6380b53..573d241676bf 100644
> --- a/drivers/iio/position/Kconfig
> +++ b/drivers/iio/position/Kconfig
> @@ -6,6 +6,20 @@
>  
>  menu "Linear and angular position sensors"
>  
> +config AS5600
> +	tristate "ams AS5600 magnetic rotary position sensor"
> +	depends on I2C && IIO && RUST

The IIO dependence should be implicit given it is buried
deep in a menu gated on that.

> +	help
> +	  Say Y here to build support for the ams AS5600 12-bit

This Say Y language is odd as it sort of disagrees with the M
section below.  Start off with what it is then finish up with
any suggestions on Y vs M.

> +	  magnetic rotary position sensor with IIO channel support
> +	  (in_angl_raw and in_angl_scale).
> +
> +	  This is a Rust driver that exposes the 12-bit raw angle

I think the aim is that no one configuring the kernel should even
know what the language used.

> +	  and radian scale via the IIO subsystem.

I'd skip the IIO reference here given to get to this help typically
someone already navigated down into the IIO menus.

> +
> +	  To compile this driver as a module, choose M here: the
> +	  module will be called as5600.
> +
>  config IQS624_POS
>  	tristate "Azoteq IQS624/625 angular position sensors"
>  	depends on MFD_IQS62X || COMPILE_TEST


> diff --git a/drivers/iio/position/as5600.rs b/drivers/iio/position/as5600.rs
> new file mode 100644
> index 000000000000..7445398c86b9
> --- /dev/null
> +++ b/drivers/iio/position/as5600.rs

> +
> +static AS5600_CHANNELS: As5600Channels = As5600Channels({
> +    // SAFETY: `iio_chan_spec` is a repr(C) struct where all-zeroes is valid
> +    // (integers default to 0, pointers to NULL).
> +    let mut chan: iio_chan_spec = unsafe { core::mem::zeroed() };
> +    chan.type_ = iio_chan_type_IIO_ANGL;
> +    // TODO: Use kernel::bits equivalent once bit_usize exists
> +    chan.info_mask_separate = (1usize << iio_chan_info_enum_IIO_CHAN_INFO_RAW)
> +        | (1usize << iio_chan_info_enum_IIO_CHAN_INFO_SCALE);

No nice BIT() equivalent?   Those names end up rather repetitive with
most of it coming twice.

I don't suppose there is any way to avoid that?

> +    [chan]
> +});
> +
> +impl IioDriver for As5600Priv {
> +    fn read_raw(&self, _chan: *const iio_chan_spec, mask: isize) -> Result<IioVal> {
> +        const INFO_RAW: isize = iio_chan_info_enum_IIO_CHAN_INFO_RAW as isize;
> +        const INFO_SCALE: isize = iio_chan_info_enum_IIO_CHAN_INFO_SCALE as isize;
> +        match mask {
> +            // IIO_CHAN_INFO_RAW: read the 12-bit raw angle value.
> +            INFO_RAW => {
> +                let hw = self.io_lock.lock();
> +
> +                // Read status register to verify magnet presence before
> +                // reading the angle.
> +                let status = hw.client.try_read8(AS5600_REG_STATUS as usize)?;
> +
> +                // Check magnet presence (MD bit). Without a magnet the angle
> +                // register contains stale/invalid data.
> +                if (status & AS5600_STATUS_MD) == 0 {
> +                    return Err(ENODATA);
> +                }
> +
> +                // Word read at register 0x0C: SMBus read_word_data returns LE,

That seems wrong.  It assumes LE data on the wire, but should return in
the CPU endianness.  Hence it's a swap in all cases, rather than an be16_to_cpu()
which would get it backwards on a big endian platform.


> +                // AS5600 stores angle big-endian, so swap_bytes() is needed.
> +                // Mutex ensures status + angle read is atomic.
> +                // NOTE: Equivalent to C's i2c_smbus_read_word_swapped().
> +                // Long-term, regmap-rs with val_format_endian=Big handles
> +                // this transparently at configuration level.
> +                let raw = hw.client.try_read16(AS5600_REG_RAW_ANGLE_H as usize)?;
> +                let angle = raw.swap_bytes() & AS5600_RAW_ANGLE_MASK;
> +                Ok(IioVal::Int(angle as i32))
> +            }
> +            // IIO_CHAN_INFO_SCALE: radians per LSB, 2*pi / 4096 = 0.001533981.
> +            INFO_SCALE => {
> +                Ok(IioVal::IntPlusNano(0, 1533981))
> +            }
> +            _ => Err(EINVAL),
> +        }
> +    }
> +
> +    fn channels(&self) -> &[iio_chan_spec] {
> +        &AS5600_CHANNELS.0
> +    }
> +}