From 06a6e11dabfee45a50cf8c59fa755a482edafbf4 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Wed, 30 Jul 2025 20:43:12 +0300 Subject: [PATCH] pinctrl: basic pinctrl/pinmux support --- doc/raspi4b.txt | 53 +++ doc/visionfive2.txt | 91 +++++ kernel/lib/device-tree/src/driver/mod.rs | 4 +- kernel/lib/device-tree/src/driver/traits.rs | 7 + kernel/lib/device-tree/src/driver/tree.rs | 51 ++- kernel/lib/device-tree/src/driver/util.rs | 77 +++++ kernel/lib/device-tree/src/tree.rs | 40 +-- kernel/src/device/pinctrl/bcm2711_gpio.rs | 196 +++++++++++ kernel/src/device/pinctrl/jh7110_pinctrl.rs | 365 ++++++++++++++++++-- kernel/src/device/pinctrl/mod.rs | 4 + kernel/src/device/pinctrl/pl061.rs | 94 +++++ 11 files changed, 932 insertions(+), 50 deletions(-) create mode 100644 kernel/src/device/pinctrl/bcm2711_gpio.rs create mode 100644 kernel/src/device/pinctrl/pl061.rs diff --git a/doc/raspi4b.txt b/doc/raspi4b.txt index 9333769c..5bd1fecf 100644 --- a/doc/raspi4b.txt +++ b/doc/raspi4b.txt @@ -43,3 +43,56 @@ $ booti ${loadaddr} ${initrd_addr_r}: ${fdt_ad ###### (FIXME when initrd gets larger than 64MiB) env set ipaddr 13.0.0.2; env set fdt_addr_r 0x11000000; env set initrd_addr_r 0x04000000; tftpboot ${initrd_addr_r} 13.0.0.1:initrd.tar; tftpboot ${loadaddr} 13.0.0.1:kernel.bin; load mmc 0:1 ${fdt_addr_r} bcm2711-rpi-4-b.dtb; fdt addr ${fdt_addr_r}; fdt resize; fdt memory 0x0 0x3C000000; booti ${loadaddr} ${initrd_addr_r}:67108864 ${fdt_addr_r} + +dhcp; +env set fdt_addr_r 0x04000000; env set initrd_addr_r 0x20000000; tftpboot ${initrd_addr_r} 192.168.88.10:initrd.img; tftpboot ${loadaddr} 192.168.88.10:yggdrasil-kernel.bin; load mmc 1:1 ${fdt_addr_r} bcm2711-rpi-4-b.dtb; fdt addr ${fdt_addr_r}; fdt resize; fdt memory 0x0 0x3C000000; booti ${loadaddr} ${initrd_addr_r}:0x4000000 ${fdt_addr_r} + + +Missing drivers: + +No driver for Some("hvs@7e400000") ("brcm,bcm2711-hvs") +No driver for Some("i2c@7e804000") ("brcm,bcm2711-i2c") + also "brcm,bcm2835-i2c" +No driver for Some("usb@7e980000") ("brcm,bcm2835-usb") +No driver for Some("local_intc@40000000") ("brcm,bcm2836-l1-intc") +: avs-monitor@7d5d2000: probed +No driver for Some("thermal") ("brcm,bcm2711-thermal") +No driver for Some("dma@7e007000") ("brcm,bcm2835-dma") +No driver for Some("watchdog@7e100000") ("brcm,bcm2835-pm") + also "brcm,bcm2835-pm-wdt" +No driver for Some("rng@7e104000") ("brcm,bcm2711-rng200") +No driver for Some("pixelvalve@7e206000") ("brcm,bcm2711-pixelvalve0") +No driver for Some("pixelvalve@7e207000") ("brcm,bcm2711-pixelvalve1") +No driver for Some("pixelvalve@7e20a000") ("brcm,bcm2711-pixelvalve2") +No driver for Some("pwm@7e20c800") ("brcm,bcm2835-pwm") +No driver for Some("pixelvalve@7e216000") ("brcm,bcm2711-pixelvalve4") +No driver for Some("clock@7ef00000") ("brcm,brcm2711-dvp") +No driver for Some("interrupt-controller@7ef00100") ("brcm,bcm2711-l2-intc") + also "brcm,l2-intc" +No driver for Some("hdmi@7ef00700") ("brcm,bcm2711-hdmi0") +No driver for Some("i2c@7ef04500") ("brcm,bcm2711-hdmi-i2c") +No driver for Some("hdmi@7ef05700") ("brcm,bcm2711-hdmi1") +No driver for Some("i2c@7ef09500") ("brcm,bcm2711-hdmi-i2c") +No driver for Some("firmware") ("raspberrypi,bcm2835-firmware") + also "simple-mfd" +No driver for Some("clocks") ("raspberrypi,firmware-clocks") +No driver for Some("gpio") ("raspberrypi,firmware-gpio") +No driver for Some("reset") ("raspberrypi,firmware-reset") +No driver for Some("power") ("raspberrypi,bcm2835-power") +No driver for Some("mailbox@7e00b840") ("brcm,bcm2835-vchiq") +No driver for Some("phy") ("usb-nop-xceiv") +No driver for Some("gpu") ("brcm,bcm2711-vc5") +No driver for Some("mmc@7e340000") ("brcm,bcm2711-emmc2") +No driver for Some("arm-pmu") ("arm,cortex-a72-pmu") + also "arm,armv8-pmuv3" +No driver for Some("cpu@0") ("arm,cortex-a72") +No driver for Some("cpu@1") ("arm,cortex-a72") +No driver for Some("cpu@2") ("arm,cortex-a72") +No driver for Some("cpu@3") ("arm,cortex-a72") +No driver for Some("pcie@7d500000") ("brcm,bcm2711-pcie") +No driver for Some("ethernet@7d580000") ("brcm,bcm2711-genet-v5") +No driver for Some("mdio@e14") ("brcm,genet-mdio-v5") +No driver for Some("leds") ("gpio-leds") +No driver for Some("wifi-pwrseq") ("mmc-pwrseq-simple") +No driver for Some("sd_io_1v8_reg") ("regulator-gpio") +No driver for Some("sd_vcc_reg") ("regulator-fixed") diff --git a/doc/visionfive2.txt b/doc/visionfive2.txt index bee1d149..fdabc77c 100644 --- a/doc/visionfive2.txt +++ b/doc/visionfive2.txt @@ -37,3 +37,94 @@ env set ipaddr 13.0.0.2; env set initrd_addr_r 0x70000000; tftpboot ${initrd_add dhcp dhcp; env set initrd_addr_r 0x70000000; tftpboot ${initrd_addr_r} 192.168.88.10:initrd.img; tftpboot ${loadaddr} 192.168.88.10:yggdrasil-kernel.bin; load mmc 1:3 ${fdt_addr_r} dtbs/6.6.20-starfive/starfive/${fdtfile}; fdt resize; booti ${loadaddr} ${initrd_addr_r}:60000000 ${fdt_addr_r} + + +Missing drivers: + +Clock/reset/pin: + No driver for Some("pinctrl@17020000") ("starfive,jh7110-aon-pinctrl") + No driver for Some("clock-controller@19810000") ("starfive,jh7110-ispcrg") + No driver for Some("clock-controller@295c0000") ("starfive,jh7110-voutcrg") + +Power/reg/GPIO: + No driver for Some("opp-table-0") ("operating-points-v2") + No driver for Some("pmic@36") ("x-powers,axp15060") + No driver for Some("power-controller@17030000") ("starfive,jh7110-pmu") + No driver for Some("leds") ("gpio-leds") + No driver for Some("gpio-restart") ("gpio-restart") + +Serial: + No driver for Some("i2c@10030000") ("snps,designware-i2c") + No driver for Some("i2c@10050000") ("snps,designware-i2c") + No driver for Some("i2c@12050000") ("snps,designware-i2c") + No driver for Some("i2c@12060000") ("snps,designware-i2c") + No driver for Some("spi@10060000") ("arm,pl022") + also "arm,primecell" + +Bus: + No driver for Some("usb@10100000") ("starfive,jh7110-usb") + No driver for Some("usb@0") ("cdns,usb3") + No driver for Some("phy@10200000") ("starfive,jh7110-usb-phy") + No driver for Some("phy@10210000") ("starfive,jh7110-pcie-phy") + No driver for Some("phy@10220000") ("starfive,jh7110-pcie-phy") + No driver for Some("pcie@940000000") ("starfive,jh7110-pcie") + No driver for Some("pcie@9c0000000") ("starfive,jh7110-pcie") + +Interrupt: + No driver for Some("interrupt-controller") ("riscv,cpu-intc") + No driver for Some("timer@2000000") ("starfive,jh7110-clint") + also "sifive,clint0" + +Display/GPU subsystem: + No driver for Some("display-subsystem") ("starfive,jh7110-display") + also "verisilicon,display-subsystem" + No driver for Some("dsi-output") ("starfive,jh7110-display-encoder") + also "verisilicon,dsi-encoder" + No driver for Some("jpu@13090000") ("starfive,jpu") + No driver for Some("vpu_dec@130a0000") ("starfive,vdec") + No driver for Some("vpu_enc@130b0000") ("starfive,venc") + No driver for Some("gpu@18000000") ("img-gpu") + No driver for Some("vin_sysctl@19800000") ("starfive,jh7110-vin") + No driver for Some("phy@19820000") ("starfive,jh7110-dphy-rx") + No driver for Some("dc8200@29400000") ("starfive,jh7110-dc8200") + also "verisilicon,dc8200" + No driver for Some("hdmi@29590000") ("starfive,jh7110-hdmi") + also "inno,hdmi" + No driver for Some("mipi@295d0000") ("starfive,jh7110-mipi_dsi") + also "cdns,dsi" + No driver for Some("mipi-dphy@295e0000") ("starfive,jh7110-mipi-dphy-tx") + also "m31,mipi-dphy-tx" + +Misc: + No driver for Some("mailbox_client") ("starfive,mailbox-test") + No driver for Some("cache-controller@2010000") ("starfive,jh7110-ccache") + also "sifive,ccache0" + also "cache" + No driver for Some("pwm@120d0000") ("starfive,jh7110-pwm") + also "opencores,pwm-v1" + No driver for Some("temperature-sensor@120e0000") ("starfive,jh7110-temp") + No driver for Some("timer@13050000") ("starfive,jh7110-timer") + No driver for Some("mailbox@13060000") ("starfive,mail_box") + No driver for Some("watchdog@13070000") ("starfive,jh7110-wdt") + No driver for Some("crypto@16000000") ("starfive,jh7110-crypto") + No driver for Some("rng@1600c000") ("starfive,jh7110-trng") + No driver for Some("mdio") ("snps,dwmac-mdio") + No driver for Some("mdio") ("snps,dwmac-mdio") + No driver for Some("dma-controller@16050000") ("starfive,jh7110-axi-dma") + No driver for Some("dma-controller@16008000") ("arm,pl080") + also "arm,primecell" + No driver for Some("rtc@17040000") ("starfive,jh7110-rtc") + No driver for Some("e24@6e210000") ("starfive,e24") + No driver for Some("linux,cma") ("shared-dma-pool") + +Storage: + No driver for Some("spi@13010000") ("starfive,jh7110-qspi") + also "cdns,qspi-nor" + No driver for Some("flash@0") ("jedec,spi-nor") + No driver for Some("partitions") ("fixed-partitions") + No driver for Some("mmc@16010000") ("starfive,jh7110-mmc") + No driver for Some("mmc@16020000") ("starfive,jh7110-mmc") + +Audio: + No driver for Some("pwmdac@100b0000") ("starfive,jh7110-pwmdac") + No driver for Some("i2s@120b0000") ("starfive,jh7110-i2stx0") diff --git a/kernel/lib/device-tree/src/driver/mod.rs b/kernel/lib/device-tree/src/driver/mod.rs index 013f6eac..bde975ce 100644 --- a/kernel/lib/device-tree/src/driver/mod.rs +++ b/kernel/lib/device-tree/src/driver/mod.rs @@ -17,8 +17,8 @@ pub use macros::device_tree_driver; pub use registry::{lookup_phandle, register_driver}; pub use syscon::DeviceTreeSyscon; pub use traits::{ - DeviceTreeClockController, DeviceTreeInterruptController, DeviceTreeResetController, Driver, - ProbeContext, + DeviceTreeClockController, DeviceTreeInterruptController, DeviceTreePinController, + DeviceTreeResetController, Driver, ProbeContext, }; pub use tree::{find_node, unflatten_device_tree, walk_device_tree, Node}; diff --git a/kernel/lib/device-tree/src/driver/traits.rs b/kernel/lib/device-tree/src/driver/traits.rs index 58294012..f05c85f5 100644 --- a/kernel/lib/device-tree/src/driver/traits.rs +++ b/kernel/lib/device-tree/src/driver/traits.rs @@ -8,6 +8,7 @@ use device_api::{ device::Device, interrupt::{ExternalInterruptController, FullIrq}, }; +use libk::error::Error; use crate::TProp; @@ -51,6 +52,12 @@ pub struct ProbeContext { pub sequence: Option, } +/// `-pinctrl`-type devices, used for configuring pin groups +pub trait DeviceTreePinController { + /// Configure a pin group + fn configure_pin_group(self: Arc, pins: &Arc) -> Result<(), Error>; +} + impl ProbeContext { /// See [Node::map_range] pub fn map_range(&self, range: Range) -> Option> { diff --git a/kernel/lib/device-tree/src/driver/tree.rs b/kernel/lib/device-tree/src/driver/tree.rs index 69bfe1b6..d4dc912c 100644 --- a/kernel/lib/device-tree/src/driver/tree.rs +++ b/kernel/lib/device-tree/src/driver/tree.rs @@ -18,7 +18,8 @@ use libk_util::OneTimeInit; use yggdrasil_abi::error::Error; use crate::{ - driver::DeviceTreeSyscon, DeviceTree, DeviceTreeNodeExt, DeviceTreePropertyRead, TNode, TProp, + driver::{traits::DeviceTreePinController, DeviceTreeSyscon}, + tree, DeviceTree, DeviceTreeNodeExt, DeviceTreePropertyRead, TNode, TProp, }; use super::{ @@ -55,6 +56,7 @@ pub struct Node { pub(crate) clock_controler: OneTimeInit>, pub(crate) reset_controller: OneTimeInit>, pub(crate) system_controller: OneTimeInit>, + pub(crate) pin_controller: OneTimeInit>, } pub(crate) struct ProbedDevice { @@ -90,6 +92,7 @@ impl NodeDevice { impl Node { fn probe_single(node: &Arc, cx: &mut ProbeContext) -> Option { + let name = node.name(); let compatible = node.compatible.as_ref()?; let drivers = DRIVERS.read(); @@ -100,7 +103,6 @@ impl Node { if libk::config::get().device_tree.log_missing { if driver.is_none() { - let name = node.name(); // FIXME don't spam virtio missing stuff if !name.map_or(false, |n| n.starts_with("virtio_mmio")) { for (i, compatible) in compatible.as_str_list().enumerate() { @@ -115,6 +117,11 @@ impl Node { } let driver = driver?; + // Initialize default pinctrl before probing + node.setup_pins() + .inspect_err(|e| log::error!("{name:?}: pinctrl init error {e:?}")) + .ok()?; + let device = driver.imp.probe(node, cx); // Move to early init unless driver wants to sleep @@ -212,6 +219,11 @@ impl Node { self.system_controller.init(syscon); } + /// Informs the node of its capability as a pin controller. + pub fn make_pin_controller(&self, pinctrl: Arc) { + self.pin_controller.init(pinctrl); + } + /// Returns the device driver associated with this node, if any was probed. pub fn driver(&self) -> Option<&'static dyn Driver> { let probed = self.device.try_get()?.as_probed()?; @@ -409,11 +421,16 @@ impl Node { .map(|e| e.clone().as_interrupt_controller()) } - /// Attempts to get an system controller represented by this node, if any + /// Attempts to get a system controller represented by this node, if any pub fn as_system_controller(self: &Arc) -> Option> { self.system_controller.try_get().cloned() } + /// Attempts to get a pin controller represented by this node, if any + pub fn as_pin_controller(&self) -> Option> { + self.pin_controller.try_get().cloned() + } + /// Returns the `#address-cells` value of the node's parent bus pub fn bus_address_cells(&self) -> usize { self.bus_address_cells @@ -444,6 +461,11 @@ impl Node { self.dt_node.property(name) } + /// Returns `true` if the node has a property `name` inside + pub fn has_property(&self, name: &str) -> bool { + self.dt_node.has_property(name) + } + /// Interprets property `name` as a single cell and casts it to usize pub fn prop_usize(&self, name: &str) -> Option { self.dt_node.prop_cell_usize(name) @@ -479,6 +501,28 @@ impl Node { Some(node) } + + /// Dump the contents of the device tree node into Info-level log + pub fn dump(&self) { + tree::dump(self.dt_node.clone(), 0); + } + + fn setup_pins(&self) -> Result<(), Error> { + // TODO lookup pin state by name + let Some(pinctrl0) = self.prop_usize("pinctrl-0") else { + return Ok(()); + }; + let pinctrl0 = lookup_phandle(pinctrl0 as Phandle, false).ok_or(Error::DoesNotExist)?; + + // Find pinctrl-group's parent pin controller + let pin_controller = pinctrl0.parent().ok_or(Error::DoesNotExist)?; + pin_controller.probe().ok_or(Error::DoesNotExist)?; + let pin_controller = pin_controller + .as_pin_controller() + .ok_or(Error::DoesNotExist)?; + + pin_controller.configure_pin_group(&pinctrl0) + } } impl fmt::Debug for Node { @@ -531,6 +575,7 @@ fn unflatten_node( clock_controler: OneTimeInit::new(), reset_controller: OneTimeInit::new(), system_controller: OneTimeInit::new(), + pin_controller: OneTimeInit::new(), }); if let Some(phandle) = phandle { diff --git a/kernel/lib/device-tree/src/driver/util.rs b/kernel/lib/device-tree/src/driver/util.rs index 6519e433..f257b8f3 100644 --- a/kernel/lib/device-tree/src/driver/util.rs +++ b/kernel/lib/device-tree/src/driver/util.rs @@ -12,6 +12,34 @@ use crate::{ use super::Node; +/// Defines how push-pull is configured for a pin +#[derive(Debug, Clone, Copy)] +pub enum GenericPinctrlBiasConfig { + /// Disable push-pull + Disable, + /// Pull pin up + PullUp, + /// Pull pin down + PullDown, +} + +/// Represents pin config parameters most commonly used in FDTs +#[derive(Debug, Clone, Copy)] +pub struct GenericPinctrlConfig { + /// Whether and how to configure the push-pull on the pin + pub bias: Option, + /// Drive strength in pinmux-specific units + pub drive_strength: Option, + /// Whether to enable the pin as an input + pub input_enable: Option, + /// Whether to enable the Schmitt trigger on the pin + pub input_schmitt_enable: Option, + /// Whether to enable the pin as an output + pub output_enable: Option, + /// GPIO rise/fall time + pub slew_rate: Option, +} + /// Represents an entry in a PCIe-controller's `interrupt-map` field #[derive(Debug)] pub struct PcieInterruptEntry { @@ -58,6 +86,55 @@ pub struct PcieMsiMapIter<'a> { offset: usize, } +impl GenericPinctrlConfig { + fn tristate(enable: bool, disable: bool) -> Option { + if enable { + Some(true) + } else if disable { + Some(false) + } else { + None + } + } + + /// Retrieves a generic pin configuration from a device tree node + pub fn from_node(node: &Node) -> Self { + let bias_disable = node.has_property("bias-disable"); + let bias_pull_up = node.has_property("bias-pull-up"); + let bias_pull_down = node.has_property("bias-pull-down"); + let drive_strength = node.prop_usize("drive-strength").map(|p| p as u32); + let input_disable = node.has_property("input-disable"); + let input_enable = node.has_property("input-enable"); + let input_schmitt_enable = node.has_property("input-schmitt-enable"); + let input_schmitt_disable = node.has_property("input-schmitt-disable"); + let output_disable = node.has_property("output-disable"); + let output_enable = node.has_property("output-enable"); + let slew_rate = node.prop_usize("slew-rate").map(|p| p as u32); + + let bias = if bias_disable { + Some(GenericPinctrlBiasConfig::Disable) + } else if bias_pull_up { + Some(GenericPinctrlBiasConfig::PullUp) + } else if bias_pull_down { + Some(GenericPinctrlBiasConfig::PullDown) + } else { + None + }; + let input_enable = Self::tristate(input_enable, input_disable); + let input_schmitt_enable = Self::tristate(input_schmitt_enable, input_schmitt_disable); + let output_enable = Self::tristate(output_enable, output_disable); + + Self { + bias, + drive_strength, + input_enable, + input_schmitt_enable, + output_enable, + slew_rate, + } + } +} + impl Iterator for PcieInterruptMapIter<'_> { type Item = PcieInterruptEntry; diff --git a/kernel/lib/device-tree/src/tree.rs b/kernel/lib/device-tree/src/tree.rs index 9065741b..0a07784d 100644 --- a/kernel/lib/device-tree/src/tree.rs +++ b/kernel/lib/device-tree/src/tree.rs @@ -197,7 +197,7 @@ impl<'a> DeviceTree<'a> { unsafe impl Sync for DeviceTree<'_> {} fn indent(amount: usize) { - log::info!(target: "raw", "{0:1$}", "", amount * 2); + log::info!(target: ":raw", "{0:1$}", "", amount * 2); } fn dump_property(node: &TNode, prop: TProp, level: usize) { @@ -211,14 +211,14 @@ fn dump_property(node: &TNode, prop: TProp, level: usize) { let name = prop.name().unwrap_or(""); indent(level); - log::info!(target: "raw", "{name}"); + log::info!(target: ":raw", "{name}"); if prop.len() == 0 { - log::info!(target: "raw", ";\n"); + log::info!(target: ":raw", ";\n"); return; } - log::info!(target: "raw", " = "); + log::info!(target: ":raw", " = "); let ty = match name { "model" | "compatible" | "clock-output-names" | "clock-names" | "device_type" @@ -234,48 +234,48 @@ fn dump_property(node: &TNode, prop: TProp, level: usize) { // "..." for (i, string) in prop.as_str_list().enumerate() { if i != 0 { - log::info!(target: "raw", ", "); + log::info!(target: ":raw", ", "); } - log::info!(target: "raw", "{string:?}"); + log::info!(target: ":raw", "{string:?}"); } } Type::Cells => { // <...> - log::info!(target: "raw", "<"); + log::info!(target: ":raw", "<"); let mut i = 0; while let Some(value) = prop.read_cell(i, 1) { if i != 0 { - log::info!(target: "raw", ", "); + log::info!(target: ":raw", ", "); } - log::info!(target: "raw", "{value:#x}"); + log::info!(target: ":raw", "{value:#x}"); i += 1; } - log::info!(target: "raw", ">"); + log::info!(target: ":raw", ">"); } Type::Bytes => { // <...> - log::info!(target: "raw", "<"); + log::info!(target: ":raw", "<"); for (i, byte) in prop.raw().iter().enumerate() { if i != 0 { - log::info!(target: "raw", ", "); + log::info!(target: ":raw", ", "); } - log::info!(target: "raw", "{byte:#x}"); + log::info!(target: ":raw", "{byte:#x}"); } - log::info!(target: "raw", ">"); + log::info!(target: ":raw", ">"); } } - log::info!(target: "raw", ";\n"); + log::info!(target: ":raw", ";\n"); } -fn dump(node: TNode, level: usize) { +pub(crate) fn dump(node: TNode, level: usize) { let name = node.name().unwrap_or(""); indent(level); if name.is_empty() { - log::info!(target: "raw", "{{\n"); + log::info!(target: ":raw", "{{\n"); } else { - log::info!(target: "raw", "{name}: {{\n"); + log::info!(target: ":raw", "{name}: {{\n"); } let mut do_break = false; @@ -286,12 +286,12 @@ fn dump(node: TNode, level: usize) { for child in node.children() { if do_break { - log::info!(target: "raw", "\n"); + log::info!(target: ":raw", "\n"); do_break = false; } dump(child, level + 1); } indent(level); - log::info!(target: "raw", "}}\n"); + log::info!(target: ":raw", "}}\n"); } diff --git a/kernel/src/device/pinctrl/bcm2711_gpio.rs b/kernel/src/device/pinctrl/bcm2711_gpio.rs new file mode 100644 index 00000000..5fc6c381 --- /dev/null +++ b/kernel/src/device/pinctrl/bcm2711_gpio.rs @@ -0,0 +1,196 @@ +use abi::error::Error; +use alloc::sync::Arc; +use device_api::{ + device::{Device, DeviceInitContext}, + interrupt::FullIrq, +}; +use device_tree::{ + driver::{device_tree_driver, DeviceTreePinController, Node, ProbeContext}, + DeviceTreePropertyRead, +}; +use libk_mm::device::DeviceMemoryIo; +use libk_util::sync::IrqSafeSpinlock; +use tock_registers::{ + interfaces::{Readable, Writeable}, + register_structs, + registers::{ReadOnly, ReadWrite, WriteOnly}, +}; + +const PUPD_NONE: u32 = 0b00; + +register_structs! { + #[allow(non_snake_case)] + Regs { + // Pin function select + (0x00 => GPFSEL: [ReadWrite; 6]), + (0x18 => _0), + // Set pin + (0x1C => GPSET: [WriteOnly; 2]), + (0x24 => _1), + // Clear pin + (0x28 => GPCLR: [WriteOnly; 2]), + (0x30 => _2), + // Current pin level + (0x34 => GPLEV: [ReadOnly; 2]), + (0x3C => _3), + // Pin event detect status + (0x40 => GPEDS: [ReadWrite; 2]), + (0x48 => _4), + // Pin rising edge event enable + (0x4C => GPREN: [ReadWrite; 2]), + (0x54 => _5), + // Pin falling edge event enable + (0x58 => GPFEN: [ReadWrite; 2]), + (0x60 => _6), + // Pin high event enable + (0x64 => GPHEN: [ReadWrite; 2]), + (0x6C => _7), + // Pin low event enable + (0x70 => GPLEN: [ReadWrite; 2]), + (0x78 => _8), + // Pin async rising edge event enable + (0x7C => GPAREN: [ReadWrite; 2]), + (0x84 => _9), + // Pin async falling edge event enable + (0x88 => GPAFEN: [ReadWrite; 2]), + (0x90 => _10), + // Pin pull up/down control + (0xE4 => GPIO_PUP_PDN_CNTRL_REG: [ReadWrite; 4]), + (0xF4 => _11), + (0x100 => @END), + } +} + +struct Bcm2711Gpio { + regs: IrqSafeSpinlock>, + #[allow(unused)] + irqs: [FullIrq; 2], +} + +impl Regs { + fn write_fsel(&self, pin: usize, value: u32) { + let fsel_reg = pin / 10; + let fsel_shift = (pin % 10) * 3; + + let mut fsel = self.GPFSEL[fsel_reg].get(); + fsel &= !(0x7 << fsel_shift); + fsel |= (value & 0x7) << fsel_shift; + self.GPFSEL[fsel_reg].set(fsel); + } + + fn write_pupd(&self, pin: usize, value: u32) { + let pupd_reg = pin / 16; + let pupd_shift = (pin % 16) * 2; + + let mut pupd = self.GPIO_PUP_PDN_CNTRL_REG[pupd_reg].get(); + pupd &= !(0x3 << pupd_shift); + pupd |= (value & 0x3) << pupd_shift; + self.GPIO_PUP_PDN_CNTRL_REG[pupd_reg].set(pupd); + } + + fn configure_pin_interrupts( + &self, + pin: usize, + rising_edge: bool, + falling_edge: bool, + level_high: bool, + level_low: bool, + ) { + #[inline] + fn modify_reg(reg: &ReadWrite, bit: u32, set: bool) { + if set { + reg.set(reg.get() | bit); + } else { + reg.set(reg.get() & !bit); + } + } + + // TODO use async edge detection (likely have some limitations)? + let reg = pin / 32; + let bit = 1 << (pin % 32); + + // Disable async edge events + modify_reg(&self.GPAREN[reg], bit, false); + modify_reg(&self.GPAFEN[reg], bit, false); + + modify_reg(&self.GPREN[reg], bit, rising_edge); + modify_reg(&self.GPFEN[reg], bit, falling_edge); + modify_reg(&self.GPHEN[reg], bit, level_high); + modify_reg(&self.GPLEN[reg], bit, level_low); + + // Clear interrupt status + self.GPEDS[reg].set(bit); + } + + fn configure_pin_function(&self, pin: usize, function: u32, pull: u32) { + self.write_fsel(pin, function); + self.write_pupd(pin, pull); + } +} + +impl Device for Bcm2711Gpio { + unsafe fn init(self: Arc, _cx: DeviceInitContext) -> Result<(), Error> { + let regs = self.regs.lock(); + + // Disable all interrupts by default + for pin in 0..58 { + regs.configure_pin_interrupts(pin, false, false, false, false); + } + + Ok(()) + } + + fn display_name(&self) -> &str { + "bcm2711-gpio" + } +} + +impl DeviceTreePinController for Bcm2711Gpio { + fn configure_pin_group(self: Arc, group: &Arc) -> Result<(), Error> { + let pins = group.property("brcm,pins").ok_or(Error::InvalidArgument)?; + let function = group.property("brcm,function").ok_or(Error::DoesNotExist)?; + let pull = group.property("brcm,pull"); + + if function.is_empty() || pins.is_empty() { + return Ok(()); + } + + let function = function.read_cell(0, 1).ok_or(Error::InvalidArgument)? as u32; + let regs = self.regs.lock(); + + for i in 0..pins.len() / 4 { + let pin = pins.read_cell(i, 1).ok_or(Error::InvalidArgument)? as u32; + let pull = if let Some(pull) = pull.as_ref().and_then(|p| p.read_cell(i, 1)) { + pull as u32 + } else { + PUPD_NONE + }; + log::info!("bcm2711-gpio: gpio{pin} function={function}"); + regs.configure_pin_function(pin as usize, function, pull); + } + + Ok(()) + } +} + +device_tree_driver! { + compatible: ["brcm,bcm2711-gpio"], + driver: { + fn probe(&self, node: &Arc, context: &mut ProbeContext) -> Option> { + let irq0 = node.interrupt(0)?; + let irq1 = node.interrupt(1)?; + + let base = node.map_base(context, 0)?; + let regs = unsafe { DeviceMemoryIo::map(base, Default::default()) }.ok()?; + + let gpio = Arc::new(Bcm2711Gpio { + regs: IrqSafeSpinlock::new(regs), + irqs: [irq0, irq1] + }); + + node.make_pin_controller(gpio.clone()); + + Some(gpio) + } + } +} diff --git a/kernel/src/device/pinctrl/jh7110_pinctrl.rs b/kernel/src/device/pinctrl/jh7110_pinctrl.rs index c6874d28..f3fce114 100644 --- a/kernel/src/device/pinctrl/jh7110_pinctrl.rs +++ b/kernel/src/device/pinctrl/jh7110_pinctrl.rs @@ -1,31 +1,335 @@ use abi::error::Error; -use alloc::sync::Arc; +use alloc::{sync::Arc, vec::Vec}; use device_api::{ - clock::ClockHandle, + clock::{ClockHandle, ResetHandle}, device::{Device, DeviceInitContext}, }; -use device_tree::driver::{device_tree_driver, Node, ProbeContext}; -use libk_mm::device::DeviceMemoryIoMut; -use libk_util::sync::IrqSafeSpinlock; +use device_tree::{ + driver::{ + device_tree_driver, + util::{GenericPinctrlBiasConfig, GenericPinctrlConfig}, + DeviceTreePinController, Node, ProbeContext, + }, + DeviceTreePropertyRead, +}; +use libk_mm::{address::PhysicalAddress, device::DeviceMemoryIoMut}; +use libk_util::{sync::IrqSafeSpinlock, OneTimeInit}; -struct Jh7110SysPinctrl { - #[allow(unused)] - regs: IrqSafeSpinlock>, - clock: ClockHandle, +#[derive(Debug)] +struct PinMuxConfig { + pin: u8, + function: u8, + doen: u8, + dout: u8, + din: u8, } -unsafe impl Send for Jh7110SysPinctrl {} -unsafe impl Sync for Jh7110SysPinctrl {} +struct SysRegs(DeviceMemoryIoMut<'static, [u32]>); +struct AonRegs(DeviceMemoryIoMut<'static, [u32]>); -impl Device for Jh7110SysPinctrl { +unsafe impl Send for SysRegs {} +unsafe impl Sync for SysRegs {} +unsafe impl Send for AonRegs {} +unsafe impl Sync for AonRegs {} + +struct Gpio { + regs: IrqSafeSpinlock, + init: OneTimeInit>, + clocks: Vec, + resets: Vec, +} + +trait GpioRegs: Sized + Send + 'static { + const NAME: &'static str; + + const DOUT_OFFSET: usize; + const DOEN_OFFSET: usize; + const GPI_OFFSET: usize; + + const DOUT_MASK: u32; + const DOEN_MASK: u32; + const GPI_MASK: u32; + + unsafe fn map(base: PhysicalAddress) -> Result; + fn reg_mut(&mut self, index: usize) -> *mut u32; + fn pin_functions(&self, pin: usize) -> Option<(usize, usize, u32)>; + + fn configure_pin(&mut self, pin: usize, doen: u32, dout: u32, din: u32) { + let reg = pin / 4; + let shift = (pin % 4) * 8; + + let reg_doen = self.reg_mut(Self::DOEN_OFFSET + reg); + let reg_dout = self.reg_mut(Self::DOUT_OFFSET + reg); + + unsafe { + let mut val = reg_dout.read_volatile(); + val &= !(Self::DOUT_MASK << shift); + val |= dout << shift; + reg_dout.write_volatile(val); + + let mut val = reg_doen.read_volatile(); + val &= !(Self::DOEN_MASK << shift); + val |= doen << shift; + reg_doen.write_volatile(val); + + if din != 0xFF { + let din_reg = din as usize / 4; + let din_shift = (din % 4) * 8; + + let reg_din = self.reg_mut(Self::GPI_OFFSET + din_reg); + let mut val = reg_din.read_volatile(); + val &= !(Self::GPI_MASK << din_shift); + val |= (pin as u32 + 2) << din_shift; + reg_din.write_volatile(val); + } + } + } + + fn configure_pin_function(&mut self, pin: usize, func: u32) { + let Some((offset, shift, max)) = self.pin_functions(pin) else { + return; + }; + + if func > max { + return; + } + + log::info!("jh7110-sys-pinctrl: set pad{pin} function={func}"); + let reg = self.reg_mut(offset / 4); + unsafe { + let mut val = reg.read_volatile(); + val &= !(0x3 << shift); + val |= func << shift; + reg.write_volatile(val); + } + } + + fn set_input_enabled(&mut self, _pin: usize, _enabled: bool) {} + + fn set_output_enabled(&mut self, _pin: usize, _enabled: bool) {} + + fn set_bias(&mut self, _pin: usize, _bias: GenericPinctrlBiasConfig) {} +} + +// sys-pinctrl pads +const fn pad_gpio(n: usize) -> usize { + n +} +const PAD_GMAC1_RXC: usize = 82; +const SYS_PIN_COUNT: usize = 95; + +const SYS_PIN_FUNCTIONS: [Option<(usize, usize, u32)>; SYS_PIN_COUNT] = const { + let mut t = [const { None }; SYS_PIN_COUNT]; + + t[PAD_GMAC1_RXC] = Some((0x29C, 0, 1)); + t[pad_gpio(10)] = Some((0x29C, 2, 3)); + t[pad_gpio(11)] = Some((0x29C, 5, 3)); + t[pad_gpio(12)] = Some((0x29C, 8, 3)); + t[pad_gpio(13)] = Some((0x29C, 11, 3)); + t[pad_gpio(14)] = Some((0x29C, 14, 3)); + t[pad_gpio(15)] = Some((0x29C, 17, 3)); + t[pad_gpio(16)] = Some((0x29C, 20, 3)); + t[pad_gpio(17)] = Some((0x29C, 23, 3)); + t[pad_gpio(18)] = Some((0x29C, 26, 3)); + t[pad_gpio(19)] = Some((0x29C, 29, 3)); + + t[pad_gpio(20)] = Some((0x2A0, 0, 3)); + t[pad_gpio(21)] = Some((0x2A0, 3, 3)); + t[pad_gpio(22)] = Some((0x2A0, 6, 3)); + t[pad_gpio(23)] = Some((0x2A0, 9, 3)); + t[pad_gpio(24)] = Some((0x2A0, 12, 3)); + t[pad_gpio(25)] = Some((0x2A0, 15, 3)); + t[pad_gpio(26)] = Some((0x2A0, 18, 3)); + t[pad_gpio(27)] = Some((0x2A0, 21, 3)); + t[pad_gpio(28)] = Some((0x2A0, 24, 3)); + t[pad_gpio(29)] = Some((0x2A0, 27, 3)); + + t[pad_gpio(30)] = Some((0x2A4, 0, 3)); + t[pad_gpio(31)] = Some((0x2A4, 3, 3)); + t[pad_gpio(32)] = Some((0x2A4, 6, 3)); + t[pad_gpio(33)] = Some((0x2A4, 9, 3)); + t[pad_gpio(34)] = Some((0x2A4, 12, 3)); + t[pad_gpio(35)] = Some((0x2A4, 15, 3)); + t[pad_gpio(36)] = Some((0x2A4, 17, 3)); + t[pad_gpio(37)] = Some((0x2A4, 20, 3)); + t[pad_gpio(38)] = Some((0x2A4, 23, 3)); + t[pad_gpio(39)] = Some((0x2A4, 26, 3)); + t[pad_gpio(40)] = Some((0x2A4, 29, 3)); + + t[pad_gpio(41)] = Some((0x2A8, 0, 3)); + t[pad_gpio(42)] = Some((0x2A8, 3, 3)); + t[pad_gpio(43)] = Some((0x2A8, 6, 3)); + t[pad_gpio(44)] = Some((0x2A8, 9, 3)); + t[pad_gpio(45)] = Some((0x2A8, 12, 3)); + t[pad_gpio(46)] = Some((0x2A8, 15, 3)); + t[pad_gpio(47)] = Some((0x2A8, 18, 3)); + t[pad_gpio(48)] = Some((0x2A8, 21, 3)); + t[pad_gpio(49)] = Some((0x2A8, 24, 3)); + t[pad_gpio(50)] = Some((0x2A8, 27, 3)); + t[pad_gpio(51)] = Some((0x2A8, 30, 3)); + + t[pad_gpio(52)] = Some((0x2AC, 0, 3)); + t[pad_gpio(53)] = Some((0x2AC, 2, 3)); + t[pad_gpio(54)] = Some((0x2AC, 4, 3)); + t[pad_gpio(55)] = Some((0x2AC, 6, 3)); + t[pad_gpio(56)] = Some((0x2AC, 9, 3)); + t[pad_gpio(57)] = Some((0x2AC, 12, 3)); + t[pad_gpio(58)] = Some((0x2AC, 15, 3)); + t[pad_gpio(59)] = Some((0x2AC, 18, 3)); + t[pad_gpio(60)] = Some((0x2AC, 21, 3)); + t[pad_gpio(61)] = Some((0x2AC, 24, 3)); + t[pad_gpio(62)] = Some((0x2AC, 27, 3)); + t[pad_gpio(63)] = Some((0x2AC, 30, 3)); + + t[pad_gpio(6)] = Some((0x2B0, 0, 3)); + t[pad_gpio(7)] = Some((0x2B0, 2, 3)); + t[pad_gpio(8)] = Some((0x2B0, 5, 3)); + t[pad_gpio(9)] = Some((0x2B0, 8, 3)); + + t +}; + +impl PinMuxConfig { + fn from_fdt(fdt: u32) -> Self { + let pin = fdt as u8; + let function = ((fdt >> 8) & 0x3) as u8; + let doen = ((fdt >> 10) & 0x3F) as u8; + let dout = (fdt >> 16) as u8; + let din = (fdt >> 24) as u8; + + Self { + pin, + function, + doen, + dout, + din, + } + } +} + +impl Gpio { + fn from_device_tree(node: &Arc, context: &mut ProbeContext) -> Result, Error> { + let base = node.map_base(context, 0).ok_or(Error::DoesNotExist)?; + let regs = unsafe { R::map(base) }?; + let clocks = node.clocks().map(|c| c.collect()).unwrap_or_default(); + let resets = node.resets().map(|c| c.collect()).unwrap_or_default(); + + let gpio = Arc::new(Self { + regs: IrqSafeSpinlock::new(regs), + init: OneTimeInit::new(), + clocks, + resets, + }); + + Ok(gpio) + } + + fn configure_pinmux(&self, mux: PinMuxConfig, cfg: &GenericPinctrlConfig) { + let mut regs = self.regs.lock(); + regs.configure_pin_function(mux.pin as _, mux.function as _); + regs.configure_pin(mux.pin as _, mux.doen as _, mux.dout as _, mux.din as _); + if let Some(enable) = cfg.input_enable { + regs.set_input_enabled(mux.pin as _, enable); + } + if let Some(enable) = cfg.output_enable { + regs.set_output_enabled(mux.pin as _, enable); + } + if let Some(bias) = cfg.bias { + regs.set_bias(mux.pin as _, bias); + } + } + + fn ensure_init(&self) -> Result<(), Error> { + let res = self.init.or_init_with(|| { + for clock in self.clocks.iter() { + clock.enable()?; + } + for reset in self.resets.iter() { + reset.deassert()?; + } + + // TODO disable all pin IRQs + + Ok(()) + }); + + *res + } +} + +impl Device for Gpio { unsafe fn init(self: Arc, _cx: DeviceInitContext) -> Result<(), Error> { - self.clock.enable()?; - - Ok(()) + self.ensure_init() } fn display_name(&self) -> &str { - "jh7110-sys-pinctrl" + R::NAME + } +} + +impl DeviceTreePinController for Gpio { + fn configure_pin_group(self: Arc, pins: &Arc) -> Result<(), Error> { + self.ensure_init()?; + + for child in pins.children() { + let pinmux = child.property("pinmux").ok_or(Error::DoesNotExist)?; + let cfg = GenericPinctrlConfig::from_node(child); + + for i in 0..pinmux.len() / 4 { + let mux = pinmux.read_cell(i, 1).unwrap() as u32; + let mux = PinMuxConfig::from_fdt(mux); + self.configure_pinmux(mux, &cfg); + } + } + Ok(()) + } +} + +impl GpioRegs for SysRegs { + const NAME: &'static str = "jh7110-sys-pinctrl"; + + const DOEN_OFFSET: usize = 0x00; + const DOUT_OFFSET: usize = 0x40 / 4; + const GPI_OFFSET: usize = 0x80 / 4; + + const DOEN_MASK: u32 = 0x1F; + const DOUT_MASK: u32 = 0x3F; + const GPI_MASK: u32 = 0x3F; + + unsafe fn map(base: PhysicalAddress) -> Result { + DeviceMemoryIoMut::map_slice(base, 192, Default::default()).map(Self) + } + + fn pin_functions(&self, pin: usize) -> Option<(usize, usize, u32)> { + SYS_PIN_FUNCTIONS.get(pin).copied()? + } + + fn reg_mut(&mut self, index: usize) -> *mut u32 { + &raw mut self.0[index] + } +} + +impl GpioRegs for AonRegs { + const NAME: &'static str = "jh7110-aon-pinctrl"; + + const DOEN_OFFSET: usize = 0x00; + const DOUT_OFFSET: usize = 0x04 / 4; + const GPI_OFFSET: usize = 0x08 / 4; + + const DOEN_MASK: u32 = 0x7; + const DOUT_MASK: u32 = 0xF; + const GPI_MASK: u32 = 0xF; + + unsafe fn map(base: PhysicalAddress) -> Result { + DeviceMemoryIoMut::map_slice(base, 64, Default::default()).map(Self) + } + + fn pin_functions(&self, _pin: usize) -> Option<(usize, usize, u32)> { + None + } + + fn reg_mut(&mut self, index: usize) -> *mut u32 { + &raw mut self.0[index] } } @@ -33,17 +337,28 @@ device_tree_driver! { compatible: ["starfive,jh7110-sys-pinctrl"], driver: { fn probe(&self, node: &Arc, context: &mut ProbeContext) -> Option> { - let clock = node.clock(0).unwrap(); - let base = node.map_base(context, 0)?; + let gpio = Gpio::::from_device_tree(node, context) + .inspect_err(|e| log::error!("jh7110-sys-pinctrl: probe error: {e:?}")) + .ok()?; - let regs = unsafe { DeviceMemoryIoMut::map_slice(base, 0x2B8 / 4, Default::default()) }.ok()?; + node.make_pin_controller(gpio.clone()); - let pinctrl = Arc::new(Jh7110SysPinctrl { - clock, - regs: IrqSafeSpinlock::new(regs) - }); - - Some(pinctrl) + Some(gpio) + } + } +} + +device_tree_driver! { + compatible: ["starfive,jh7110-aon-pinctrl"], + driver: { + fn probe(&self, node: &Arc, context: &mut ProbeContext) -> Option> { + let gpio = Gpio::::from_device_tree(node, context) + .inspect_err(|e| log::error!("jh7110-aon-pinctrl: probe error: {e:?}")) + .ok()?; + + node.make_pin_controller(gpio.clone()); + + Some(gpio) } } } diff --git a/kernel/src/device/pinctrl/mod.rs b/kernel/src/device/pinctrl/mod.rs index 133a9702..745d9f9d 100644 --- a/kernel/src/device/pinctrl/mod.rs +++ b/kernel/src/device/pinctrl/mod.rs @@ -1,4 +1,8 @@ //! GPIO/Pin controller, multiplexer drivers +#[cfg(any(target_arch = "aarch64", rust_analyzer))] +mod bcm2711_gpio; #[cfg(any(target_arch = "riscv64", rust_analyzer))] mod jh7110_pinctrl; +#[cfg(any(target_arch = "aarch64", rust_analyzer))] +mod pl061; diff --git a/kernel/src/device/pinctrl/pl061.rs b/kernel/src/device/pinctrl/pl061.rs new file mode 100644 index 00000000..e4308b1d --- /dev/null +++ b/kernel/src/device/pinctrl/pl061.rs @@ -0,0 +1,94 @@ +use abi::error::Error; +use alloc::{sync::Arc, vec::Vec}; +use device_api::{ + clock::{ClockHandle, ResetHandle}, + device::{Device, DeviceInitContext}, +}; +use device_tree::driver::{device_tree_driver, DeviceTreePinController, Node, ProbeContext}; +use libk_mm::device::DeviceMemoryIo; +use libk_util::sync::IrqSafeSpinlock; +use tock_registers::{ + register_structs, + registers::{ReadOnly, ReadWrite, WriteOnly}, +}; + +register_structs! { + #[allow(non_snake_case)] + Regs { + (0x0000 => GPIODATA: [ReadWrite; 256]), + (0x0400 => GPIODIR: ReadWrite), + (0x0404 => GPIOIS: ReadWrite), + (0x0408 => GPIOIBE: ReadWrite), + (0x040C => GPIOIEV: ReadWrite), + (0x0410 => GPIOIE: ReadWrite), + (0x0414 => GPIORIS: ReadOnly), + (0x0418 => GPIOMIS: ReadOnly), + (0x041C => GPIOIC: WriteOnly), + (0x0420 => GPIOAFSEL: ReadWrite), + (0x0424 => _0), + (0x0FE0 => GPIOPERIPHID: [ReadOnly; 4]), + (0x0FF0 => GPIOPCELLID: [ReadOnly; 4]), + (0x1000 => @END), + } +} + +struct Pl061 { + #[allow(unused)] + regs: IrqSafeSpinlock>, + clocks: Vec, + resets: Vec, +} + +impl Device for Pl061 { + unsafe fn init(self: Arc, _cx: DeviceInitContext) -> Result<(), Error> { + for clock in self.clocks.iter() { + clock.enable()?; + } + for reset in self.resets.iter() { + reset.deassert()?; + } + Ok(()) + } + + fn display_name(&self) -> &str { + "PL061 GPIO Controller" + } +} + +impl DeviceTreePinController for Pl061 { + fn configure_pin_group(self: Arc, _pins: &Arc) -> Result<(), Error> { + // TODO implement this when I get some board with this pinctrl + todo!() + } +} + +device_tree_driver! { + compatible: ["arm,pl061"], + driver: { + fn probe(&self, node: &Arc, context: &mut ProbeContext) -> Option> { + let clocks = if let Some(clocks) = node.clocks() { + clocks.collect() + } else { + Vec::new() + }; + let resets = if let Some(resets) = node.resets() { + resets.collect() + } else { + Vec::new() + }; + + let base = node.map_base(context, 0)?; + let regs = unsafe { DeviceMemoryIo::map(base, Default::default()) }.ok()?; + + let pinctrl = Arc::new(Pl061 { + regs: IrqSafeSpinlock::new(regs), + clocks, + resets, + }); + + node.make_pin_controller(pinctrl.clone()); + + Some(pinctrl) + } + } +}