* [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* 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
* [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