From: Bruce Robertson <brucer42@gmail.com>
To: rust-for-linux@vger.kernel.org, linux-pm@vger.kernel.org,
linux-i2c@vger.kernel.org, linux-kernel@vger.kernel.org
Cc: Sebastian Reichel <sre@kernel.org>,
Miguel Ojeda <ojeda@kernel.org>,
Igor Korotin <igor.korotin@linux.dev>,
Gary Guo <gary@garyguo.net>, Tamir Duberstein <tamird@kernel.org>,
Alice Ryhl <aliceryhl@google.com>, Boqun Feng <boqun@kernel.org>,
Bruce Robertson <brucer42@gmail.com>
Subject: [RFC PATCH 3/3] power: supply: add Rust SMB347 charger driver
Date: Wed, 8 Jul 2026 21:47:38 +0000 [thread overview]
Message-ID: <20260708214738.25008-4-brucer42@gmail.com> (raw)
In-Reply-To: <20260708214738.25008-1-brucer42@gmail.com>
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
prev parent reply other threads:[~2026-07-08 21:48 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
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-11 10:34 ` Igor Korotin
2026-07-11 12:06 ` Danilo Krummrich
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 ` Bruce Robertson [this message]
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=20260708214738.25008-4-brucer42@gmail.com \
--to=brucer42@gmail.com \
--cc=aliceryhl@google.com \
--cc=boqun@kernel.org \
--cc=gary@garyguo.net \
--cc=igor.korotin@linux.dev \
--cc=linux-i2c@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pm@vger.kernel.org \
--cc=ojeda@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=sre@kernel.org \
--cc=tamird@kernel.org \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.