* [RFC PATCH 0/3] rust: power_supply class abstraction and SMB347 charger driver
@ 2026-07-08 21:47 Bruce Robertson
2026-07-08 21:47 ` [RFC PATCH 1/3] rust: i2c: add SMBus byte transfer helpers Bruce Robertson
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Bruce Robertson @ 2026-07-08 21:47 UTC (permalink / raw)
To: rust-for-linux, linux-pm, linux-i2c, linux-kernel
Cc: Sebastian Reichel, Miguel Ojeda, Igor Korotin, Gary Guo,
Tamir Duberstein, Alice Ryhl, Boqun Feng, Bruce Robertson
This series adds a Rust abstraction for the power supply class -- which
does not currently exist in mainline or rust-next -- together with the
SMBus helpers needed to drive an I2C charger from Rust, and a Rust port
of the Summit SMB347 battery charger as the first consumer.
Motivation
----------
Rust drivers for power supply hardware are currently blocked: there is
no safe Rust interface to the power supply class, and the I2cClient
abstraction exposes no register I/O. This series provides a minimal,
self-contained path from binding an I2C charger to reporting charging
state through sysfs, entirely in safe Rust, with the unsafe FFI confined
to the two abstraction layers.
The series is structured abstraction-first:
1/3 Safe SMBus read/write/update_bits over i2c::I2cClient.
2/3 A power_supply Driver trait, a generic get_property trampoline,
and an RAII Registration that owns the descriptor lifetime.
3/3 A Rust SMB347 charger driver consuming both: it binds over I2C
and reports STATUS, ONLINE and CHARGE_TYPE.
Testing
-------
Built and exercised against an emulated SMB347 using i2c-stub: seeding
the chip's status registers and reading back the corresponding sysfs
attributes (status, online, charge_type) confirms the full C->Rust->C
path. No physical hardware or interrupt path has been tested.
Open questions / known limitations (hence RFC)
----------------------------------------------
- get_property recovers the driver's private data via the parent
device's drvdata. The current code relies on the observed ordering
(callbacks only fire after probe() has set drvdata); the contract
should be made explicit.
- smbus_update_bits() is not atomic against concurrent callers; a lock
will be required before a charger IRQ handler is added (not yet
implemented).
- Only get_property and a handful of properties are wired up;
set_property, property_is_writeable and the IRQ-driven
power_supply_changed() notification are future work.
Feedback on the abstraction's shape -- especially the descriptor
lifetime and the drvdata recovery -- would be very welcome.
checkpatch emits one MAINTAINERS warning on patch 3/3 (new driver file);
it is a false positive -- the driver is covered by the existing POWER
SUPPLY CLASS "F: drivers/power/supply/" glob and needs no new entry.
Patch 2/3 adds the one file outside any existing glob
(rust/kernel/power_supply.rs) and updates MAINTAINERS accordingly.
Based on rust-next (v7.2-rc1).
Bruce Robertson (3):
rust: i2c: add SMBus byte transfer helpers
rust: power_supply: add power supply class abstraction
power: supply: add Rust SMB347 charger driver
MAINTAINERS | 1 +
drivers/power/supply/Kconfig | 14 ++
drivers/power/supply/Makefile | 1 +
drivers/power/supply/smb347-charger_rust.rs | 180 ++++++++++++++++++++
rust/bindings/bindings_helper.h | 1 +
rust/kernel/i2c.rs | 42 +++++
rust/kernel/lib.rs | 1 +
rust/kernel/power_supply.rs | 133 +++++++++++++++
8 files changed, 373 insertions(+)
create mode 100644 drivers/power/supply/smb347-charger_rust.rs
create mode 100644 rust/kernel/power_supply.rs
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [RFC PATCH 1/3] rust: i2c: add SMBus byte transfer helpers
2026-07-08 21:47 [RFC PATCH 0/3] rust: power_supply class abstraction and SMB347 charger driver Bruce Robertson
@ 2026-07-08 21:47 ` Bruce Robertson
2026-07-08 21:47 ` [RFC PATCH 2/3] rust: power_supply: add power supply class abstraction Bruce Robertson
2026-07-08 21:47 ` [RFC PATCH 3/3] power: supply: add Rust SMB347 charger driver Bruce Robertson
2 siblings, 0 replies; 5+ messages in thread
From: Bruce Robertson @ 2026-07-08 21:47 UTC (permalink / raw)
To: rust-for-linux, linux-pm, linux-i2c, linux-kernel
Cc: Sebastian Reichel, Miguel Ojeda, Igor Korotin, Gary Guo,
Tamir Duberstein, Alice Ryhl, Boqun Feng, Bruce Robertson
The Rust I2cClient abstraction provides device-model plumbing but no way
to perform register I/O. Add safe wrappers over the SMBus byte
primitives:
- smbus_read_byte_data() / smbus_write_byte_data() wrap the C
functions, converting the overloaded s32 return into a Result and
confining the unsafe FFI behind the type invariant of I2cClient;
- smbus_update_bits() composes them into a masked read-modify-write,
with the bit arithmetic factored into a pure, testable apply_bits().
smbus_update_bits() is not atomic against concurrent callers; this is
documented and sufficient for single-threaded probe-time use. A lock
will be required before a second accessor is introduced.
Signed-off-by: Bruce Robertson <brucer42@gmail.com>
---
rust/kernel/i2c.rs | 42 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 42 insertions(+)
diff --git a/rust/kernel/i2c.rs b/rust/kernel/i2c.rs
index 624b971ca8b0..07ce4299ff56 100644
--- a/rust/kernel/i2c.rs
+++ b/rust/kernel/i2c.rs
@@ -486,6 +486,48 @@ impl<Ctx: device::DeviceContext> I2cClient<Ctx> {
fn as_raw(&self) -> *mut bindings::i2c_client {
self.0.get()
}
+
+ /// Read a byte from the device register at `command` (SMBus read-byte-data).
+ pub fn smbus_read_byte_data(&self, command: u8) -> Result<u8> {
+ // SAFETY: `self.as_raw()` returns a valid pointer to a `struct i2c_client`
+ // by the type invariant of `I2cClient`.
+ let ret = unsafe { bindings::i2c_smbus_read_byte_data(self.as_raw(), command) };
+ if ret < 0 {
+ Err(Error::from_errno(ret))
+ } else {
+ Ok(ret as u8)
+ }
+ }
+
+ /// Write `value` to the device register at `command` (SMBus write-byte-data).
+ pub fn smbus_write_byte_data(&self, command: u8, value: u8) -> Result {
+ // SAFETY: `self.as_raw()` returns a valid pointer to a `struct i2c_client`
+ // by the type invariant of `I2cClient`.
+ to_result(unsafe { bindings::i2c_smbus_write_byte_data(self.as_raw(), command, value) })
+ }
+
+ /// Read-modify-write: set the bits selected by `mask` in the register at
+ /// `command` to `value`. Bits outside `mask` are preserved. Skips the write
+ /// if nothing changes.
+ ///
+ /// # Atomicity
+ ///
+ /// This read-modify-write is **not** atomic against concurrent callers. Two
+ /// callers updating disjoint fields of the same register can lose an update
+ /// (both read the old value; the second write clobbers the first). Rust's
+ /// safety guarantees cover memory, not device-register atomicity. Until this
+ /// is serialized by a lock (cf. `regmap`, which holds its own lock across the
+ /// RMW), callers must ensure mutual exclusion — currently safe only because
+ /// the single probe path is the sole accessor. FIXME: add a lock before the
+ /// threaded IRQ handler (Phase 4) introduces a second accessor.
+ pub fn smbus_update_bits(&self, command: u8, mask: u8, value: u8) -> Result {
+ let old = self.smbus_read_byte_data(command)?;
+ let new = (old & !mask) | (value & mask);
+ if new != old {
+ self.smbus_write_byte_data(command, new)?;
+ }
+ Ok(())
+ }
}
// SAFETY: `I2cClient` is a transparent wrapper of `struct i2c_client`.
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [RFC PATCH 2/3] rust: power_supply: add power supply class abstraction
2026-07-08 21:47 [RFC PATCH 0/3] rust: power_supply class abstraction and SMB347 charger driver Bruce Robertson
2026-07-08 21:47 ` [RFC PATCH 1/3] rust: i2c: add SMBus byte transfer helpers Bruce Robertson
@ 2026-07-08 21:47 ` Bruce Robertson
2026-07-09 9:04 ` Alice Ryhl
2026-07-08 21:47 ` [RFC PATCH 3/3] power: supply: add Rust SMB347 charger driver Bruce Robertson
2 siblings, 1 reply; 5+ messages in thread
From: Bruce Robertson @ 2026-07-08 21:47 UTC (permalink / raw)
To: rust-for-linux, linux-pm, linux-i2c, linux-kernel
Cc: Sebastian Reichel, Miguel Ojeda, Igor Korotin, Gary Guo,
Tamir Duberstein, Alice Ryhl, Boqun Feng, Bruce Robertson
There is no Rust abstraction for the power supply class in mainline or
rust-next. Add a minimal one:
- a Driver trait carrying the supply's name, type and property list,
plus a get_property() method;
- a generic extern "C" trampoline the power supply core calls back
into, recovering the driver's private data via the parent device and
dispatching to get_property();
- a Registration that owns the power_supply_desc and unregisters the
supply on drop, freeing the descriptor in the correct order.
Property, type and status constants are re-exported so drivers need not
reach into the generated bindings directly.
Signed-off-by: Bruce Robertson <brucer42@gmail.com>
---
MAINTAINERS | 1 +
rust/bindings/bindings_helper.h | 1 +
rust/kernel/lib.rs | 1 +
rust/kernel/power_supply.rs | 133 ++++++++++++++++++++++++++++++++
4 files changed, 136 insertions(+)
create mode 100644 rust/kernel/power_supply.rs
diff --git a/MAINTAINERS b/MAINTAINERS
index 15011f5752a9..8e41f7917825 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -21546,6 +21546,7 @@ F: Documentation/devicetree/bindings/power/supply/
F: drivers/power/supply/
F: include/linux/power/
F: include/linux/power_supply.h
+F: rust/kernel/power_supply.rs
F: tools/testing/selftests/power_supply/
POWERNV OPERATOR PANEL LCD DISPLAY DRIVER
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 1124785e210b..0bdaf7316140 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -78,6 +78,7 @@
#include <linux/platform_device.h>
#include <linux/pm_opp.h>
#include <linux/poll.h>
+#include <linux/power_supply.h>
#include <linux/property.h>
#include <linux/pwm.h>
#include <linux/random.h>
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 9512af7156df..5013bdd30361 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -106,6 +106,7 @@
pub mod pci;
pub mod pid_namespace;
pub mod platform;
+pub mod power_supply;
pub mod prelude;
pub mod print;
pub mod processor;
diff --git a/rust/kernel/power_supply.rs b/rust/kernel/power_supply.rs
new file mode 100644
index 000000000000..f466a89e7b3a
--- /dev/null
+++ b/rust/kernel/power_supply.rs
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Power supply class abstraction.
+//!
+//! C header: [`include/linux/power_supply.h`](srctree/include/linux/power_supply.h)
+
+use crate::{
+ bindings,
+ device::{self, Device},
+ error::*,
+ prelude::*,
+};
+
+/// A power-supply property id.
+pub use bindings::power_supply_property as Property;
+/// The value returned for a property query.
+pub use bindings::power_supply_propval as PropertyValue;
+/// A power-supply type.
+pub use bindings::power_supply_type as Type;
+
+/// `POWER_SUPPLY_PROP_STATUS`.
+pub const PROP_STATUS: Property = bindings::power_supply_property_POWER_SUPPLY_PROP_STATUS;
+/// `POWER_SUPPLY_PROP_ONLINE`.
+pub const PROP_ONLINE: Property = bindings::power_supply_property_POWER_SUPPLY_PROP_ONLINE;
+/// `POWER_SUPPLY_TYPE_MAINS`.
+pub const TYPE_MAINS: Type = bindings::power_supply_type_POWER_SUPPLY_TYPE_MAINS;
+/// `POWER_SUPPLY_STATUS_CHARGING`.
+pub const STATUS_CHARGING: i32 = bindings::POWER_SUPPLY_STATUS_CHARGING as i32;
+/// `POWER_SUPPLY_STATUS_NOT_CHARGING`.
+pub const STATUS_NOT_CHARGING: i32 = bindings::POWER_SUPPLY_STATUS_NOT_CHARGING as i32;
+/// `POWER_SUPPLY_PROP_CHARGE_TYPE`.
+pub const PROP_CHARGE_TYPE: Property =
+ bindings::power_supply_property_POWER_SUPPLY_PROP_CHARGE_TYPE;
+/// `POWER_SUPPLY_CHARGE_TYPE_NONE`.
+pub const CHARGE_TYPE_NONE: i32 =
+ bindings::power_supply_charge_type_POWER_SUPPLY_CHARGE_TYPE_NONE as i32;
+/// `POWER_SUPPLY_CHARGE_TYPE_TRICKLE`.
+pub const CHARGE_TYPE_TRICKLE: i32 =
+ bindings::power_supply_charge_type_POWER_SUPPLY_CHARGE_TYPE_TRICKLE as i32;
+/// `POWER_SUPPLY_CHARGE_TYPE_FAST`.
+pub const CHARGE_TYPE_FAST: i32 =
+ bindings::power_supply_charge_type_POWER_SUPPLY_CHARGE_TYPE_FAST as i32;
+
+/// A driver backing a power supply.
+pub trait Driver {
+ /// sysfs name: appears at `/sys/class/power_supply/<NAME>`.
+ const NAME: &'static CStr;
+
+ /// The power-supply type (mains, USB, battery, …).
+ const TYPE: Type;
+
+ /// The properties this supply can answer.
+ const PROPERTIES: &'static [Property];
+
+ /// Answer a single property query.
+ fn get_property(self: Pin<&Self>, psp: Property, val: &mut PropertyValue) -> Result;
+}
+
+/// `get_property` trampoline: the C power supply core calls this and it
+/// dispatches to [`Driver::get_property`].
+///
+/// # Safety
+///
+/// May only be installed as the `get_property` field of a `power_supply_desc`
+/// registered with `drv_data` set to the parent `*mut device`. The core
+/// guarantees `psy` and `val` are valid for the duration of the call.
+unsafe extern "C" fn get_property_trampoline<T: Driver + 'static>(
+ psy: *mut bindings::power_supply,
+ psp: Property,
+ val: *mut PropertyValue,
+) -> kernel::ffi::c_int {
+ // SAFETY: core passes a valid `psy`; we stored the parent device ptr as its drv_data.
+ let dev_ptr = unsafe { bindings::power_supply_get_drvdata(psy) }.cast::<bindings::device>();
+ // SAFETY: that device is valid while the (devm) supply lives.
+ let dev: &Device<device::CoreInternal<'_>> = unsafe { Device::from_raw(dev_ptr) };
+ // SAFETY: callbacks only fire after probe returned, so set_drvdata stored a `T`.
+ let data = unsafe { dev.drvdata_borrow::<T>() };
+ // SAFETY: core passes a valid `val` for the call.
+ let val = unsafe { &mut *val };
+ from_result(|| {
+ data.get_property(psp, val)?;
+ Ok(0)
+ })
+}
+
+/// A registered power supply. Unregisters and frees its descriptor on drop.
+pub struct Registration {
+ psy: *mut bindings::power_supply,
+ // Kept alive so the descriptor the core holds stays valid until unregister.
+ _desc: KBox<bindings::power_supply_desc>,
+}
+
+// SAFETY: `Registration` owns the `power_supply` and its descriptor; the raw
+// pointer is only used to unregister on drop. Safe to send/share across threads.
+unsafe impl Send for Registration {}
+// SAFETY: as above — psy and descriptor are only accessed on drop, so sharing
+// a `&Registration` across threads is sound.
+unsafe impl Sync for Registration {}
+
+impl Drop for Registration {
+ fn drop(&mut self) {
+ // SAFETY: `psy` came from a successful `power_supply_register` and has not been
+ // unregistered. `power_supply_unregister` blocks until no callback is running;
+ // `_desc` (dropped immediately after) is then no longer referenced by the core.
+ unsafe { bindings::power_supply_unregister(self.psy) };
+ }
+}
+
+/// Register a power supply backed by driver `T` against `dev`.
+pub fn register<T: Driver + 'static>(dev: &Device) -> Result<Registration> {
+ let mut desc = KBox::new(bindings::power_supply_desc::default(), GFP_KERNEL)?;
+ desc.name = T::NAME.as_char_ptr();
+ desc.type_ = T::TYPE;
+ desc.properties = T::PROPERTIES.as_ptr();
+ desc.num_properties = T::PROPERTIES.len();
+ desc.get_property = Some(get_property_trampoline::<T>);
+
+ // Stable heap address; moving the KBox into `Registration` keeps this valid
+ // (moving a KBox moves the pointer, not the heap allocation).
+ let desc_ptr: *const bindings::power_supply_desc = &*desc;
+
+ let cfg = bindings::power_supply_config {
+ drv_data: dev.as_raw().cast::<kernel::ffi::c_void>(),
+ ..Default::default()
+ };
+
+ // SAFETY: `dev` valid; `desc_ptr` points into `desc`, kept alive in the returned
+ // `Registration` and freed only after `power_supply_unregister`; `cfg` valid for the call.
+ let psy = unsafe { bindings::power_supply_register(dev.as_raw(), desc_ptr, &cfg) };
+ let psy = from_err_ptr(psy)?;
+
+ Ok(Registration { psy, _desc: desc })
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [RFC PATCH 3/3] power: supply: add Rust SMB347 charger driver
2026-07-08 21:47 [RFC PATCH 0/3] rust: power_supply class abstraction and SMB347 charger driver Bruce Robertson
2026-07-08 21:47 ` [RFC PATCH 1/3] rust: i2c: add SMBus byte transfer helpers Bruce Robertson
2026-07-08 21:47 ` [RFC PATCH 2/3] rust: power_supply: add power supply class abstraction Bruce Robertson
@ 2026-07-08 21:47 ` Bruce Robertson
2 siblings, 0 replies; 5+ messages in thread
From: Bruce Robertson @ 2026-07-08 21:47 UTC (permalink / raw)
To: rust-for-linux, linux-pm, linux-i2c, linux-kernel
Cc: Sebastian Reichel, Miguel Ojeda, Igor Korotin, Gary Guo,
Tamir Duberstein, Alice Ryhl, Boqun Feng, Bruce Robertson
Add a Rust port of the Summit SMB347 battery charger driver, built on
the Rust i2c and power_supply abstractions. The driver binds over I2C,
reads the charger's status registers, and registers a power supply that
reports STATUS, ONLINE and CHARGE_TYPE to sysfs.
It is the first in-tree consumer of the new power_supply abstraction.
Signed-off-by: Bruce Robertson <brucer42@gmail.com>
---
drivers/power/supply/Kconfig | 14 ++
drivers/power/supply/Makefile | 1 +
drivers/power/supply/smb347-charger_rust.rs | 180 ++++++++++++++++++++
3 files changed, 195 insertions(+)
create mode 100644 drivers/power/supply/smb347-charger_rust.rs
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index f0ede1cecb6a..b6521c86b47c 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -875,6 +875,20 @@ config CHARGER_SMB347
Say Y to include support for Summit Microelectronics SMB345,
SMB347 or SMB358 Battery Charger.
+config CHARGER_SMB347_RUST
+ tristate "Summit Microelectronics SMB3XX Battery Charger RUST"
+ depends on I2C
+ depends on RUST
+ help
+ Say Y to include support for the Summit Microelectronics SMB347
+ battery charger, implemented in Rust. The driver binds over I2C
+ and reports charging status, online state and charge type through
+ the power supply class. It is a Rust port demonstrating the
+ in-tree i2c and power_supply Rust abstractions.
+
+ To compile as a module, choose M here; the module will be called
+ smb347-charger_rust.
+
config CHARGER_TPS65090
tristate "TPS65090 battery charger driver"
depends on MFD_TPS65090
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 31fe4a145929..96616fb381a9 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -109,6 +109,7 @@ obj-$(CONFIG_CHARGER_BQ256XX) += bq256xx_charger.o
obj-$(CONFIG_CHARGER_RK817) += rk817_charger.o
obj-$(CONFIG_CHARGER_S2M) += s2m-charger.o
obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
+obj-$(CONFIG_CHARGER_SMB347_RUST) += smb347-charger_rust.o
obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o
obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o
obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o
diff --git a/drivers/power/supply/smb347-charger_rust.rs b/drivers/power/supply/smb347-charger_rust.rs
new file mode 100644
index 000000000000..c8286956a7a8
--- /dev/null
+++ b/drivers/power/supply/smb347-charger_rust.rs
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Rust SMB347 i2c driver
+
+use kernel::{
+ device::Core,
+ i2c,
+ of,
+ prelude::*, //
+ sync::aref::ARef,
+};
+
+/// SMB347 register map. Ported from drivers/power/supply/smb347-charger.c.
+#[allow(dead_code)]
+mod reg {
+ // Configuration registers (0x00–0x0e, write-protected; unlock via CMD_A bit 7)
+ pub(crate) const CFG_CHARGE_CURRENT: u8 = 0x00;
+ pub(crate) const CFG_CURRENT_LIMIT: u8 = 0x01;
+ pub(crate) const CFG_FLOAT_VOLTAGE: u8 = 0x03;
+ pub(crate) const CFG_STAT: u8 = 0x05;
+ pub(crate) const CFG_PIN: u8 = 0x06;
+ pub(crate) const CFG_THERM: u8 = 0x07;
+ pub(crate) const CFG_SYSOK: u8 = 0x08;
+ pub(crate) const CFG_OTHER: u8 = 0x09;
+ pub(crate) const CFG_OTG: u8 = 0x0a;
+ pub(crate) const CFG_TEMP_LIMIT: u8 = 0x0b;
+ pub(crate) const CFG_FAULT_IRQ: u8 = 0x0c;
+ pub(crate) const CFG_STATUS_IRQ: u8 = 0x0d;
+ pub(crate) const CFG_ADDRESS: u8 = 0x0e;
+
+ // Command registers
+ pub(crate) const CMD_A: u8 = 0x30;
+ pub(crate) const CMD_A_ALLOW_WRITE: u8 = 1 << 7;
+
+ pub(crate) const CMD_B: u8 = 0x31;
+ pub(crate) const CMD_C: u8 = 0x33;
+
+ // Interrupt-status registers
+ pub(crate) const IRQSTAT_A: u8 = 0x35;
+ pub(crate) const IRQSTAT_C: u8 = 0x37;
+ pub(crate) const IRQSTAT_D: u8 = 0x38;
+ pub(crate) const IRQSTAT_E: u8 = 0x39;
+ pub(crate) const IRQSTAT_E_DCIN_UV_STAT: u8 = 1 << 4;
+
+ pub(crate) const IRQSTAT_F: u8 = 0x3a;
+
+ // Status registers
+ pub(crate) const STAT_A: u8 = 0x3b;
+ pub(crate) const STAT_B: u8 = 0x3c;
+ pub(crate) const STAT_C: u8 = 0x3d;
+ pub(crate) const STAT_C_CHG_SHIFT: u8 = 1;
+ pub(crate) const STAT_C_CHG_MASK: u8 = 0x06;
+
+ pub(crate) const STAT_E: u8 = 0x3f;
+
+ pub(crate) const MAX_REGISTER: u8 = 0x3f;
+}
+
+// Declared before `client` so it drops first: the supply is
+// unregistered (no further get_property callbacks) before the I2C
+// client it reads through is released.
+struct Smb347 {
+ _registration: kernel::power_supply::Registration,
+ client: ARef<i2c::I2cClient>,
+}
+
+kernel::i2c_device_table! {
+ I2C_TABLE,
+ MODULE_I2C_TABLE,
+ <Smb347 as i2c::Driver>::IdInfo,
+ [(i2c::DeviceId::new(c"smb347"), 0)]
+}
+
+kernel::of_device_table! {
+ OF_TABLE,
+ MODULE_OF_TABLE,
+ <Smb347 as i2c::Driver>::IdInfo,
+ [(of::DeviceId::new(c"summit,smb347"), 0)]
+}
+
+impl Smb347 {
+ /// Enable/disable writes to the non-volatile config registers (0x00–0x0e)
+ /// by toggling CMD_A.ALLOW_WRITE. Other CMD_A bits are preserved.
+ #[expect(dead_code)] // TODO: remove once a config-write path calls this (Phase 3)
+ fn set_writable(idev: &i2c::I2cClient<Core<'_>>, writable: bool) -> Result {
+ let bits = if writable { reg::CMD_A_ALLOW_WRITE } else { 0 };
+ idev.smbus_update_bits(reg::CMD_A, reg::CMD_A_ALLOW_WRITE, bits)
+ }
+}
+
+impl kernel::power_supply::Driver for Smb347 {
+ const NAME: &'static CStr = c"smb347-mains";
+ const TYPE: kernel::power_supply::Type = kernel::power_supply::TYPE_MAINS;
+ const PROPERTIES: &'static [kernel::power_supply::Property] = &[
+ kernel::power_supply::PROP_STATUS,
+ kernel::power_supply::PROP_ONLINE,
+ kernel::power_supply::PROP_CHARGE_TYPE,
+ ];
+
+ fn get_property(
+ self: Pin<&Self>,
+ psp: kernel::power_supply::Property,
+ val: &mut kernel::power_supply::PropertyValue,
+ ) -> Result {
+ if psp == kernel::power_supply::PROP_ONLINE {
+ let irqstat_e = self.client.smbus_read_byte_data(reg::IRQSTAT_E)?;
+ val.intval = if irqstat_e & reg::IRQSTAT_E_DCIN_UV_STAT == 0 {
+ 1
+ } else {
+ 0
+ };
+ Ok(())
+ } else if psp == kernel::power_supply::PROP_STATUS {
+ let stat_c = self.client.smbus_read_byte_data(reg::STAT_C)?;
+ val.intval = if stat_c & reg::STAT_C_CHG_MASK != 0 {
+ kernel::power_supply::STATUS_CHARGING
+ } else {
+ kernel::power_supply::STATUS_NOT_CHARGING
+ };
+ Ok(())
+ } else if psp == kernel::power_supply::PROP_CHARGE_TYPE {
+ let stat_c = self.client.smbus_read_byte_data(reg::STAT_C)?;
+ let cs = (stat_c & reg::STAT_C_CHG_MASK) >> reg::STAT_C_CHG_SHIFT;
+ val.intval = match cs {
+ 1 => kernel::power_supply::CHARGE_TYPE_TRICKLE,
+ 2 => kernel::power_supply::CHARGE_TYPE_FAST,
+ _ => kernel::power_supply::CHARGE_TYPE_NONE,
+ };
+ Ok(())
+ } else {
+ Err(EINVAL)
+ }
+ }
+}
+
+impl i2c::Driver for Smb347 {
+ type IdInfo = u32;
+ type Data<'bound> = Self;
+
+ const I2C_ID_TABLE: Option<i2c::IdTable<Self::IdInfo>> = Some(&I2C_TABLE);
+ const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+
+ fn probe(
+ idev: &i2c::I2cClient<Core<'_>>,
+ info: Option<&Self::IdInfo>,
+ ) -> impl PinInit<Self, Error> {
+ let dev = idev.as_ref();
+
+ dev_info!(dev, "Probe Rust SMB347 driver.\n");
+
+ if let Some(info) = info {
+ dev_info!(dev, "Probed with info: '{}'.\n", info);
+ let v = idev.smbus_read_byte_data(reg::STAT_A)?;
+ dev_info!(dev, "STAT_A = {:#04x}\n", v);
+ }
+ let client: ARef<i2c::I2cClient> = idev.into();
+ let registration = kernel::power_supply::register::<Smb347>(dev)?;
+ dev_info!(dev, "registered power_supply\n");
+ Ok(Self {
+ _registration: registration,
+ client,
+ })
+ }
+
+ fn shutdown(idev: &i2c::I2cClient<Core<'_>>, _this: Pin<&Self>) {
+ dev_info!(idev.as_ref(), "Shutdown Rust SMB347 driver.\n");
+ }
+
+ fn unbind(idev: &i2c::I2cClient<Core<'_>>, _this: Pin<&Self>) {
+ dev_info!(idev.as_ref(), "Unbind Rust SMB347 driver.\n");
+ }
+}
+
+kernel::module_i2c_driver! {
+ type: Smb347,
+ name: "smb347_rust",
+ authors: ["Bruce Robertson"],
+ description: "Rust SMB347 driver",
+ license: "GPL v2",
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [RFC PATCH 2/3] rust: power_supply: add power supply class abstraction
2026-07-08 21:47 ` [RFC PATCH 2/3] rust: power_supply: add power supply class abstraction Bruce Robertson
@ 2026-07-09 9:04 ` Alice Ryhl
0 siblings, 0 replies; 5+ messages in thread
From: Alice Ryhl @ 2026-07-09 9:04 UTC (permalink / raw)
To: Bruce Robertson
Cc: rust-for-linux, linux-pm, linux-i2c, linux-kernel,
Sebastian Reichel, Miguel Ojeda, Igor Korotin, Gary Guo,
Tamir Duberstein, Boqun Feng
On Wed, Jul 08, 2026 at 09:47:37PM +0000, Bruce Robertson wrote:
> There is no Rust abstraction for the power supply class in mainline or
> rust-next. Add a minimal one:
>
> - a Driver trait carrying the supply's name, type and property list,
> plus a get_property() method;
> - a generic extern "C" trampoline the power supply core calls back
> into, recovering the driver's private data via the parent device and
> dispatching to get_property();
> - a Registration that owns the power_supply_desc and unregisters the
> supply on drop, freeing the descriptor in the correct order.
>
> Property, type and status constants are re-exported so drivers need not
> reach into the generated bindings directly.
>
> Signed-off-by: Bruce Robertson <brucer42@gmail.com>
> +impl Drop for Registration {
> + fn drop(&mut self) {
> + // SAFETY: `psy` came from a successful `power_supply_register` and has not been
> + // unregistered. `power_supply_unregister` blocks until no callback is running;
> + // `_desc` (dropped immediately after) is then no longer referenced by the core.
> + unsafe { bindings::power_supply_unregister(self.psy) };
> + }
> +}
> +
> +/// Register a power supply backed by driver `T` against `dev`.
> +pub fn register<T: Driver + 'static>(dev: &Device) -> Result<Registration> {
Should this be &Device<Bound>?
> + let mut desc = KBox::new(bindings::power_supply_desc::default(), GFP_KERNEL)?;
> + desc.name = T::NAME.as_char_ptr();
> + desc.type_ = T::TYPE;
> + desc.properties = T::PROPERTIES.as_ptr();
> + desc.num_properties = T::PROPERTIES.len();
> + desc.get_property = Some(get_property_trampoline::<T>);
> +
> + // Stable heap address; moving the KBox into `Registration` keeps this valid
> + // (moving a KBox moves the pointer, not the heap allocation).
> + let desc_ptr: *const bindings::power_supply_desc = &*desc;
> +
> + let cfg = bindings::power_supply_config {
> + drv_data: dev.as_raw().cast::<kernel::ffi::c_void>(),
> + ..Default::default()
> + };
> +
> + // SAFETY: `dev` valid; `desc_ptr` points into `desc`, kept alive in the returned
> + // `Registration` and freed only after `power_supply_unregister`; `cfg` valid for the call.
> + let psy = unsafe { bindings::power_supply_register(dev.as_raw(), desc_ptr, &cfg) };
> + let psy = from_err_ptr(psy)?;
> +
> + Ok(Registration { psy, _desc: desc })
> +}
This looks like it should be a ::new() method on Registration instead?
I would compare with existing Registration abstractions and mirror how
they do it.
Alice
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-07-09 9:04 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-08 21:47 [RFC PATCH 0/3] rust: power_supply class abstraction and SMB347 charger driver Bruce Robertson
2026-07-08 21:47 ` [RFC PATCH 1/3] rust: i2c: add SMBus byte transfer helpers Bruce Robertson
2026-07-08 21:47 ` [RFC PATCH 2/3] rust: power_supply: add power supply class abstraction Bruce Robertson
2026-07-09 9:04 ` Alice Ryhl
2026-07-08 21:47 ` [RFC PATCH 3/3] power: supply: add Rust SMB347 charger driver Bruce Robertson
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox