From: Kwanghoon Son <asdfkwang@gmail.com>
To: "Rob Herring" <robh@kernel.org>,
"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
"Conor Dooley" <conor+dt@kernel.org>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>,
"Daniel Almeida" <daniel.almeida@collabora.com>,
"Tamir Duberstein" <tamird@kernel.org>,
"Alexandre Courbot" <acourbot@nvidia.com>,
"Onur Özkan" <work@onurozkan.dev>,
"Thomas Gleixner" <tglx@kernel.org>,
"Radu Rendec" <radu@rendec.net>, "Paul Walmsley" <pjw@kernel.org>,
"Samuel Holland" <samuel.holland@sifive.com>,
"Palmer Dabbelt" <palmer@dabbelt.com>,
"Daniel Lezcano" <daniel.lezcano@kernel.org>,
"Anup Patel" <anup@brainfault.org>,
"Kwanghoon Son" <asdfkwang@gmail.com>,
"Albert Ou" <aou@eecs.berkeley.edu>,
"Alexandre Ghiti" <alex@ghiti.fr>,
"Greg Kroah-Hartman" <gregkh@linuxfoundation.org>,
"Jiri Slaby" <jirislaby@kernel.org>
Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
rust-for-linux@vger.kernel.org,
Paul Walmsley <paul.walmsley@sifive.com>,
linux-riscv@lists.infradead.org, linux-serial@vger.kernel.org
Subject: [PATCH RFC 07/12] serial: bflb: Add Rust BL808 polling console
Date: Thu, 29 Oct 2026 01:05:48 -0400 [thread overview]
Message-ID: <20261029-rust-bl808-console-v1-7-b736ee920078@gmail.com> (raw)
In-Reply-To: <20261029-rust-bl808-console-v1-0-b736ee920078@gmail.com>
Add a Rust driver for the Bouffalo Lab BL808 UART controller with polling
console support. Register UART3 as ttyBFLB0 and provide bounded polling
and newline conversion.
The driver uses an exclusive MMIO mapping and a baud clock supplied by
the device tree. Firmware configures clock routing, gates, reset and
pinmux. Userspace TTY I/O, RX, interrupts and DMA are not implemented.
Signed-off-by: Kwanghoon Son <asdfkwang@gmail.com>
---
drivers/tty/serial/Kconfig | 8 +
drivers/tty/serial/Makefile | 2 +
drivers/tty/serial/bflb_uart.rs | 334 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 344 insertions(+)
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 9b17d451f19df6575bc8a4a08245065f9b8e3715..9f549c5033fc077e921f5b6f3e72bad2144f83b7 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -26,6 +26,14 @@ config RUST_SERIAL_ABSTRACTIONS
help
This enables Rust abstractions for the serial core API.
+config SERIAL_BFLB_RUST
+ bool "Bouffalo Lab polling serial console in Rust"
+ depends on RUST && OF && COMMON_CLK && HAS_IOMEM
+ select RUST_SERIAL_ABSTRACTIONS
+ help
+ Say Y here to enable kernel console support for the Bouffalo Lab
+ BL808 UART controller.
+
config SERIAL_AMBA_PL010
tristate "ARM AMBA PL010 serial port support"
depends on ARM_AMBA || COMPILE_TEST
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index d63b126f40caf55b44c9ac2e6a484a0c13ca1b57..36364fe714c85df5029f2ac95a027a3787f331c7 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -99,3 +99,5 @@ obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o
obj-$(CONFIG_KGDB_SERIAL_CONSOLE) += kgdboc.o
obj-$(CONFIG_SERIAL_NUVOTON_MA35D1) += ma35d1_serial.o
+
+obj-$(CONFIG_SERIAL_BFLB_RUST) += bflb_uart.o
diff --git a/drivers/tty/serial/bflb_uart.rs b/drivers/tty/serial/bflb_uart.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4e34aa9a95cab665e18230453af5706b0061049a
--- /dev/null
+++ b/drivers/tty/serial/bflb_uart.rs
@@ -0,0 +1,334 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+//! Bouffalo Lab UART3 polling console.
+
+use core::num::NonZeroU32;
+
+use kernel::{
+ clk::Clk,
+ device::{
+ Bound,
+ Core,
+ Device, //
+ },
+ devres::DevresGuard,
+ driver,
+ io::{
+ mem::{
+ DevresExclusiveIoMem,
+ ExclusiveIoMem, //
+ },
+ poll::read_poll_timeout_atomic,
+ Io,
+ IoBase, //
+ },
+ of,
+ platform,
+ prelude::*,
+ serial::{
+ self,
+ DriverConfig,
+ LockedPort,
+ Port,
+ Termios, //
+ },
+ sync::Arc,
+ time::Delta, //
+};
+
+const REG_SIZE: usize = 0x90;
+const FIFO_DEPTH: u32 = 32;
+const PORTS: u32 = 1;
+const DEFAULT_BAUD: u32 = 115200;
+const POLL_RETRIES: usize = 100_000;
+const MAX_BIT_PERIOD: u32 = 65536;
+const MAX_BAUD: u32 = 4000000;
+
+const UTX_CONFIG: usize = 0x00;
+const URX_CONFIG: usize = 0x04;
+const BIT_PRD: usize = 0x08;
+const DATA_CONFIG: usize = 0x0c;
+const SW_MODE: usize = 0x1c;
+const INT_MASK: usize = 0x24;
+const INT_EN: usize = 0x2c;
+const STATUS: usize = 0x30;
+const FIFO_CONFIG0: usize = 0x80;
+const FIFO_CONFIG1: usize = 0x84;
+const FIFO_WDATA: usize = 0x88;
+
+// BL808 UART register fields, named after the reference manual.
+const UTX_EN: u32 = 1 << 0;
+const UTX_FRM_EN: u32 = 1 << 2;
+const UTX_PRT_EN: u32 = 1 << 4;
+const UTX_PRT_SEL: u32 = 1 << 5;
+const UTX_BIT_CNT_D_SHIFT: u32 = 8;
+const UTX_BIT_CNT_P_SHIFT: u32 = 11;
+const UTX_STOP_ONE: u32 = 1 << UTX_BIT_CNT_P_SHIFT;
+const UTX_STOP_TWO: u32 = 3 << UTX_BIT_CNT_P_SHIFT;
+const UTX_BUS_BUSY: u32 = 1 << 0;
+const TX_FIFO_CNT_MASK: u32 = 0x3f;
+const URX_BIT_PRD_SHIFT: u32 = 16;
+
+kernel::sync::global_lock! {
+ // SAFETY: Initialized once in module init, before registering the platform driver.
+ unsafe(uninit) static SERIAL: Mutex<Option<Arc<serial::Driver<Hardware>>>> = None;
+}
+
+struct SerialOwner;
+
+impl SerialOwner {
+ fn new(module: &'static ThisModule) -> Result<Self> {
+ // SAFETY: Module init runs once per load, before any access to SERIAL.
+ unsafe { SERIAL.init() };
+ *SERIAL.lock() = Some(Arc::pin_init(
+ serial::Driver::new(
+ DriverConfig {
+ name: c"bflb_uart",
+ device_name: c"ttyBFLB",
+ ports: PORTS,
+ default_baud: DEFAULT_BAUD,
+ },
+ module,
+ ),
+ GFP_KERNEL,
+ )?);
+ Ok(Self)
+ }
+}
+
+impl Drop for SerialOwner {
+ fn drop(&mut self) {
+ SERIAL.lock().take();
+ }
+}
+
+#[pin_data]
+struct UartModule {
+ // Fields drop in declaration order: remove platform devices and drain their
+ // serial callbacks before releasing the module's serial-driver reference.
+ #[pin]
+ _platform: driver::Registration<platform::Adapter<Uart>>,
+ _serial: SerialOwner,
+}
+
+impl kernel::InPlaceModule for UartModule {
+ fn init(module: &'static ThisModule) -> impl PinInit<Self, Error> {
+ try_pin_init!(Self {
+ // Publish the serial driver before platform registration can probe.
+ _serial: SerialOwner::new(module)?,
+ _platform <- driver::Registration::new(c"bflb_uart", module),
+ })
+ }
+}
+
+module! {
+ type: UartModule,
+ name: "bflb_uart",
+ authors: ["Kwanghoon Son"],
+ description: "Bouffalo Lab polling serial console",
+ license: "GPL",
+}
+
+struct Uart;
+
+kernel::of_device_table!(OF_TABLE, (), [(of::DeviceId::new(c"bflb,uart"), ())]);
+
+impl Uart {
+ fn register(pdev: &platform::Device<Core<'_>>) -> Result {
+ let dev: &Device<Bound> = pdev.as_ref();
+ let mapbase = pdev.resource_by_index(0).ok_or(ENODEV)?.start();
+ let io = pdev
+ .io_request_by_index(0)
+ .ok_or(ENODEV)?
+ .iomap_exclusive_sized::<REG_SIZE>()?
+ .into_devres()?;
+ let baud = EnabledClock::get(dev, c"baud")?;
+ let clock = u32::try_from(baud.0.rate().as_hz())?;
+ if clock == 0 {
+ return Err(EINVAL);
+ }
+ let serial = SERIAL.lock().as_ref().ok_or(ENODEV)?.clone();
+ serial::register_port(
+ serial,
+ dev,
+ serial::PortConfig {
+ line: 0,
+ mapbase,
+ clock,
+ fifo_size: FIFO_DEPTH,
+ },
+ Ok(Hardware {
+ io,
+ clock,
+ _baud: baud,
+ }),
+ )?;
+ Ok(())
+ }
+}
+
+impl platform::Driver for Uart {
+ type IdInfo = ();
+ type Data<'bound> = ();
+ const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+
+ fn probe<'bound>(
+ pdev: &'bound platform::Device<Core<'_>>,
+ _info: Option<&'bound ()>,
+ ) -> impl PinInit<Self::Data<'bound>, Error> + 'bound {
+ Self::register(pdev)
+ }
+}
+
+struct EnabledClock(Clk);
+
+impl EnabledClock {
+ fn get(dev: &Device, name: &CStr) -> Result<Self> {
+ let clock = Clk::get(dev, Some(name))?;
+ clock.prepare_enable()?;
+ Ok(Self(clock))
+ }
+}
+
+impl Drop for EnabledClock {
+ fn drop(&mut self) {
+ self.0.disable_unprepare();
+ }
+}
+
+struct Hardware {
+ io: DevresExclusiveIoMem<REG_SIZE>,
+ clock: u32,
+ _baud: EnabledClock,
+}
+
+impl Hardware {
+ fn registers(&self, dev: &Device) -> Option<Registers<'_>> {
+ if !core::ptr::eq(self.io.device(), dev) {
+ return None;
+ }
+ Some(Registers {
+ io: self.io.try_access()?,
+ })
+ }
+}
+
+/// Keeps the mapping alive for the entire non-sleeping register operation.
+/// Unbind can revoke other resources before the serial port itself is removed.
+struct Registers<'a> {
+ io: DevresGuard<'a, ExclusiveIoMem<'static, REG_SIZE>>,
+}
+
+impl Registers<'_> {
+ fn read<const REG: usize>(&self) -> u32 {
+ (&*self.io).as_view().read32(REG)
+ }
+
+ fn write<const REG: usize>(&self, value: u32) {
+ (&*self.io).as_view().write32(value, REG);
+ }
+
+ fn wait_for(&self, ready: impl Fn() -> bool) -> Result {
+ read_poll_timeout_atomic(|| Ok(ready()), |ready| *ready, Delta::ZERO, POLL_RETRIES)?;
+ Ok(())
+ }
+
+ fn write_byte(&self, ch: u8) -> Result {
+ self.wait_for(|| self.read::<FIFO_CONFIG1>() & TX_FIFO_CNT_MASK != 0)?;
+ (&*self.io).as_view().write8(ch, FIFO_WDATA);
+ Ok(())
+ }
+
+ fn tx_empty(&self) -> bool {
+ self.read::<FIFO_CONFIG1>() & TX_FIFO_CNT_MASK == FIFO_DEPTH
+ && self.read::<STATUS>() & UTX_BUS_BUSY == 0
+ }
+
+ fn write_record(&self, bytes: &[u8]) -> Result {
+ for &ch in bytes {
+ if ch == b'\n' {
+ self.write_byte(b'\r')?;
+ }
+ self.write_byte(ch)?;
+ }
+ self.wait_for(|| self.tx_empty())
+ }
+
+ fn mask_interrupts(&self) -> u32 {
+ let saved = self.read::<INT_MASK>();
+ self.write::<INT_MASK>(u32::MAX);
+ saved
+ }
+
+ fn enable_tx(&self) {
+ self.write::<UTX_CONFIG>(self.read::<UTX_CONFIG>() | UTX_EN);
+ }
+
+ fn console_write(&self, bytes: &[u8]) {
+ let saved_mask = self.mask_interrupts();
+ self.enable_tx();
+ let _ = self.write_record(bytes);
+ self.write::<INT_MASK>(saved_mask);
+ }
+}
+
+impl serial::SerialOps for Hardware {
+ fn tx_empty(&self, port: &Port<'_>) -> bool {
+ let Some(registers) = self.registers(port.device()) else {
+ return false;
+ };
+ registers.tx_empty()
+ }
+
+ fn set_termios(&self, port: &LockedPort<'_>, new: &mut Termios, old: Option<&Termios>) {
+ let Some(registers) = self.registers(port.device()) else {
+ return;
+ };
+ new.disable_mark_space_parity();
+ new.disable_hardware_flow_control();
+ let mut tx = (new.char_size() - 1) << UTX_BIT_CNT_D_SHIFT;
+ if new.parity_enabled() {
+ tx |= UTX_PRT_EN;
+ if new.odd_parity() {
+ tx |= UTX_PRT_SEL;
+ }
+ }
+ tx |= if new.two_stop_bits() {
+ UTX_STOP_TWO
+ } else {
+ UTX_STOP_ONE
+ };
+ tx |= UTX_EN | UTX_FRM_EN;
+ let Some(baud) = port.baud_rate(
+ new,
+ old,
+ self.clock.div_ceil(MAX_BIT_PERIOD),
+ self.clock.min(MAX_BAUD),
+ ) else {
+ return;
+ };
+ let baud = baud.get();
+ let divisor = ((u64::from(self.clock) + u64::from(baud) / 2) / u64::from(baud))
+ .clamp(1, u64::from(MAX_BIT_PERIOD)) as u32;
+ let Some(actual_baud) = NonZeroU32::new(self.clock / divisor) else {
+ return;
+ };
+ let _ = registers.wait_for(|| registers.tx_empty());
+ registers.write::<INT_MASK>(u32::MAX);
+ registers.write::<INT_EN>(0);
+ registers.write::<FIFO_CONFIG0>(0);
+ registers.write::<URX_CONFIG>(0);
+ registers.write::<DATA_CONFIG>(0);
+ registers.write::<SW_MODE>(0);
+ registers.write::<BIT_PRD>((divisor - 1) | ((divisor - 1) << URX_BIT_PRD_SHIFT));
+ registers.write::<UTX_CONFIG>(tx);
+ port.configure(new, actual_baud);
+ }
+
+ fn console_write(&self, port: &Port<'_>, bytes: &[u8]) {
+ let Some(registers) = self.registers(port.device()) else {
+ return;
+ };
+ registers.console_write(bytes);
+ }
+}
--
2.47.3
WARNING: multiple messages have this Message-ID (diff)
From: Kwanghoon Son <asdfkwang@gmail.com>
To: "Rob Herring" <robh@kernel.org>,
"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
"Conor Dooley" <conor+dt@kernel.org>,
"Miguel Ojeda" <ojeda@kernel.org>,
"Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
"Benno Lossin" <lossin@kernel.org>,
"Andreas Hindborg" <a.hindborg@kernel.org>,
"Alice Ryhl" <aliceryhl@google.com>,
"Trevor Gross" <tmgross@umich.edu>,
"Danilo Krummrich" <dakr@kernel.org>,
"Daniel Almeida" <daniel.almeida@collabora.com>,
"Tamir Duberstein" <tamird@kernel.org>,
"Alexandre Courbot" <acourbot@nvidia.com>,
"Onur Özkan" <work@onurozkan.dev>,
"Thomas Gleixner" <tglx@kernel.org>,
"Radu Rendec" <radu@rendec.net>, "Paul Walmsley" <pjw@kernel.org>,
"Samuel Holland" <samuel.holland@sifive.com>,
"Palmer Dabbelt" <palmer@dabbelt.com>,
"Daniel Lezcano" <daniel.lezcano@kernel.org>,
"Anup Patel" <anup@brainfault.org>,
"Kwanghoon Son" <asdfkwang@gmail.com>,
"Albert Ou" <aou@eecs.berkeley.edu>,
"Alexandre Ghiti" <alex@ghiti.fr>,
"Greg Kroah-Hartman" <gregkh@linuxfoundation.org>,
"Jiri Slaby" <jirislaby@kernel.org>
Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
rust-for-linux@vger.kernel.org,
Paul Walmsley <paul.walmsley@sifive.com>,
linux-riscv@lists.infradead.org, linux-serial@vger.kernel.org
Subject: [PATCH RFC 07/12] serial: bflb: Add Rust BL808 polling console
Date: Thu, 29 Oct 2026 01:05:48 -0400 [thread overview]
Message-ID: <20261029-rust-bl808-console-v1-7-b736ee920078@gmail.com> (raw)
In-Reply-To: <20261029-rust-bl808-console-v1-0-b736ee920078@gmail.com>
Add a Rust driver for the Bouffalo Lab BL808 UART controller with polling
console support. Register UART3 as ttyBFLB0 and provide bounded polling
and newline conversion.
The driver uses an exclusive MMIO mapping and a baud clock supplied by
the device tree. Firmware configures clock routing, gates, reset and
pinmux. Userspace TTY I/O, RX, interrupts and DMA are not implemented.
Signed-off-by: Kwanghoon Son <asdfkwang@gmail.com>
---
drivers/tty/serial/Kconfig | 8 +
drivers/tty/serial/Makefile | 2 +
drivers/tty/serial/bflb_uart.rs | 334 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 344 insertions(+)
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 9b17d451f19df6575bc8a4a08245065f9b8e3715..9f549c5033fc077e921f5b6f3e72bad2144f83b7 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -26,6 +26,14 @@ config RUST_SERIAL_ABSTRACTIONS
help
This enables Rust abstractions for the serial core API.
+config SERIAL_BFLB_RUST
+ bool "Bouffalo Lab polling serial console in Rust"
+ depends on RUST && OF && COMMON_CLK && HAS_IOMEM
+ select RUST_SERIAL_ABSTRACTIONS
+ help
+ Say Y here to enable kernel console support for the Bouffalo Lab
+ BL808 UART controller.
+
config SERIAL_AMBA_PL010
tristate "ARM AMBA PL010 serial port support"
depends on ARM_AMBA || COMPILE_TEST
diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index d63b126f40caf55b44c9ac2e6a484a0c13ca1b57..36364fe714c85df5029f2ac95a027a3787f331c7 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -99,3 +99,5 @@ obj-$(CONFIG_SERIAL_MCTRL_GPIO) += serial_mctrl_gpio.o
obj-$(CONFIG_KGDB_SERIAL_CONSOLE) += kgdboc.o
obj-$(CONFIG_SERIAL_NUVOTON_MA35D1) += ma35d1_serial.o
+
+obj-$(CONFIG_SERIAL_BFLB_RUST) += bflb_uart.o
diff --git a/drivers/tty/serial/bflb_uart.rs b/drivers/tty/serial/bflb_uart.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4e34aa9a95cab665e18230453af5706b0061049a
--- /dev/null
+++ b/drivers/tty/serial/bflb_uart.rs
@@ -0,0 +1,334 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+//! Bouffalo Lab UART3 polling console.
+
+use core::num::NonZeroU32;
+
+use kernel::{
+ clk::Clk,
+ device::{
+ Bound,
+ Core,
+ Device, //
+ },
+ devres::DevresGuard,
+ driver,
+ io::{
+ mem::{
+ DevresExclusiveIoMem,
+ ExclusiveIoMem, //
+ },
+ poll::read_poll_timeout_atomic,
+ Io,
+ IoBase, //
+ },
+ of,
+ platform,
+ prelude::*,
+ serial::{
+ self,
+ DriverConfig,
+ LockedPort,
+ Port,
+ Termios, //
+ },
+ sync::Arc,
+ time::Delta, //
+};
+
+const REG_SIZE: usize = 0x90;
+const FIFO_DEPTH: u32 = 32;
+const PORTS: u32 = 1;
+const DEFAULT_BAUD: u32 = 115200;
+const POLL_RETRIES: usize = 100_000;
+const MAX_BIT_PERIOD: u32 = 65536;
+const MAX_BAUD: u32 = 4000000;
+
+const UTX_CONFIG: usize = 0x00;
+const URX_CONFIG: usize = 0x04;
+const BIT_PRD: usize = 0x08;
+const DATA_CONFIG: usize = 0x0c;
+const SW_MODE: usize = 0x1c;
+const INT_MASK: usize = 0x24;
+const INT_EN: usize = 0x2c;
+const STATUS: usize = 0x30;
+const FIFO_CONFIG0: usize = 0x80;
+const FIFO_CONFIG1: usize = 0x84;
+const FIFO_WDATA: usize = 0x88;
+
+// BL808 UART register fields, named after the reference manual.
+const UTX_EN: u32 = 1 << 0;
+const UTX_FRM_EN: u32 = 1 << 2;
+const UTX_PRT_EN: u32 = 1 << 4;
+const UTX_PRT_SEL: u32 = 1 << 5;
+const UTX_BIT_CNT_D_SHIFT: u32 = 8;
+const UTX_BIT_CNT_P_SHIFT: u32 = 11;
+const UTX_STOP_ONE: u32 = 1 << UTX_BIT_CNT_P_SHIFT;
+const UTX_STOP_TWO: u32 = 3 << UTX_BIT_CNT_P_SHIFT;
+const UTX_BUS_BUSY: u32 = 1 << 0;
+const TX_FIFO_CNT_MASK: u32 = 0x3f;
+const URX_BIT_PRD_SHIFT: u32 = 16;
+
+kernel::sync::global_lock! {
+ // SAFETY: Initialized once in module init, before registering the platform driver.
+ unsafe(uninit) static SERIAL: Mutex<Option<Arc<serial::Driver<Hardware>>>> = None;
+}
+
+struct SerialOwner;
+
+impl SerialOwner {
+ fn new(module: &'static ThisModule) -> Result<Self> {
+ // SAFETY: Module init runs once per load, before any access to SERIAL.
+ unsafe { SERIAL.init() };
+ *SERIAL.lock() = Some(Arc::pin_init(
+ serial::Driver::new(
+ DriverConfig {
+ name: c"bflb_uart",
+ device_name: c"ttyBFLB",
+ ports: PORTS,
+ default_baud: DEFAULT_BAUD,
+ },
+ module,
+ ),
+ GFP_KERNEL,
+ )?);
+ Ok(Self)
+ }
+}
+
+impl Drop for SerialOwner {
+ fn drop(&mut self) {
+ SERIAL.lock().take();
+ }
+}
+
+#[pin_data]
+struct UartModule {
+ // Fields drop in declaration order: remove platform devices and drain their
+ // serial callbacks before releasing the module's serial-driver reference.
+ #[pin]
+ _platform: driver::Registration<platform::Adapter<Uart>>,
+ _serial: SerialOwner,
+}
+
+impl kernel::InPlaceModule for UartModule {
+ fn init(module: &'static ThisModule) -> impl PinInit<Self, Error> {
+ try_pin_init!(Self {
+ // Publish the serial driver before platform registration can probe.
+ _serial: SerialOwner::new(module)?,
+ _platform <- driver::Registration::new(c"bflb_uart", module),
+ })
+ }
+}
+
+module! {
+ type: UartModule,
+ name: "bflb_uart",
+ authors: ["Kwanghoon Son"],
+ description: "Bouffalo Lab polling serial console",
+ license: "GPL",
+}
+
+struct Uart;
+
+kernel::of_device_table!(OF_TABLE, (), [(of::DeviceId::new(c"bflb,uart"), ())]);
+
+impl Uart {
+ fn register(pdev: &platform::Device<Core<'_>>) -> Result {
+ let dev: &Device<Bound> = pdev.as_ref();
+ let mapbase = pdev.resource_by_index(0).ok_or(ENODEV)?.start();
+ let io = pdev
+ .io_request_by_index(0)
+ .ok_or(ENODEV)?
+ .iomap_exclusive_sized::<REG_SIZE>()?
+ .into_devres()?;
+ let baud = EnabledClock::get(dev, c"baud")?;
+ let clock = u32::try_from(baud.0.rate().as_hz())?;
+ if clock == 0 {
+ return Err(EINVAL);
+ }
+ let serial = SERIAL.lock().as_ref().ok_or(ENODEV)?.clone();
+ serial::register_port(
+ serial,
+ dev,
+ serial::PortConfig {
+ line: 0,
+ mapbase,
+ clock,
+ fifo_size: FIFO_DEPTH,
+ },
+ Ok(Hardware {
+ io,
+ clock,
+ _baud: baud,
+ }),
+ )?;
+ Ok(())
+ }
+}
+
+impl platform::Driver for Uart {
+ type IdInfo = ();
+ type Data<'bound> = ();
+ const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
+
+ fn probe<'bound>(
+ pdev: &'bound platform::Device<Core<'_>>,
+ _info: Option<&'bound ()>,
+ ) -> impl PinInit<Self::Data<'bound>, Error> + 'bound {
+ Self::register(pdev)
+ }
+}
+
+struct EnabledClock(Clk);
+
+impl EnabledClock {
+ fn get(dev: &Device, name: &CStr) -> Result<Self> {
+ let clock = Clk::get(dev, Some(name))?;
+ clock.prepare_enable()?;
+ Ok(Self(clock))
+ }
+}
+
+impl Drop for EnabledClock {
+ fn drop(&mut self) {
+ self.0.disable_unprepare();
+ }
+}
+
+struct Hardware {
+ io: DevresExclusiveIoMem<REG_SIZE>,
+ clock: u32,
+ _baud: EnabledClock,
+}
+
+impl Hardware {
+ fn registers(&self, dev: &Device) -> Option<Registers<'_>> {
+ if !core::ptr::eq(self.io.device(), dev) {
+ return None;
+ }
+ Some(Registers {
+ io: self.io.try_access()?,
+ })
+ }
+}
+
+/// Keeps the mapping alive for the entire non-sleeping register operation.
+/// Unbind can revoke other resources before the serial port itself is removed.
+struct Registers<'a> {
+ io: DevresGuard<'a, ExclusiveIoMem<'static, REG_SIZE>>,
+}
+
+impl Registers<'_> {
+ fn read<const REG: usize>(&self) -> u32 {
+ (&*self.io).as_view().read32(REG)
+ }
+
+ fn write<const REG: usize>(&self, value: u32) {
+ (&*self.io).as_view().write32(value, REG);
+ }
+
+ fn wait_for(&self, ready: impl Fn() -> bool) -> Result {
+ read_poll_timeout_atomic(|| Ok(ready()), |ready| *ready, Delta::ZERO, POLL_RETRIES)?;
+ Ok(())
+ }
+
+ fn write_byte(&self, ch: u8) -> Result {
+ self.wait_for(|| self.read::<FIFO_CONFIG1>() & TX_FIFO_CNT_MASK != 0)?;
+ (&*self.io).as_view().write8(ch, FIFO_WDATA);
+ Ok(())
+ }
+
+ fn tx_empty(&self) -> bool {
+ self.read::<FIFO_CONFIG1>() & TX_FIFO_CNT_MASK == FIFO_DEPTH
+ && self.read::<STATUS>() & UTX_BUS_BUSY == 0
+ }
+
+ fn write_record(&self, bytes: &[u8]) -> Result {
+ for &ch in bytes {
+ if ch == b'\n' {
+ self.write_byte(b'\r')?;
+ }
+ self.write_byte(ch)?;
+ }
+ self.wait_for(|| self.tx_empty())
+ }
+
+ fn mask_interrupts(&self) -> u32 {
+ let saved = self.read::<INT_MASK>();
+ self.write::<INT_MASK>(u32::MAX);
+ saved
+ }
+
+ fn enable_tx(&self) {
+ self.write::<UTX_CONFIG>(self.read::<UTX_CONFIG>() | UTX_EN);
+ }
+
+ fn console_write(&self, bytes: &[u8]) {
+ let saved_mask = self.mask_interrupts();
+ self.enable_tx();
+ let _ = self.write_record(bytes);
+ self.write::<INT_MASK>(saved_mask);
+ }
+}
+
+impl serial::SerialOps for Hardware {
+ fn tx_empty(&self, port: &Port<'_>) -> bool {
+ let Some(registers) = self.registers(port.device()) else {
+ return false;
+ };
+ registers.tx_empty()
+ }
+
+ fn set_termios(&self, port: &LockedPort<'_>, new: &mut Termios, old: Option<&Termios>) {
+ let Some(registers) = self.registers(port.device()) else {
+ return;
+ };
+ new.disable_mark_space_parity();
+ new.disable_hardware_flow_control();
+ let mut tx = (new.char_size() - 1) << UTX_BIT_CNT_D_SHIFT;
+ if new.parity_enabled() {
+ tx |= UTX_PRT_EN;
+ if new.odd_parity() {
+ tx |= UTX_PRT_SEL;
+ }
+ }
+ tx |= if new.two_stop_bits() {
+ UTX_STOP_TWO
+ } else {
+ UTX_STOP_ONE
+ };
+ tx |= UTX_EN | UTX_FRM_EN;
+ let Some(baud) = port.baud_rate(
+ new,
+ old,
+ self.clock.div_ceil(MAX_BIT_PERIOD),
+ self.clock.min(MAX_BAUD),
+ ) else {
+ return;
+ };
+ let baud = baud.get();
+ let divisor = ((u64::from(self.clock) + u64::from(baud) / 2) / u64::from(baud))
+ .clamp(1, u64::from(MAX_BIT_PERIOD)) as u32;
+ let Some(actual_baud) = NonZeroU32::new(self.clock / divisor) else {
+ return;
+ };
+ let _ = registers.wait_for(|| registers.tx_empty());
+ registers.write::<INT_MASK>(u32::MAX);
+ registers.write::<INT_EN>(0);
+ registers.write::<FIFO_CONFIG0>(0);
+ registers.write::<URX_CONFIG>(0);
+ registers.write::<DATA_CONFIG>(0);
+ registers.write::<SW_MODE>(0);
+ registers.write::<BIT_PRD>((divisor - 1) | ((divisor - 1) << URX_BIT_PRD_SHIFT));
+ registers.write::<UTX_CONFIG>(tx);
+ port.configure(new, actual_baud);
+ }
+
+ fn console_write(&self, port: &Port<'_>, bytes: &[u8]) {
+ let Some(registers) = self.registers(port.device()) else {
+ return;
+ };
+ registers.console_write(bytes);
+ }
+}
--
2.47.3
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
next prev parent reply other threads:[~2026-10-29 5:06 UTC|newest]
Thread overview: 37+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-29 5:05 [PATCH RFC 00/12] Add BL808 UART3 support with a Rust serial-core polling console Kwanghoon Son
2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:05 ` [PATCH RFC 01/12] dt-bindings: vendor-prefixes: Add Bouffalo Lab Kwanghoon Son
2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:10 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 02/12] dt-bindings: interrupt-controller: Add BL808 PLIC Kwanghoon Son
2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:11 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 03/12] dt-bindings: timer: Add BL808 CLINT Kwanghoon Son
2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:12 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 04/12] dt-bindings: riscv: Add Bouffalo Lab BL808 Kwanghoon Son
2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:11 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 05/12] dt-bindings: serial: Add BL808 UART Kwanghoon Son
2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:15 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 06/12] rust: serial: Add minimal serial-core polling console support Kwanghoon Son
2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:37 ` sashiko-bot
2026-10-29 5:05 ` Kwanghoon Son [this message]
2026-10-29 5:05 ` [PATCH RFC 07/12] serial: bflb: Add Rust BL808 polling console Kwanghoon Son
2026-10-29 5:16 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 08/12] riscv: Add Bouffalo Lab SoC selection Kwanghoon Son
2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:14 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 09/12] riscv: dts: bouffalolab: Add BL808 SoC Kwanghoon Son
2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:12 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 10/12] riscv: dts: bouffalolab: Add Sipeed M1s Dock Kwanghoon Son
2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:10 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 11/12] riscv: defconfig: Enable Bouffalo Lab SoCs Kwanghoon Son
2026-10-29 5:05 ` Kwanghoon Son
2026-10-29 5:13 ` sashiko-bot
2026-10-29 5:05 ` [PATCH RFC 12/12] MAINTAINERS: Add Bouffalo Lab BL808 support Kwanghoon Son
2026-10-29 5:05 ` Kwanghoon Son
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=20261029-rust-bl808-console-v1-7-b736ee920078@gmail.com \
--to=asdfkwang@gmail.com \
--cc=a.hindborg@kernel.org \
--cc=acourbot@nvidia.com \
--cc=alex@ghiti.fr \
--cc=aliceryhl@google.com \
--cc=anup@brainfault.org \
--cc=aou@eecs.berkeley.edu \
--cc=bjorn3_gh@protonmail.com \
--cc=boqun@kernel.org \
--cc=conor+dt@kernel.org \
--cc=dakr@kernel.org \
--cc=daniel.almeida@collabora.com \
--cc=daniel.lezcano@kernel.org \
--cc=devicetree@vger.kernel.org \
--cc=gary@garyguo.net \
--cc=gregkh@linuxfoundation.org \
--cc=jirislaby@kernel.org \
--cc=krzk+dt@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-riscv@lists.infradead.org \
--cc=linux-serial@vger.kernel.org \
--cc=lossin@kernel.org \
--cc=ojeda@kernel.org \
--cc=palmer@dabbelt.com \
--cc=paul.walmsley@sifive.com \
--cc=pjw@kernel.org \
--cc=radu@rendec.net \
--cc=robh@kernel.org \
--cc=rust-for-linux@vger.kernel.org \
--cc=samuel.holland@sifive.com \
--cc=tamird@kernel.org \
--cc=tglx@kernel.org \
--cc=tmgross@umich.edu \
--cc=work@onurozkan.dev \
/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.