The Linux Kernel Mailing List
 help / color / mirror / Atom feed
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


      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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox