pinctrl: basic pinctrl/pinmux support
This commit is contained in:
@@ -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};
|
||||
|
||||
|
||||
@@ -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<InitSequence>,
|
||||
}
|
||||
|
||||
/// `-pinctrl`-type devices, used for configuring pin groups
|
||||
pub trait DeviceTreePinController {
|
||||
/// Configure a pin group
|
||||
fn configure_pin_group(self: Arc<Self>, pins: &Arc<Node>) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
impl ProbeContext {
|
||||
/// See [Node::map_range]
|
||||
pub fn map_range(&self, range: Range<u64>) -> Option<Range<u64>> {
|
||||
|
||||
@@ -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<Arc<dyn DeviceTreeClockController>>,
|
||||
pub(crate) reset_controller: OneTimeInit<Arc<dyn DeviceTreeResetController>>,
|
||||
pub(crate) system_controller: OneTimeInit<Arc<dyn DeviceTreeSyscon>>,
|
||||
pub(crate) pin_controller: OneTimeInit<Arc<dyn DeviceTreePinController>>,
|
||||
}
|
||||
|
||||
pub(crate) struct ProbedDevice {
|
||||
@@ -90,6 +92,7 @@ impl NodeDevice {
|
||||
|
||||
impl Node {
|
||||
fn probe_single(node: &Arc<Node>, cx: &mut ProbeContext) -> Option<ProbedDevice> {
|
||||
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<dyn DeviceTreePinController>) {
|
||||
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<Self>) -> Option<Arc<dyn DeviceTreeSyscon>> {
|
||||
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<Arc<dyn DeviceTreePinController>> {
|
||||
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<usize> {
|
||||
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 {
|
||||
|
||||
@@ -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<GenericPinctrlBiasConfig>,
|
||||
/// Drive strength in pinmux-specific units
|
||||
pub drive_strength: Option<u32>,
|
||||
/// Whether to enable the pin as an input
|
||||
pub input_enable: Option<bool>,
|
||||
/// Whether to enable the Schmitt trigger on the pin
|
||||
pub input_schmitt_enable: Option<bool>,
|
||||
/// Whether to enable the pin as an output
|
||||
pub output_enable: Option<bool>,
|
||||
/// GPIO rise/fall time
|
||||
pub slew_rate: Option<u32>,
|
||||
}
|
||||
|
||||
/// 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<bool> {
|
||||
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;
|
||||
|
||||
|
||||
@@ -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("<unknown-prop>");
|
||||
|
||||
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("<unknown>");
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -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<u32>; 6]),
|
||||
(0x18 => _0),
|
||||
// Set pin
|
||||
(0x1C => GPSET: [WriteOnly<u32>; 2]),
|
||||
(0x24 => _1),
|
||||
// Clear pin
|
||||
(0x28 => GPCLR: [WriteOnly<u32>; 2]),
|
||||
(0x30 => _2),
|
||||
// Current pin level
|
||||
(0x34 => GPLEV: [ReadOnly<u32>; 2]),
|
||||
(0x3C => _3),
|
||||
// Pin event detect status
|
||||
(0x40 => GPEDS: [ReadWrite<u32>; 2]),
|
||||
(0x48 => _4),
|
||||
// Pin rising edge event enable
|
||||
(0x4C => GPREN: [ReadWrite<u32>; 2]),
|
||||
(0x54 => _5),
|
||||
// Pin falling edge event enable
|
||||
(0x58 => GPFEN: [ReadWrite<u32>; 2]),
|
||||
(0x60 => _6),
|
||||
// Pin high event enable
|
||||
(0x64 => GPHEN: [ReadWrite<u32>; 2]),
|
||||
(0x6C => _7),
|
||||
// Pin low event enable
|
||||
(0x70 => GPLEN: [ReadWrite<u32>; 2]),
|
||||
(0x78 => _8),
|
||||
// Pin async rising edge event enable
|
||||
(0x7C => GPAREN: [ReadWrite<u32>; 2]),
|
||||
(0x84 => _9),
|
||||
// Pin async falling edge event enable
|
||||
(0x88 => GPAFEN: [ReadWrite<u32>; 2]),
|
||||
(0x90 => _10),
|
||||
// Pin pull up/down control
|
||||
(0xE4 => GPIO_PUP_PDN_CNTRL_REG: [ReadWrite<u32>; 4]),
|
||||
(0xF4 => _11),
|
||||
(0x100 => @END),
|
||||
}
|
||||
}
|
||||
|
||||
struct Bcm2711Gpio {
|
||||
regs: IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>,
|
||||
#[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<u32>, 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<Self>, _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<Self>, group: &Arc<Node>) -> 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<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<DeviceMemoryIoMut<'static, [u32]>>,
|
||||
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<R: GpioRegs> {
|
||||
regs: IrqSafeSpinlock<R>,
|
||||
init: OneTimeInit<Result<(), Error>>,
|
||||
clocks: Vec<ClockHandle>,
|
||||
resets: Vec<ResetHandle>,
|
||||
}
|
||||
|
||||
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<Self, Error>;
|
||||
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<R: GpioRegs> Gpio<R> {
|
||||
fn from_device_tree(node: &Arc<Node>, context: &mut ProbeContext) -> Result<Arc<Self>, 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<R: GpioRegs> Device for Gpio<R> {
|
||||
unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
|
||||
self.clock.enable()?;
|
||||
|
||||
Ok(())
|
||||
self.ensure_init()
|
||||
}
|
||||
|
||||
fn display_name(&self) -> &str {
|
||||
"jh7110-sys-pinctrl"
|
||||
R::NAME
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: GpioRegs> DeviceTreePinController for Gpio<R> {
|
||||
fn configure_pin_group(self: Arc<Self>, pins: &Arc<Node>) -> 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<Self, Error> {
|
||||
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<Self, Error> {
|
||||
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<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
|
||||
let clock = node.clock(0).unwrap();
|
||||
let base = node.map_base(context, 0)?;
|
||||
let gpio = Gpio::<SysRegs>::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<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
|
||||
let gpio = Gpio::<AonRegs>::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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<u32>; 256]),
|
||||
(0x0400 => GPIODIR: ReadWrite<u32>),
|
||||
(0x0404 => GPIOIS: ReadWrite<u32>),
|
||||
(0x0408 => GPIOIBE: ReadWrite<u32>),
|
||||
(0x040C => GPIOIEV: ReadWrite<u32>),
|
||||
(0x0410 => GPIOIE: ReadWrite<u32>),
|
||||
(0x0414 => GPIORIS: ReadOnly<u32>),
|
||||
(0x0418 => GPIOMIS: ReadOnly<u32>),
|
||||
(0x041C => GPIOIC: WriteOnly<u32>),
|
||||
(0x0420 => GPIOAFSEL: ReadWrite<u32>),
|
||||
(0x0424 => _0),
|
||||
(0x0FE0 => GPIOPERIPHID: [ReadOnly<u32>; 4]),
|
||||
(0x0FF0 => GPIOPCELLID: [ReadOnly<u32>; 4]),
|
||||
(0x1000 => @END),
|
||||
}
|
||||
}
|
||||
|
||||
struct Pl061 {
|
||||
#[allow(unused)]
|
||||
regs: IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>,
|
||||
clocks: Vec<ClockHandle>,
|
||||
resets: Vec<ResetHandle>,
|
||||
}
|
||||
|
||||
impl Device for Pl061 {
|
||||
unsafe fn init(self: Arc<Self>, _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<Self>, _pins: &Arc<Node>) -> 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<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user