From: Muchamad Coirul Anwar <muchamadcoirulanwar@gmail.com>
To: jic23@kernel.org, lars@metafoo.de
Cc: linux-iio@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-i2c@vger.kernel.org, rust-for-linux@vger.kernel.org,
andi.shyti@kernel.org, wsa+renesas@sang-engineering.com,
ojeda@kernel.org, dakr@kernel.org, igor.korotin@linux.dev,
branstj@gmail.com, brucer42@gmail.com,
Muchamad Coirul Anwar <muchamadcoirulanwar@gmail.com>
Subject: [RFC PATCH v5 3/3] iio: position: add Rust driver for ams AS5600
Date: Sat, 22 Aug 2026 14:26:58 +0800 [thread overview]
Message-ID: <20260822062725.60519-4-muchamadcoirulanwar@gmail.com> (raw)
In-Reply-To: <20260822062725.60519-1-muchamadcoirulanwar@gmail.com>
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<Bound>> 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)
- Type-safe IioChanInfo enum dispatch in read_raw
The AS5600 stores the 12-bit raw angle big-endian across registers
0x0C-0x0D. smbus_read_word_swapped() handles the byte swap: SMBus
always transmits the low byte first (little-endian wire), so an
unconditional byte swap recovers the correct value regardless of CPU
endianness. The long-term solution is regmap-rs where endianness is
configured once at the transport level.
This driver uses INDIO_DIRECT_MODE without buffer or trigger support.
All userspace access is through sysfs attributes, which ensures safe
cleanup via kernfs_drain() synchronization in the IIO abstraction's
PinnedDrop. See the module-level doc comment for details.
Tested on BeagleBone Black (AM335x) with AS5600 on i2c-2 (0x36).
Signed-off-by: Muchamad Coirul Anwar <muchamadcoirulanwar@gmail.com>
---
drivers/iio/position/Kconfig | 11 ++
drivers/iio/position/Makefile | 1 +
drivers/iio/position/as5600.rs | 189 +++++++++++++++++++++++++++++++++
3 files changed, 201 insertions(+)
create mode 100644 drivers/iio/position/as5600.rs
diff --git a/drivers/iio/position/Kconfig b/drivers/iio/position/Kconfig
index 1576a6380b53..ac4f19d61ff6 100644
--- a/drivers/iio/position/Kconfig
+++ b/drivers/iio/position/Kconfig
@@ -6,6 +6,17 @@
menu "Linear and angular position sensors"
+config AS5600
+ tristate "ams AS5600 magnetic rotary position sensor"
+ depends on I2C && RUST
+ help
+ Support for the ams OSRAM AS5600 12-bit magnetic rotary
+ position sensor. Provides in_angl_raw (0-4095) and
+ in_angl_scale (radians per LSB) via sysfs.
+
+ 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/Makefile b/drivers/iio/position/Makefile
index d70902f2979d..2d26f6d6ace3 100644
--- a/drivers/iio/position/Makefile
+++ b/drivers/iio/position/Makefile
@@ -4,5 +4,6 @@
# When adding new entries keep the list in alphabetical order
+obj-$(CONFIG_AS5600) += as5600.o
obj-$(CONFIG_HID_SENSOR_CUSTOM_INTEL_HINGE) += hid-sensor-custom-intel-hinge.o
obj-$(CONFIG_IQS624_POS) += iqs624-pos.o
diff --git a/drivers/iio/position/as5600.rs b/drivers/iio/position/as5600.rs
new file mode 100644
index 000000000000..8f2ea20a0645
--- /dev/null
+++ b/drivers/iio/position/as5600.rs
@@ -0,0 +1,189 @@
+// SPDX-License-Identifier: GPL-2.0-only
+// Copyright (C) 2026 Muchamad Coirul Anwar <muchamadcoirulanwar@gmail.com>
+//! Driver for ams AS5600 12-bit magnetic rotary position sensor.
+//!
+//! This driver uses `INDIO_DIRECT_MODE` without buffer or trigger support.
+//! All userspace access is through sysfs attributes (`in_angl_raw`,
+//! `in_angl_scale`), which ensures safe cleanup via `kernfs_drain()`
+//! synchronization in the IIO abstraction's `PinnedDrop`.
+//!
+//! Datasheet: https://look.ams-osram.com/m/7059eac7531a86fd/original/AS5600-DS000365.pdf
+
+use kernel::{
+ bindings::{
+ iio_chan_info_enum_IIO_CHAN_INFO_RAW,
+ iio_chan_info_enum_IIO_CHAN_INFO_SCALE,
+ iio_chan_spec,
+ iio_chan_type_IIO_ANGL,
+ INDIO_DIRECT_MODE, //
+ },
+ bits::{
+ bit_u8,
+ bit_usize,
+ genmask_u16, //
+ },
+ device::{
+ Bound,
+ Core, //
+ },
+ error::code::ENODATA,
+ i2c::{
+ DeviceId,
+ Driver,
+ I2cClient,
+ IdTable, //
+ },
+ i2c_device_table,
+ iio::{
+ Device,
+ IioChanInfo,
+ IioDriver,
+ IioVal,
+ Registered, //
+ },
+ io::Io,
+ module_i2c_driver,
+ of,
+ of_device_table,
+ prelude::*,
+ sync::{
+ aref::ARef,
+ new_mutex,
+ Mutex, //
+ }, //
+};
+
+const AS5600_REG_STATUS: u8 = 0x0B;
+const AS5600_REG_RAW_ANGLE_H: u8 = 0x0C;
+
+const AS5600_STATUS_MD: u8 = bit_u8(5);
+const AS5600_RAW_ANGLE_MASK: u16 = genmask_u16(0..=11);
+
+module_i2c_driver! {
+ type: As5600,
+ name: "as5600",
+ authors: ["Muchamad Coirul Anwar"],
+ description: "I2C Driver for ams OSRAM AS5600 Magnetic Rotary Position Sensor",
+ license: "GPL",
+}
+
+i2c_device_table!(
+ I2C_TABLE,
+ MODULE_I2C_TABLE,
+ <As5600 as Driver>::IdInfo,
+ [(DeviceId::new(c"as5600"), ())]
+);
+
+of_device_table!(
+ OF_TABLE,
+ MODULE_OF_TABLE,
+ <As5600 as Driver>::IdInfo,
+ [(of::DeviceId::new(c"ams,as5600"), ())]
+);
+
+struct As5600Channels([iio_chan_spec; 1]);
+
+// SAFETY: `iio_chan_spec` is a plain C struct with no interior mutability.
+// All pointer fields (`event_spec`, `ext_info`, `extend_name`, etc.) are
+// NULL — set via `zeroed()` and never reassigned — so no shared mutable
+// state exists behind them. The static is a compile-time constant with no
+// `&mut` access path, making concurrent shared access safe.
+unsafe impl Sync for As5600Channels {}
+
+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;
+ chan.info_mask_separate = bit_usize(iio_chan_info_enum_IIO_CHAN_INFO_RAW)
+ | bit_usize(iio_chan_info_enum_IIO_CHAN_INFO_SCALE);
+ [chan]
+});
+
+#[pin_data]
+struct As5600Priv {
+ #[pin]
+ io_lock: Mutex<As5600HwState>,
+}
+
+struct As5600HwState {
+ client: ARef<I2cClient<Bound>>,
+}
+
+impl IioDriver for As5600Priv {
+ fn read_raw(&self, _chan: *const iio_chan_spec, info: IioChanInfo) -> Result<IioVal> {
+ match info {
+ IioChanInfo::Raw => {
+ let hw = self.io_lock.lock();
+ let io = hw.client.smbus_io();
+ // Read status register to verify magnet presence before
+ // reading the angle.
+ let status = io.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 returns big-endian data.
+ // smbus_read_word_swapped() handles the byte swap.
+ // Mutex ensures status + angle read is atomic.
+ let raw = hw.client.smbus_read_word_swapped(AS5600_REG_RAW_ANGLE_H)?;
+ let angle = raw & AS5600_RAW_ANGLE_MASK;
+ Ok(IioVal::Int(angle as i32))
+ }
+ // Scale factor: radians per LSB = 2*pi / 4096 ~= 0.001533981
+ IioChanInfo::Scale => Ok(IioVal::IntPlusNano(0, 1533981)),
+ }
+ }
+
+ fn channels(&self) -> &'static [iio_chan_spec] {
+ &AS5600_CHANNELS.0
+ }
+}
+
+#[pin_data]
+struct As5600 {
+ #[pin]
+ _iio_dev: Device<As5600Priv, Registered>,
+}
+
+impl Driver for As5600 {
+ type IdInfo = ();
+ type Data<'bound> = As5600;
+
+ const I2C_ID_TABLE: Option<IdTable<Self::IdInfo>> = Some(&I2C_TABLE);
+ const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+
+ // `try_pin_init!` returns a concrete anonymous type that may expose more
+ // bounds than the trait signature declares (e.g. auto-traits like `Send`).
+ // This refinement of the RPITIT return type is intentional.
+ #[allow(refining_impl_trait)]
+ fn probe<'bound>(
+ dev: &'bound I2cClient<Core<'_>>,
+ _id_info: Option<&'bound Self::IdInfo>,
+ ) -> impl PinInit<Self::Data<'bound>, Error> + 'bound {
+ try_pin_init!(As5600 {
+ _iio_dev: {
+ // Deref coercion: I2cClient<Core<'_>> -> I2cClient<Bound>.
+ // We capture the Bound context to call smbus_read_word_swapped()
+ // and try_read8(), which require Bound.
+ let bound: &I2cClient<Bound> = dev;
+ let client: ARef<I2cClient<Bound>> = ARef::from(bound);
+
+ let priv_init = pin_init!(As5600Priv {
+ io_lock <- new_mutex!(As5600HwState {
+ client
+ }),
+ });
+
+ let iio_dev =
+ Device::build_device(dev.as_ref(), c"as5600", INDIO_DIRECT_MODE, priv_init)?;
+ let registered = iio_dev.register(&crate::THIS_MODULE)?;
+ dev_dbg!(dev.as_ref(), "AS5600 magnetic position sensor ready\n");
+ registered
+ }
+ })
+ }
+}
--
2.50.0
next prev parent reply other threads:[~2026-08-22 6:28 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-22 6:26 [RFC PATCH v5 0/3] iio: position: add Rust driver for ams AS5600 Muchamad Coirul Anwar
2026-08-22 6:26 ` [RFC PATCH v5 1/3] i2c: rust: implement SMBus access via IoBackend and FallibleIoCapable Muchamad Coirul Anwar
2026-08-23 23:41 ` Jonathan Cameron
2026-08-22 6:26 ` [RFC PATCH v5 2/3] rust: add minimal IIO subsystem abstractions Muchamad Coirul Anwar
2026-08-24 0:07 ` Jonathan Cameron
2026-08-22 6:26 ` Muchamad Coirul Anwar [this message]
2026-08-24 0:17 ` [RFC PATCH v5 3/3] iio: position: add Rust driver for ams AS5600 Jonathan Cameron
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=20260822062725.60519-4-muchamadcoirulanwar@gmail.com \
--to=muchamadcoirulanwar@gmail.com \
--cc=andi.shyti@kernel.org \
--cc=branstj@gmail.com \
--cc=brucer42@gmail.com \
--cc=dakr@kernel.org \
--cc=igor.korotin@linux.dev \
--cc=jic23@kernel.org \
--cc=lars@metafoo.de \
--cc=linux-i2c@vger.kernel.org \
--cc=linux-iio@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=wsa+renesas@sang-engineering.com \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox