From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=74.125.227.133 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1793250415; b=gcUVGZtrNN4lwqal9Wj/Yg6gXdc3mkVaIas+n/6PAucuAp+PjcvQ+1qKsYHTGXhqPo7V /QHO4ejbQTnuxOJhaqQu0r/h6yZcDNyiMAqk5jodKXu+ywCJI/xMGoqNrtxgjgM80tbCS uh0+bjog9lBau4fZNFfdHGvpKp2AUuiT8FuVx3zu2ErjWbq/8VGKKMuhtfC/yf7OFdEHN dIIewcjXfLHnTWSW9TaCo0X9KHfP9htjW02IlT2TMVBWbbYVZYXsZ7UKiXYXriiu5yuen xL4QfgJIgaje9YcvQXjYzgA6Hc2MbmtXVnKJe5bNTJDPNl3ef/mF96Ubt0/dTk2lhkQ== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1793250415; h=DMARC-Filter:Received:DKIM-Signature:X-Google-DKIM-Signature: X-Forwarded-Encrypted:X-Gm-Message-State:X-Gm-Gg:X-Received:Received: From:Date:Subject:MIME-Version:Content-Type:Content-Transfer-Encoding: Message-Id:References:In-Reply-To:To:Cc:X-Mailer; bh=P1HpC8OctDgrucLKXFCuwI9p/x3lA/iI3ZaNcaH9r4Q=; b=Mae3zgTpbjMphab25T2+gMRN9x760ViSHIH0xcuNqYyCpjzkuVucpWJC4xg/blcMDHQg 5mNN6iYiVP3S1M5PCvCaVVZvZ8znkjY2UzxFk1+mdol60mMUJkS2hxyRV+edzF9bBIJt0 QQNZRJPiyr2a2t5FHSzykAM3RTUWe939QUPZ3MW+sRVn3fAv4jVKl3BlvnLdDZent/Knt dTWZDLxStoiQ3dVOLboWZnSlF9lNcZkwjeDm4bxV530peR7U5Xu6/shWLvlHRFb/+UhnB /Q0/o6vRLoBN2G+xsAUxZP4NufJeWE7QKNLNPtxIoltsfG55k3zH7ecbU3rbgUTNA9w== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=Z6W48XXH; arc=none smtp.remote-ip=74.125.227.133 Received: from mail-pj2-f5.google.com (mail-pj2-f5.google.com [74.125.227.133]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature ECDSA (prime256v1) server-digest SHA256) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D95BE606A31D for ; Thu, 29 Oct 2026 05:06:54 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.4.2 smtp.subspace.kernel.org D95BE606A31D Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20260707 header.b=Z6W48XXH Received: by mail-pj2-f5.google.com with SMTP id d9443c01a7336-2eb0981df4aso2462495ad.1 for ; Wed, 28 Oct 2026 22:06:54 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20260707; t=1793250414; x=1793855214; darn=vger.kernel.org; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=P1HpC8OctDgrucLKXFCuwI9p/x3lA/iI3ZaNcaH9r4Q=; b=Z6W48XXHoO9UsrPsmk143+ho2vWLYo/IKtMtiGauJTH2i+hXIRpZ4d0GS+YhVYvtU2 0cGgxBLn97uVls7x5I6dis1fcVkOgKqyDgDbTnWRAJ5X5fyjNF1Tb8gk3U+yHDzLEvo/ iBOeOv6RGgMmgHgFEnuc7zFqDbHLjh/a2I7Gq+u8cCAGcg2mv02nXLg4TgBaSleutauP DVNbZaJrYskygNvADx8Cct8txdezYdKJjVYi65RLFrzi/ga4/IhzuJmUAy0fCPYOLS/K b2RB1B8h6fjZZUCUWW8XWmS3zuvmCrWBRJzk/e/xmW6cVvTZWpcFs5RUwReVH0JEMrWA cbEw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1793250414; x=1793855214; h=cc:to:in-reply-to:references:message-id:content-transfer-encoding :content-type:mime-version:subject:date:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=P1HpC8OctDgrucLKXFCuwI9p/x3lA/iI3ZaNcaH9r4Q=; b=dkRzqjp2MulzIK9ygZLBF4ZuWApYJuz/lkgOhY/RXaAoM6O1qKXra7hDvz/0+hZU6Y FoJzx2NMY3cnob3iiY8pMABanwUxScHaov7HVb/PfpU/9/OMRUNGadNh/4HgtoB0vvb2 wXUz+UHOra+ijU3Q96bP3GRCX+5ZE4o82Bvgu1rU3LFhT6fOTnEvsRtVCDd6pWSYjSm9 u2sJcK0n/E1Njoo45m0YkEW98sXsfz+TwLFrmKn5fpYJlfu/bQGLoci7qf4rFxNiGi/P p5l269E/fOeP0GpNdDByWMdjOOoIZnQWvsOIFOhEVXwyGRkMVKRQ9HHl8hvgqDHrJzbO B8EA== X-Forwarded-Encrypted: i=1; AKwUvBx1b4PyLhlUIRumJG0nGagcVRU1mSqjw/77ZyDn9+9vBEagO7S5klUtrrpz4tuCivOrOzm/QPzCkdc54Yyybg==@vger.kernel.org X-Gm-Message-State: AFq9FYLunXDJ2MZRNibUxwPKVFvWNgCJDFSNzZymUNb+ikIDMJ4njV+t tLOTk3+IJe75SMFTYcshLGXa7J/XwAydPVQedh1VAt5jS73TYcLz8lZmz6ahiZkT X-Gm-Gg: AYBFou0s2uO2l8G+UixPRlKA4+BZoabx+ggO5Bw0pg71uguQFIT9ndTHuCIEXnLmX8v 4ddfnzmB5T6ckent4NM+FGphWhoRn99gaHvD/TlHZxHbW4o7wQa/opJNmVTdEktJ4Y6OVADizs9 43ii9z/mIgF5NMNR23xZktbprr+leGC6F6iKuLeaXvq5J10z7oaVQy4uTqGFjlnLNyRl9SsByga fdnm3KV1u2MqRHhN44OwQ8kfekHYpouczBJJzOdcm6jOxunocxNdaWBUX3j7vrAFajxK/6qgemK izvdMT4tVhG2F3zaBBzY4luaHZlyPRktYtz5K9cVRW3AKCzhd+EO37oROzQdEVxNEZt+79gqAkc 6N6x07vjchNt3ID4BJ+Bz7d73kQlQj7gajJv23i8MptcyGFladNuWvaIz4YuiHh3oJtnOfHC2kk Xi3jssr2/3PFuryHAXsv/YloYm8RRzxRswPlTcq/TTWf+UQ6tfXZK2MtUUuU9oMgBxhyVGNyEdv HXK0hteRRe9/JWmxuwQUaz+arnScTCPTDz9G53lIs71znpRqITRBYI5iVxCpnXqPwPDrpormqCg MUzS7AWP7IFSBgpDa8/sQh+KsYa9863PJjVcmZE2l/ojuW0pyg== X-Received: by 2002:a17:902:e848:b0:2e6:943:39df with SMTP id d9443c01a7336-2eb00488b94mr42201205ad.25.1793250414083; Wed, 28 Oct 2026 22:06:54 -0700 (PDT) Received: from [127.0.1.1] ([169.211.140.146]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2eae6c9d2cesm28159685ad.65.2026.10.28.22.06.45 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 28 Oct 2026 22:06:52 -0700 (PDT) From: Kwanghoon Son Date: Thu, 29 Oct 2026 01:05:48 -0400 Subject: [PATCH RFC 07/12] serial: bflb: Add Rust BL808 polling console X-Mailing-List: rust-for-linux@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20261029-rust-bl808-console-v1-7-b736ee920078@gmail.com> References: <20261029-rust-bl808-console-v1-0-b736ee920078@gmail.com> In-Reply-To: <20261029-rust-bl808-console-v1-0-b736ee920078@gmail.com> To: Rob Herring , Krzysztof Kozlowski , Conor Dooley , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= , Thomas Gleixner , Radu Rendec , Paul Walmsley , Samuel Holland , Palmer Dabbelt , Daniel Lezcano , Anup Patel , Kwanghoon Son , Albert Ou , Alexandre Ghiti , Greg Kroah-Hartman , Jiri Slaby Cc: devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, Paul Walmsley , linux-riscv@lists.infradead.org, linux-serial@vger.kernel.org X-Mailer: b4 0.14.2 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 --- 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>>> = None; +} + +struct SerialOwner; + +impl SerialOwner { + fn new(module: &'static ThisModule) -> Result { + // 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>, + _serial: SerialOwner, +} + +impl kernel::InPlaceModule for UartModule { + fn init(module: &'static ThisModule) -> impl PinInit { + 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>) -> Result { + let dev: &Device = 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::()? + .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> = Some(&OF_TABLE); + + fn probe<'bound>( + pdev: &'bound platform::Device>, + _info: Option<&'bound ()>, + ) -> impl PinInit, Error> + 'bound { + Self::register(pdev) + } +} + +struct EnabledClock(Clk); + +impl EnabledClock { + fn get(dev: &Device, name: &CStr) -> Result { + 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, + clock: u32, + _baud: EnabledClock, +} + +impl Hardware { + fn registers(&self, dev: &Device) -> Option> { + 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(&self) -> u32 { + (&*self.io).as_view().read32(REG) + } + + fn write(&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::() & TX_FIFO_CNT_MASK != 0)?; + (&*self.io).as_view().write8(ch, FIFO_WDATA); + Ok(()) + } + + fn tx_empty(&self) -> bool { + self.read::() & TX_FIFO_CNT_MASK == FIFO_DEPTH + && self.read::() & 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::(); + self.write::(u32::MAX); + saved + } + + fn enable_tx(&self) { + self.write::(self.read::() | UTX_EN); + } + + fn console_write(&self, bytes: &[u8]) { + let saved_mask = self.mask_interrupts(); + self.enable_tx(); + let _ = self.write_record(bytes); + self.write::(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::(u32::MAX); + registers.write::(0); + registers.write::(0); + registers.write::(0); + registers.write::(0); + registers.write::(0); + registers.write::((divisor - 1) | ((divisor - 1) << URX_BIT_PRD_SHIFT)); + registers.write::(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