From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pj1-f46.google.com (mail-pj1-f46.google.com [209.85.216.46]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 1406411CA9 for ; Sun, 24 May 2026 13:28:54 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.216.46 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779629335; cv=none; b=n06MqZCO5R4W+T6IBQQEbWgkSfkiRGn5zd4U/Mg7PMXniu73rby0yKY8QVsWk/cxdbKz/M/ZyYG8Np65PzgMVg51LO4PE+SD/GKPa/DI61w9xnN87tXiej/iFlIok6jBeFYf6Ipxd+US7Amqwj3DrP0BinY7hdq/9QnL7UgcUY4= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779629335; c=relaxed/simple; bh=sCPRa+Pg6iqOf8JJkDf2nkLCIFCgH9NKdJKw4TM4vwY=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=M1Win122ZwVderkMxNxKAQ++kWsdxqUp1Er3oGDjB0rlMOBQRwBk5e6fGuUsafCk7re2kWitFT0t2ogIg2fiAynTjzADsMf9GfJPls3LSxgScavOGEDUXsP52zd4SyMFNT/W47wCkaMLketB8z/DhucRiPbdRaOHP3FyWDrjybU= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=BI5x5GtG; arc=none smtp.client-ip=209.85.216.46 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="BI5x5GtG" Received: by mail-pj1-f46.google.com with SMTP id 98e67ed59e1d1-36931e4f5e8so8100505a91.2 for ; Sun, 24 May 2026 06:28:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1779629333; x=1780234133; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=uVuOrsHgj0yEZq2/rleCfYS+yYI4maV483S6Tokx11g=; b=BI5x5GtGots6fwUKcpewsEcPHsIHPyQgMWyro8awez8COSreITBfdqaMB0hp2VPt8S yeIZPFyP9Vk918NudFhE7ZpSYF/gDhhkFn8wpIX6OYTL+bOVbQKxo4toRRv+SRzJhPBB 1QdYX5KJdX8bWHm7IcEFYjg2mT8zqH8VJwi/uAC8aKhZBWfGvdjWskkMXwmlTSVsFpAO sgriWZaAPVc1aemvcHyQRMVWOgILCvNCK+/9eeKJeAyCnekjSaSjdMAIQFMjF90qlXZX r5saWV5hmqGOyeu30KAmPRTP4tFMnVCpFLdwer/lQY6IGHqndBke4yuD0Q0maePFAb4N b4qg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1779629333; x=1780234133; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=uVuOrsHgj0yEZq2/rleCfYS+yYI4maV483S6Tokx11g=; b=PO4chk+Pltl7eeQPlzGdgIRJeTICBGShRtH0TYnCLspDlI2Pw0Jxwy/fN/i1mftXoM SjPjpNUOx0RZklkE0GGnJOUupnbZL72bYo5z8dBvtCVtKzYijX7+BtHHqk7NaSw3NnEo hghInr5b1tUV4cgZ26ozDvukvQHsj3ORMKfxmUMcllpJF6ceqPCvItg1BSFfhQsSSigq /XilK5RI8m8QVxXud/pyIZ1OsVPwsAfED10Z6c2jOGxTAT9LAUCSnlRrJ/NaUC+FHesw bMDGZpNSquha9Fa68/hRZeVb6laWd6AdBrm9u6M0Q1bXesCWk+SqJrqeu0oMNwWkA8i5 6CHg== X-Forwarded-Encrypted: i=1; AFNElJ9/4CJKtzErDcipOyFgRTgQ/kFzc17i2zS/xkWkjQmvWl4LUhn/YeQIKHRPvzq3sQ6DjGYGmIu3DaBsDPp08w==@vger.kernel.org X-Gm-Message-State: AOJu0YxwvVdQcUC6KgJRlP+uU7yac4smchQEHXU2paruDk/NAWTpkP32 n91tI7KFAsNz4bhhjclsBC1gXuCyOcYq+OF3jsK3h/Ad/evPhgktFNZjhq8LHQBN X-Gm-Gg: Acq92OH13//HCL5Wd9L8G/pCtuinCM1vPHId52mKZ0K4AFHQoX3ePE23n5d4MWY8iYj A+R4aW64qwzuoCO16uUh45B1IZQIK0/Xz8jH7MMg2uWWsf3lHMbJ4lvY4qRTe7ll/X5qcJ9jTP/ 0U21uXkDh8vlDowJIaelIV+hQFZ5C52Oo27azbYnK+COtk43tl7c7o/Bwqtsrp7HuUXl5NDKNXx Qoke+1RVTYgbVdqqp1HwhzsPtMvofjnybM1yIojU0NR6wnDDp9e3ajs0dM2fi3yKxPMg1nzW4aA rdPY9ENDlpIt0WH/aS94zYR4syu1xwrGHHqkTlZm3rz/FfrefecgUALItIVBH9Baq5Spakcjx6e XrLP3B4YC4x2MLudJG0ors5XWpCynsCCNBbMaduAcU/wLc26cFwDEjqMn8Y8PaBo2kfmF7Fn4uY cvIRlqx5ygzNOdg88i/y0JesUKIchkoxg5i0CX0mJBgBr1QfDfy361hbi6a4byfg== X-Received: by 2002:a17:903:2b0e:b0:2bc:7c62:187 with SMTP id d9443c01a7336-2beb06133f5mr125142545ad.29.1779629333392; Sun, 24 May 2026 06:28:53 -0700 (PDT) Received: from 192.168.1.3 ([2001:448a:2003:4286:c553:4edf:bb9:bbd2]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2beb56f4343sm91197175ad.36.2026.05.24.06.28.50 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Sun, 24 May 2026 06:28:52 -0700 (PDT) From: Muchamad Coirul Anwar To: Jonathan Cameron Cc: linux-iio@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, Miguel Ojeda , Igor Korotin , Brandon Saint-John , Muchamad Coirul Anwar Subject: [RFC PATCH v3 3/4] iio: position: add Rust driver for ams AS5600 Date: Sun, 24 May 2026 20:28:22 +0700 Message-ID: <20260524132824.54918-4-muchamadcoirulanwar@gmail.com> X-Mailer: git-send-email 2.50.0 In-Reply-To: <20260524132824.54918-1-muchamadcoirulanwar@gmail.com> References: <20260524132824.54918-1-muchamadcoirulanwar@gmail.com> Precedence: bulk X-Mailing-List: rust-for-linux@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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: - Circuit breaker pattern for I/O error resilience - Mutex-serialized multi-byte angle read sequence - Automatic recovery from bus failures (Poisoned -> Normal) - No magnet validation at probe time (deferred to read_raw) Tested on BeagleBone Black (AM335x) with AS5600 on i2c-2 (0x36). Signed-off-by: Muchamad Coirul Anwar --- drivers/iio/position/as5600.rs | 289 +++++++++++++++++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 drivers/iio/position/as5600.rs diff --git a/drivers/iio/position/as5600.rs b/drivers/iio/position/as5600.rs new file mode 100644 index 000000000000..f87df650a91d --- /dev/null +++ b/drivers/iio/position/as5600.rs @@ -0,0 +1,289 @@ +// SPDX-License-Identifier: GPL-2.0-only +// Copyright (C) 2026 Muchamad Coirul Anwar +//! Driver for ams AS5600 12-bit magnetic rotary position sensor. +//! +//! Datasheet: https://ams.com/documents/20143/36005/AS5600_DS000365_5-00.pdf + +use kernel::{ + alloc::KBox, + bindings::{ + i2c_client, + iio_chan_info_enum_IIO_CHAN_INFO_RAW, + iio_chan_info_enum_IIO_CHAN_INFO_SCALE, + iio_chan_spec, + iio_chan_type_IIO_ANGL, + ENODATA, // + }, + bits::bit_u8, + device::Core, + error::{code::EIO, Error}, + i2c::{ + DeviceId, + Driver, + I2cClient, + IdTable, // + }, + i2c_device_table, + iio::{ + Device, + IioDriver, + IioVal, + Registered, // + }, + io::{Io, IoCapable}, + module_i2c_driver, of, of_device_table, + prelude::*, + sync::{ + new_mutex, + Mutex, // + }, +}; + +const AS5600_REG_STATUS: u8 = 0x0B; +const AS5600_REG_RAW_ANGLE_H: u8 = 0x0C; +const AS5600_REG_RAW_ANGLE_L: u8 = 0x0D; + +const AS5600_STATUS_MD: u8 = bit_u8(5); + +/// Returns kernel error `ENODATA`. +/// +/// Helper needed because `Error::from_errno` is not `const fn` and `ENODATA` +/// is only available as a raw `u32` binding, not as a wrapped `kernel::error::code`. +#[inline(always)] +fn err_enodata() -> Error { + Error::from_errno(-(ENODATA as i32)) +} + +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, + ::IdInfo, + [(DeviceId::new(c"as5600"), ())] +); + +of_device_table!( + OF_TABLE, + MODULE_OF_TABLE, + ::IdInfo, + [(of::DeviceId::new(c"ams,as5600"), ())] +); + +#[derive(Clone, Copy)] +struct As5600Io(*mut i2c_client); + +/// Tracks the health state of the hardware bus to prevent I/O storms. +#[derive(Clone, Copy, PartialEq, Eq)] +enum DeviceState { + Normal, + Poisoned, +} + +// SAFETY: `As5600Io` wraps a raw pointer to `i2c_client`. This is `Send` +// and `Sync` because: +// - The I2C subsystem guarantees the `i2c_client` (parent) outlives the +// IIO device (child) via the Linux Device Model unbind ordering. +// - All hardware access is serialized through `Mutex>` +// (`io_lock`), and individual SMBus transactions are serialized by the I2C +// adapter lock. +unsafe impl Send for As5600Io {} +unsafe impl Sync for As5600Io {} + +impl IoCapable for As5600Io {} +impl IoCapable for As5600Io {} + +impl Io for As5600Io { + #[inline] + fn addr(&self) -> usize { + 0 + } + + #[inline] + fn maxsize(&self) -> usize { + 256 + } + + #[inline] + fn try_read8(&self, offset: usize) -> Result + where + Self: IoCapable, + { + // SAFETY: `self.0` points to a valid `i2c_client` guaranteed by the + // Device Model lifetime hierarchy (parent outlives child). The cast is + // valid because `I2cClient` is `#[repr(transparent)]` over `i2c_client`. + let client = unsafe { &*(self.0 as *const I2cClient) }; + client.try_read8(offset) + } + + #[inline] + fn try_read16(&self, offset: usize) -> Result + where + Self: IoCapable, + { + // SAFETY: `self.0` points to a valid `i2c_client` guaranteed by the + // Device Model lifetime hierarchy (parent outlives child). The cast is + // valid because `I2cClient` is `#[repr(transparent)]` over `i2c_client`. + let client = unsafe { &*(self.0 as *const I2cClient) }; + client.try_read16(offset) + } +} + +#[pin_data] +struct As5600Priv { + #[pin] + io_lock: Mutex>, + channels: KBox<[iio_chan_spec; 1]>, +} + +/// Encapsulates the I/O interface and its runtime health state. +/// +/// This prevents operations on a known-dead bus (Circuit Breaker pattern). +struct As5600HwState { + io: T, + state: DeviceState, +} + +impl> As5600HwState { + /// Performs a dummy read to probe bus health after an I/O failure. + /// + /// Returns `EIO` in all cases — the caller should always propagate the error. + /// The side effect determines recovery behavior: + /// - If the dummy read **succeeds**: state is reset to `Normal`, meaning the + /// next `read_raw` call will attempt normal operation directly. + /// - If the dummy read **fails**: state is set to `Poisoned`, meaning the + /// next `read_raw` call will attempt recovery before normal operation. + fn handle_io_error(&mut self) -> Error { + match self.io.try_read8(AS5600_REG_STATUS as usize) { + Ok(_) => { + self.state = DeviceState::Normal; + EIO + } + Err(_) => { + self.state = DeviceState::Poisoned; + EIO + } + } + } +} + +// SAFETY: `As5600Priv` is `Send` and `Sync` because: +// - `T: IoCapable` is a marker trait with no interior mutability. +// The underlying `As5600Io` wrapper's Send/Sync is guaranteed by its +// manual impls (serialized via Mutex + I2C adapter lock). +// - `channels` is a heap-allocated array (`KBox`) with no interior mutability. +// - `io_lock: Mutex>` provides synchronized interior mutability. +// - `DeviceState` is a plain enum without interior mutability (Send + Sync +// implicitly). +// All concurrent access to hardware goes through the `Mutex` guard. +// The `Unpin` bound is strictly required because `kernel::sync::lock::Guard` +// only implements `DerefMut` for `T: Unpin`. Without it, state mutation fails. +unsafe impl + Unpin> Send for As5600Priv {} +unsafe impl + Unpin> Sync for As5600Priv {} + +impl + Unpin> IioDriver for As5600Priv { + fn read_raw(&self, _chan: *const iio_chan_spec, mask: isize) -> Result { + match mask { + // IIO_CHAN_INFO_RAW — read the 12-bit raw angle value. + m if m == iio_chan_info_enum_IIO_CHAN_INFO_RAW as isize => { + let mut hw_guard = self.io_lock.lock(); + + // If the bus was previously poisoned, attempt a single recovery + // read before proceeding with normal operation. + let status = if hw_guard.state == DeviceState::Poisoned { + match hw_guard.io.try_read8(AS5600_REG_STATUS as usize) { + Ok(s) => { + hw_guard.state = DeviceState::Normal; + s + } + Err(_) => return Err(EIO), + } + } else { + match hw_guard.io.try_read8(AS5600_REG_STATUS as usize) { + Ok(s) => s, + Err(_) => return Err(hw_guard.handle_io_error()), + } + }; + + // Check magnet presence (MD bit). Without a magnet the angle + // register contains stale/invalid data. + if (status & AS5600_STATUS_MD) == 0 { + return Err(err_enodata()); + } + + // Read the 12-bit angle as two bytes. The AS5600 hardware + // freezes the internal angle value on reading the high byte + // until the low byte is read — the Mutex ensures this + // sequence is not interleaved by concurrent readers. + let angle_h = match hw_guard.io.try_read8(AS5600_REG_RAW_ANGLE_H as usize) { + Ok(v) => v as u16, + Err(_) => return Err(hw_guard.handle_io_error()), + }; + let angle_l = match hw_guard.io.try_read8(AS5600_REG_RAW_ANGLE_L as usize) { + Ok(v) => v as u16, + Err(_) => return Err(hw_guard.handle_io_error()), + }; + + let angle = (angle_h << 8 | angle_l) & 0x0FFF; + Ok(IioVal::Int(angle as i32)) + } + // IIO_CHAN_INFO_SCALE — radians per LSB: 2π / 4096 ≈ 0.001533981. + m if m == iio_chan_info_enum_IIO_CHAN_INFO_SCALE as isize => { + Ok(IioVal::IntPlusNano(0, 1533981)) + } + _ => Err(kernel::error::code::EINVAL), + } + } + + fn channels(&self) -> &[iio_chan_spec] { + &self.channels[..] + } +} + +struct As5600 { + _iio_dev: Device, Registered>, +} + +impl Driver for As5600 { + type IdInfo = (); + const I2C_ID_TABLE: Option> = Some(&I2C_TABLE); + const OF_ID_TABLE: Option> = Some(&OF_TABLE); + + #[allow(refining_impl_trait)] + fn probe(dev: &I2cClient, _id_info: Option<&Self::IdInfo>) -> Result { + // SAFETY: `iio_chan_spec` is a C struct whose fields are all integers + // and pointers. Zero is a valid initialization for all of them. + let mut channels_alloc = kernel::alloc::KBox::new( + [unsafe { core::mem::zeroed::() }], + kernel::alloc::flags::GFP_KERNEL, + )?; + + channels_alloc[0].info_mask_separate = (1 << iio_chan_info_enum_IIO_CHAN_INFO_RAW) + | (1 << iio_chan_info_enum_IIO_CHAN_INFO_SCALE); + channels_alloc[0].type_ = iio_chan_type_IIO_ANGL; + + let client_ptr = dev as *const _ as *mut i2c_client; + + let priv_init = pin_init!(As5600Priv { + io_lock <- new_mutex!(As5600HwState { + io: As5600Io(client_ptr), + state: DeviceState::Normal, + }), + channels: channels_alloc, + }); + + let iio_dev = Device::build_device(dev.as_ref(), c"as5600", priv_init)?; + let iio_dev_registered = iio_dev.register(&crate::THIS_MODULE)?; + + dev_info!(dev.as_ref(), "AS5600 magnetic position sensor ready\n"); + Ok(As5600 { + _iio_dev: iio_dev_registered, + }) + } +} -- 2.50.0