diff --git a/documentation/6.components/device-driver/INDEX.md b/documentation/6.components/device-driver/INDEX.md index b8600c9e79a..bbdeb43f7ac 100644 --- a/documentation/6.components/device-driver/INDEX.md +++ b/documentation/6.components/device-driver/INDEX.md @@ -1,6 +1,7 @@ @page page_device Device Driver - @subpage page_device_framework +- @subpage page_device_dm - @subpage page_device_pin - @subpage page_device_uart - @subpage page_device_adc diff --git a/documentation/6.components/device-driver/device_driver_model/INDEX.md b/documentation/6.components/device-driver/device_driver_model/INDEX.md new file mode 100644 index 00000000000..46bad2a0f90 --- /dev/null +++ b/documentation/6.components/device-driver/device_driver_model/INDEX.md @@ -0,0 +1,361 @@ +# Device Driver Model (DM) Documentation Index + +This directory contains comprehensive documentation for RT-Thread's Device Driver Model and related subsystems. + +## Core Documentation + +- **[README.md](README.md)** - Device Driver Model Overview (English) + - DM architecture and concepts + - Bus, driver, and platform device models + - OFW/Device Tree integration + - Common APIs and usage patterns + - Migration guide + +- **[README_zh.md](README_zh.md)** - 设备驱动模型概述 (中文) + - DM 架构和概念 + - 总线、驱动和平台设备模型 + - OFW/设备树集成 + - 通用 API 和使用模式 + - 迁移指南 + +## Module-Specific Documentation + +### Power Management + +#### Regulator Framework +- **[regulator/README.md](regulator/README.md)** - Voltage/Current Regulator (English) +- **[regulator/README_zh.md](regulator/README_zh.md)** - 电压/电流调节器 (中文) + +**Status**: ✅ Complete +**Topics Covered**: +- Consumer API with complete examples +- Provider (driver) implementation guide +- Device tree bindings (fixed, GPIO, SCMI) +- Best practices and troubleshooting +- Performance considerations + +#### Clock Framework +- **clk/** - Clock Management Framework + +**Status**: 📝 To Be Documented +**Planned Topics**: +- Clock consumer API +- Clock provider implementation +- Clock tree management +- Fixed-rate, PLL, mux, divider clocks +- Device tree bindings + +#### Pin Control +- **pinctrl/** - Pin Multiplexing and Configuration + +**Status**: 📝 To Be Documented +**Planned Topics**: +- Pin configuration API +- Pin multiplexing +- Pin control driver implementation +- Device tree bindings + +#### Reset Controller +- **reset/** - Reset Controller Framework + +**Status**: 📝 To Be Documented +**Planned Topics**: +- Reset consumer API +- Reset provider implementation +- Device tree bindings + +#### Power Domain +- **pmdomain/** - Power Domain Management + +**Status**: 📝 To Be Documented +**Planned Topics**: +- Power domain consumer API +- Power domain provider implementation +- Domain hierarchy +- Device tree bindings + +### Interrupt and Timing + +#### Platform Interrupt Controller +- **pic/** - Platform Interrupt Controller + +**Status**: 📝 To Be Documented +**Planned Topics**: +- IRQ domain management +- IRQ mapping +- PIC driver implementation + +#### Hardware Timer +- **hwtimer/** - Hardware Timer Framework (with DM support) + +**Status**: 📝 To Be Documented +**Planned Topics**: +- Timer API with DM +- Timer driver implementation +- Device tree bindings + +### Storage and Memory + +#### NVMEM +- **nvmem/** - Non-Volatile Memory Framework + +**Status**: 📝 To Be Documented +**Planned Topics**: +- NVMEM consumer API +- NVMEM provider implementation +- EEPROM, OTP, eFuse support +- Device tree bindings + +#### Block Devices +- **block/** - Block Device Layer (with DM support) + +**Status**: 📝 To Be Documented +**Planned Topics**: +- Block device API with DM +- Partition support +- Device tree bindings + +#### MTD +- **mtd/** - Memory Technology Device (with DM support) + +**Status**: 📝 To Be Documented +**Planned Topics**: +- MTD framework with DM +- NOR/NAND flash support +- Device tree bindings + +### Communication + +#### Mailbox +- **mailbox/** - Mailbox/Doorbell Framework + +**Status**: 📝 To Be Documented +**Planned Topics**: +- Mailbox consumer API +- Mailbox controller driver +- Inter-processor communication +- Device tree bindings + +#### DMA +- **dma/** - DMA Engine Framework + +**Status**: 📝 To Be Documented +**Planned Topics**: +- DMA consumer API +- DMA controller driver +- DMA transfer types +- Device tree bindings + +### Bus Controllers + +#### PCI +- **pci/** - PCI Bus Support + +**Status**: 📝 To Be Documented +**Planned Topics**: +- PCI device enumeration +- PCI driver implementation +- PCIe host controller +- Device tree bindings + +#### I2C (DM Extensions) +- **i2c/** - I2C with DM Support + +**Status**: 📝 To Be Documented +**Planned Topics**: +- I2C with device tree +- I2C controller driver with DM + +#### SPI (DM Extensions) +- **spi/** - SPI with DM Support + +**Status**: 📝 To Be Documented +**Planned Topics**: +- SPI with device tree +- SPI controller driver with DM + +### Specialized Hardware + +#### Thermal Management +- **thermal/** - Thermal Management Framework + +**Status**: 📝 To Be Documented +**Planned Topics**: +- Thermal zone management +- Thermal cooling devices +- Trip points +- Device tree bindings + +#### Multi-Function Device +- **mfd/** - Multi-Function Device Framework + +**Status**: 📝 To Be Documented +**Planned Topics**: +- MFD parent device +- Sub-device registration +- Shared resources +- Device tree bindings + +#### Industrial I/O +- **iio/** - Industrial I/O Subsystem + +**Status**: 📝 To Be Documented +**Planned Topics**: +- IIO device framework +- Channels and attributes +- Buffered data capture +- Device tree bindings + +#### PHY Framework +- **phy/** - Physical Layer Framework + +**Status**: 📝 To Be Documented +**Planned Topics**: +- PHY consumer API +- PHY provider implementation +- USB, PCIe, SATA PHY support +- Device tree bindings + +#### Ethernet PHY +- **phye/** - Ethernet PHY Framework + +**Status**: 📝 To Be Documented +**Planned Topics**: +- Ethernet PHY management +- MDIO bus +- PHY drivers +- Device tree bindings + +### System Support + +#### Open Firmware (OFW) +- **ofw/** - Device Tree Support + +**Status**: ⚠️ Partially Documented +**Existing**: [../ofw/dtc.md](../ofw/dtc.md) +**Additional Planned Topics**: +- FDT parsing internals +- OFW API reference +- Property reading helpers +- IRQ and address translation + +#### Firmware +- **firmware/** - Firmware Framework + +**Status**: 📝 To Be Documented +**Planned Topics**: +- ARM SCMI support +- Firmware loading +- Device tree bindings + +#### Hardware Cache +- **hwcache/** - Hardware Cache Management + +**Status**: 📝 To Be Documented +**Planned Topics**: +- Cache controller framework +- L2/L3 cache management + +#### Hardware Spinlock +- **hwspinlock/** - Hardware Spinlock + +**Status**: 📝 To Be Documented +**Planned Topics**: +- Hardware spinlock API +- Multi-core synchronization + +## Documentation Standards + +All module documentation should include: + +1. **Introduction** + - General concept overview + - RT-Thread specific implementation + - Use cases + +2. **Kconfig Configuration** + - All configuration options + - Menuconfig location + - Dependencies + +3. **Device Tree Bindings** + - Standard properties + - Example DTS nodes + - Consumer usage examples + +4. **Application Layer API** + - Complete function reference + - Parameter descriptions + - Return values + - Usage examples + +5. **Driver Implementation Guide** + - Key structures + - Operations to implement + - Complete driver examples + - Best practices + +6. **Comprehensive Examples** + - Real-world use cases + - Complete working code + - Error handling patterns + +7. **Troubleshooting** + - Common issues + - Debugging tips + - Performance considerations + +8. **Architecture Diagrams** (SVG) + - Component relationships + - Data flow + - Clear and professional formatting + +## Contributing + +When adding new module documentation: + +1. Follow the structure established by the regulator documentation +2. Include both English and Chinese versions +3. Provide complete, tested code examples +4. Create clear SVG diagrams +5. Cross-reference related modules +6. Update this INDEX.md file + +## Status Legend + +- ✅ Complete - Full documentation available +- ⚠️ Partial - Some documentation exists, needs completion +- 📝 To Be Documented - Planned but not yet created +- 🚧 In Progress - Currently being written + +## Priority Modules + +Based on usage frequency and importance: + +1. ✅ **regulator** - Complete +2. **clk** - High priority (power management) +3. **pinctrl** - High priority (hardware configuration) +4. **reset** - High priority (hardware control) +5. **ofw** - High priority (device tree) +6. **pic** - Medium priority (interrupt handling) +7. **dma** - Medium priority (data transfer) +8. **nvmem** - Medium priority (persistent data) +9. **mailbox** - Medium priority (IPC) +10. **thermal** - Medium priority (power management) + +Other modules follow based on community needs and hardware support requirements. + +## Related Documentation + +- [Device Driver Framework](../framework/device.md) - Basic device model +- [Device Tree Compiler](../ofw/dtc.md) - DTC tool usage +- [I2C Driver](../i2c/i2c.md) - Traditional I2C (compare with DM) +- [SPI Driver](../spi/spi.md) - Traditional SPI (compare with DM) +- [PIN Driver](../pin/pin.md) - Traditional PIN (compare with DM) + +## External References + +- [Device Tree Specification](https://devicetree-specification.readthedocs.io/) +- [Linux Kernel Driver Model](https://www.kernel.org/doc/html/latest/driver-api/driver-model/) +- [Linux Device Tree Bindings](https://www.kernel.org/doc/Documentation/devicetree/bindings/) +- RT-Thread Source: `components/drivers/` diff --git a/documentation/6.components/device-driver/device_driver_model/README.md b/documentation/6.components/device-driver/device_driver_model/README.md new file mode 100644 index 00000000000..43917d000cb --- /dev/null +++ b/documentation/6.components/device-driver/device_driver_model/README.md @@ -0,0 +1,670 @@ +# RT-Thread Device Driver Model (DM) + +## Overview + +The RT-Thread Device Driver Model (DM) is a comprehensive framework that provides a standardized, hierarchical approach to managing hardware devices and their drivers. Enabled through the `RT_USING_DM` configuration option, it introduces a sophisticated device-driver matching mechanism, bus abstraction, and seamless integration with device tree (Open Firmware) support. + +The DM framework significantly enhances RT-Thread's capability to handle complex hardware systems, particularly those requiring dynamic device discovery, power management, clock control, and other hardware resource management. + +### Key Features + +- **Bus-Driver-Device Architecture**: Hierarchical organization of hardware components +- **Device Tree Integration**: Full Open Firmware (OFW) support for hardware description +- **Dynamic Device Management**: Runtime device discovery and driver binding +- **Resource Management**: Unified APIs for clocks, regulators, resets, power domains, etc. +- **Platform Device Model**: Abstraction for memory-mapped and non-discoverable devices +- **Reference Counting**: Safe resource lifecycle management +- **Extensible Framework**: Easy addition of new bus types and device classes + +## Architecture + +### Core Components + +The DM framework consists of several key components: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Application Layer │ +│ (Uses device APIs: clk, regulator, pinctrl, gpio, etc.) │ +└────────────────────┬────────────────────────────────────────┘ + │ +┌────────────────────┴────────────────────────────────────────┐ +│ Device Driver Model (DM) │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ Bus │ │ Driver │ │ Device │ │ Platform │ │ +│ │ Subsys │ │ Subsys │ │ Subsys │ │ Layer │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ OFW (Open Firmware/Device Tree) │ │ +│ │ - FDT parsing - Property reading - IRQ mapping │ │ +│ └──────────────────────────────────────────────────────┘ │ +└────────────────────┬────────────────────────────────────────┘ + │ +┌────────────────────┴────────────────────────────────────────┐ +│ Hardware Resources │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ Clocks │ │Regulator │ │ Resets │ │ GPIOs │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ Pinctrl │ │ DMA │ │ IRQs │ │ Memory │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +#### 1. Bus Subsystem + +The bus subsystem (`rt_bus`) provides the foundation for organizing devices and drivers: + +- **Bus Registration**: Buses are registered with the system and maintain lists of devices and drivers +- **Device-Driver Matching**: Each bus implements a match function to pair compatible drivers with devices +- **Probe/Remove**: Buses coordinate the lifecycle of device-driver bindings +- **Lock Management**: Thread-safe operations on device and driver lists + +**Key Structures**: +```c +struct rt_bus { + struct rt_object parent; + const char *name; + rt_list_t list; + rt_list_t dev_list; /* List of devices on this bus */ + rt_list_t drv_list; /* List of drivers on this bus */ + rt_bool_t (*match)(rt_driver_t drv, rt_device_t dev); + rt_err_t (*probe)(rt_device_t dev); + rt_err_t (*remove)(rt_device_t dev); + rt_err_t (*shutdown)(rt_device_t dev); +}; +``` + +#### 2. Driver Subsystem + +Drivers implement hardware-specific functionality and are bound to compatible devices: + +- **Driver Registration**: Drivers register with a specific bus +- **Device Operations**: Standard operations (init, open, close, read, write, control) +- **Probe/Remove Callbacks**: Initialization and cleanup when bound to devices +- **Reference Counting**: Tracks the number of active device bindings + +**Key Structures**: +```c +struct rt_driver { + struct rt_object parent; + struct rt_bus *bus; + rt_list_t node; + rt_uint32_t ref_count; + + /* Device operations */ + rt_err_t (*init)(rt_device_t dev); + rt_err_t (*open)(rt_device_t dev, rt_uint16_t oflag); + rt_err_t (*close)(rt_device_t dev); + rt_ssize_t (*read)(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size); + rt_ssize_t (*write)(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size); + rt_err_t (*control)(rt_device_t dev, int cmd, void *args); + + /* Lifecycle callbacks */ + int (*probe)(struct rt_device *dev); + int (*remove)(struct rt_device *dev); + int (*shutdown)(struct rt_device *dev); +}; +``` + +#### 3. Platform Device Model + +The platform device model is the most common usage of DM in RT-Thread, handling memory-mapped devices: + +- **Platform Bus**: A virtual bus for devices not on a physical bus +- **Device Tree Integration**: Automatic device creation from FDT nodes +- **Resource Abstraction**: Unified access to memory regions, IRQs, clocks, etc. +- **Driver Binding**: Automatic matching based on compatible strings + +**Key Structures**: +```c +struct rt_platform_device { + struct rt_device parent; + int dev_id; + const char *name; + const struct rt_ofw_node_id *id; + void *priv; +}; + +struct rt_platform_driver { + struct rt_driver parent; + const char *name; + const struct rt_ofw_node_id *ids; /* Compatible strings */ + + rt_err_t (*probe)(struct rt_platform_device *pdev); + rt_err_t (*remove)(struct rt_platform_device *pdev); + rt_err_t (*shutdown)(struct rt_platform_device *pdev); +}; +``` + +#### 4. OFW (Open Firmware) Support + +The OFW subsystem provides device tree support: + +- **FDT Parsing**: Flattened Device Tree (FDT) binary format parsing +- **Device Node Management**: Hierarchical device tree node representation +- **Property Reading**: APIs to read device tree properties +- **Resource Mapping**: Translation of DT resources to system resources (IRQ, memory, etc.) + +### Device Lifecycle + +The typical lifecycle of a device in the DM framework: + +``` + Device Tree Platform Driver + │ │ │ + │ Parse DT │ │ + ├──────────────────>│ │ + │ │ │ + │ Create Platform │ │ + │ Device │ │ + │ │ │ + │ Register Device │ │ + │ on Platform Bus │ │ + │ │ │ + │ │ Match Device │ + │ │ with Driver │ + │ ├─────────────────>│ + │ │ │ + │ │ Driver Probe │ + │ │<─────────────────┤ + │ │ │ + │ │ Initialize HW │ + │ │ │ + │ │ Register APIs │ + │ │ │ + │ │ RUNNING │ + │ │ │ + │ │ Driver Remove │ + │ ├─────────────────>│ + │ │ │ + │ │ Cleanup │ + │ │<─────────────────┤ + │ │ │ +``` + +## Kconfig Configuration + +### Main DM Option + +The Device Driver Model is enabled through the main Kconfig option: + +```kconfig +config RT_USING_DM + bool "Enable device driver model with device tree" + default n + help + Enable device driver model with device tree (FDT). It will use more memory + to parse and support device tree feature. +``` + +**Location in menuconfig**: `RT-Thread Components → Device Drivers → Enable device driver model with device tree` + +### Related Options + +#### Device Bus +```kconfig +config RT_USING_DEV_BUS + bool "Using Device Bus device drivers" + default y if RT_USING_SMART + default n +``` + +#### Open Firmware (OFW) +```kconfig +menuconfig RT_USING_OFW + bool "Using Open Firmware (OFW)" + select RT_USING_ADT + select RT_USING_ADT_REF + select RT_USING_ADT_BITMAP + select RT_USING_MEMBLOCK + depends on RT_USING_DM + default n +``` + +**Location**: `RT-Thread Components → Device Drivers → Using Open Firmware (OFW)` + +#### Builtin FDT +```kconfig +config RT_USING_BUILTIN_FDT + bool "Using builtin fdt in kernel" + depends on RT_USING_OFW + default n + +config RT_BUILTIN_FDT_PATH + string "Builtin fdt path, will rebuild if have dts" + depends on RT_USING_BUILTIN_FDT + default "rtthread.dtb" +``` + +## Device Tree Integration + +### Device Tree Basics + +When `RT_USING_DM` and `RT_USING_OFW` are enabled, RT-Thread can parse and use device tree (FDT) to describe hardware: + +#### Example Device Tree Node + +```dts +/ { + compatible = "myboard,example"; + #address-cells = <1>; + #size-cells = <1>; + + clocks { + osc24M: osc24M_clk { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <24000000>; + clock-output-names = "osc24M"; + }; + }; + + soc { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + uart0: serial@10000000 { + compatible = "myvendor,uart"; + reg = <0x10000000 0x1000>; + interrupts = <32>; + clocks = <&osc24M>; + clock-names = "baudclk"; + status = "okay"; + }; + }; + + regulators { + vcc_3v3: regulator-vcc-3v3 { + compatible = "regulator-fixed"; + regulator-name = "vcc-3v3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + regulator-boot-on; + }; + }; +}; +``` + +### Using Device Tree in Drivers + +#### Getting Device Node + +```c +#include + +struct rt_ofw_node *np = dev->ofw_node; +``` + +#### Reading Properties + +```c +/* Read string property */ +const char *status; +rt_ofw_prop_read_string(np, "status", &status); + +/* Read u32 property */ +rt_uint32_t freq; +rt_ofw_prop_read_u32(np, "clock-frequency", &freq); + +/* Read array */ +rt_uint32_t reg[2]; +rt_ofw_prop_read_u32_array(np, "reg", reg, 2); +``` + +#### Getting Resources + +```c +/* Get memory resources */ +rt_uint64_t addr, size; +rt_dm_dev_get_address(dev, 0, &addr, &size); + +/* Get IRQ */ +int irq = rt_dm_dev_get_irq(dev, 0); + +/* Map MMIO */ +void *base = rt_dm_dev_iomap(dev, 0); + +/* Get clock */ +struct rt_clk *clk = rt_clk_get_by_name(dev, "baudclk"); +``` + +## DM-Dependent Modules + +The Device Driver Model supports numerous hardware subsystems. Each module provides standardized APIs for both application layer and driver implementation: + +| Module | Description | Documentation | +|--------|-------------|---------------| +| **clk** | Clock management framework | [clk.md](clk/README.md) | +| **regulator** | Voltage/current regulation | [regulator.md](regulator/README.md) | +| **pinctrl** | Pin multiplexing and configuration | [pinctrl.md](pinctrl/README.md) | +| **reset** | Reset controller management | [reset.md](reset/README.md) | +| **pmdomain** | Power domain management | [pmdomain.md](pmdomain/README.md) | +| **pic** | Platform interrupt controller | [pic.md](pic/README.md) | +| **nvmem** | Non-volatile memory framework | [nvmem.md](nvmem/README.md) | +| **mailbox** | Mailbox/doorbell communication | [mailbox.md](mailbox/README.md) | +| **thermal** | Thermal management | [thermal.md](thermal/README.md) | +| **mfd** | Multi-function device | [mfd.md](mfd/README.md) | +| **dma** | DMA engine management | [dma.md](dma/README.md) | +| **iio** | Industrial I/O subsystem | [iio.md](iio/README.md) | +| **phy** | PHY (physical layer) framework | [phy.md](phy/README.md) | +| **phye** | Ethernet PHY framework | [phye.md](phye/README.md) | +| **pci** | PCI bus support | [pci.md](pci/README.md) | +| **ofw** | Open Firmware/Device Tree | [ofw.md](ofw/README.md) | + +## Common APIs + +### Device Management + +```c +/* Find device by master_id and device_id */ +rt_device_t rt_dm_device_find(int master_id, int device_id); + +/* Set device name */ +int rt_dm_dev_set_name(rt_device_t dev, const char *format, ...); +int rt_dm_dev_set_name_auto(rt_device_t dev, const char *prefix); + +/* Get device name */ +const char *rt_dm_dev_get_name(rt_device_t dev); +int rt_dm_dev_get_name_id(rt_device_t dev); +``` + +### Device Tree APIs + +```c +/* Get address resources */ +int rt_dm_dev_get_address_count(rt_device_t dev); +rt_err_t rt_dm_dev_get_address(rt_device_t dev, int index, + rt_uint64_t *out_address, rt_uint64_t *out_size); +rt_err_t rt_dm_dev_get_address_by_name(rt_device_t dev, const char *name, + rt_uint64_t *out_address, rt_uint64_t *out_size); + +/* Map MMIO regions */ +void *rt_dm_dev_iomap(rt_device_t dev, int index); +void *rt_dm_dev_iomap_by_name(rt_device_t dev, const char *name); + +/* Get IRQ resources */ +int rt_dm_dev_get_irq_count(rt_device_t dev); +int rt_dm_dev_get_irq(rt_device_t dev, int index); +int rt_dm_dev_get_irq_by_name(rt_device_t dev, const char *name); +``` + +### Property Reading + +```c +/* Read various property types */ +int rt_dm_dev_prop_read_u8_array_index(rt_device_t dev, const char *propname, + int index, int nr, rt_uint8_t *out_values); +int rt_dm_dev_prop_read_u32_array_index(rt_device_t dev, const char *propname, + int index, int nr, rt_uint32_t *out_values); +int rt_dm_dev_prop_read_string_index(rt_device_t dev, const char *propname, + int index, const char **out_string); + +/* Simplified single-value reading */ +#define rt_dm_dev_prop_read_u32(dev, propname, out_value) \ + rt_dm_dev_prop_read_u32_array_index(dev, propname, 0, 1, out_value) +``` + +### Platform Device APIs + +```c +/* Register platform driver */ +rt_err_t rt_platform_driver_register(struct rt_platform_driver *pdrv); + +/* Register platform device */ +rt_err_t rt_platform_device_register(struct rt_platform_device *pdev); + +/* Create platform device from device tree */ +rt_err_t rt_platform_ofw_device_probe_child(struct rt_ofw_node *np); +``` + +## Writing a Platform Driver + +### Basic Driver Structure + +```c +#include +#include +#include + +struct mydevice_data { + void *base; + int irq; + struct rt_clk *clk; +}; + +static rt_err_t mydevice_probe(struct rt_platform_device *pdev) +{ + struct mydevice_data *data; + struct rt_device *dev = &pdev->parent; + + /* Allocate private data */ + data = rt_calloc(1, sizeof(*data)); + if (!data) + return -RT_ENOMEM; + + /* Map MMIO region */ + data->base = rt_dm_dev_iomap(dev, 0); + if (!data->base) { + rt_free(data); + return -RT_ERROR; + } + + /* Get IRQ */ + data->irq = rt_dm_dev_get_irq(dev, 0); + + /* Get clock */ + data->clk = rt_clk_get_by_name(dev, "baudclk"); + if (data->clk) { + rt_clk_prepare_enable(data->clk); + } + + /* Store private data */ + pdev->priv = data; + + /* Initialize hardware */ + /* ... */ + + return RT_EOK; +} + +static rt_err_t mydevice_remove(struct rt_platform_device *pdev) +{ + struct mydevice_data *data = pdev->priv; + + /* Cleanup hardware */ + /* ... */ + + /* Release resources */ + if (data->clk) { + rt_clk_disable_unprepare(data->clk); + rt_clk_put(data->clk); + } + + rt_free(data); + + return RT_EOK; +} + +static const struct rt_ofw_node_id mydevice_ofw_ids[] = { + { .compatible = "myvendor,mydevice" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver mydevice_driver = { + .name = "mydevice", + .ids = mydevice_ofw_ids, + .probe = mydevice_probe, + .remove = mydevice_remove, +}; + +/* Auto-register driver at boot */ +RT_PLATFORM_DRIVER_EXPORT(mydevice_driver); +``` + +### Device Tree Binding + +```dts +mydev: mydevice@10000000 { + compatible = "myvendor,mydevice"; + reg = <0x10000000 0x1000>; + interrupts = <32>; + clocks = <&osc24M>; + clock-names = "baudclk"; + status = "okay"; +}; +``` + +## Best Practices + +### 1. Resource Management + +- Always check return values when getting resources +- Use reference counting for shared resources (clocks, regulators) +- Clean up resources in the remove callback +- Handle probe deferral for dependencies + +### 2. Device Tree + +- Use standard bindings when available +- Document custom properties in driver documentation +- Use labels for phandle references +- Keep device tree and driver in sync + +### 3. Error Handling + +```c +static rt_err_t mydevice_probe(struct rt_platform_device *pdev) +{ + rt_err_t ret; + struct mydevice_data *data; + + data = rt_calloc(1, sizeof(*data)); + if (!data) + return -RT_ENOMEM; + + data->base = rt_dm_dev_iomap(&pdev->parent, 0); + if (!data->base) { + ret = -RT_ERROR; + goto err_free; + } + + data->clk = rt_clk_get_by_name(&pdev->parent, "baudclk"); + if (!data->clk) { + ret = -RT_ERROR; + goto err_unmap; + } + + ret = rt_clk_prepare_enable(data->clk); + if (ret) + goto err_put_clk; + + /* Success */ + pdev->priv = data; + return RT_EOK; + +err_put_clk: + rt_clk_put(data->clk); +err_unmap: + rt_iounmap(data->base); +err_free: + rt_free(data); + return ret; +} +``` + +### 4. Thread Safety + +- Use spinlocks for hardware register access +- Use mutexes for long operations +- Consider interrupt context in ISRs +- Protect shared data structures + +## Performance Considerations + +### Memory Usage + +DM adds memory overhead: +- Device tree parsing requires memory for FDT and node structures +- Each device/driver adds object overhead +- Additional metadata for resource management + +**Recommendations**: +- Only enable DM for systems that need it +- Consider memory constraints when designing device trees +- Use static allocation where possible + +### Boot Time + +- Device tree parsing happens early in boot +- Driver probing can be deferred +- Parallel initialization where possible + +## Debugging + +### Enable Debug Output + +```c +#define DBG_TAG "mydriver" +#define DBG_LVL DBG_INFO +#include + +LOG_D("Debug message"); +LOG_I("Info message"); +LOG_W("Warning message"); +LOG_E("Error message"); +``` + +### DM-Specific Debugging + +``` +/* In menuconfig, enable OFW debugging */ +RT-Thread Components → Device Drivers → Using Open Firmware (OFW) +``` + +### Common Issues + +1. **Device not probing**: Check compatible string matches +2. **Resource not found**: Verify device tree properties +3. **Probe deferral loop**: Check for circular dependencies +4. **Memory access fault**: Verify MMIO mapping is correct + +## Migration Guide + +### From Traditional RT-Thread Drivers + +Traditional RT-Thread drivers: +```c +rt_device_t dev = rt_device_find("uart0"); +rt_device_open(dev, RT_DEVICE_OFLAG_RDWR); +``` + +With DM: +```c +/* Device automatically created from device tree */ +rt_device_t dev = rt_device_find("uart0"); +rt_device_open(dev, RT_DEVICE_OFLAG_RDWR); +/* API remains the same for users */ +``` + +Driver implementation changes: +- Add platform driver structure +- Implement probe/remove callbacks +- Use DM APIs for resource management +- Add device tree binding + +## Related Documentation + +- [Open Firmware (OFW) Documentation](../ofw/dtc.md) +- [Device Framework](../framework/device.md) +- For module-specific documentation, see the links in the DM-Dependent Modules section + +## References + +- [Device Tree Specification](https://devicetree-specification.readthedocs.io/) +- [Linux Kernel Driver Model](https://www.kernel.org/doc/html/latest/driver-api/driver-model/index.html) +- RT-Thread Source Code: `components/drivers/core/`, `components/drivers/ofw/` diff --git a/documentation/6.components/device-driver/device_driver_model/README_zh.md b/documentation/6.components/device-driver/device_driver_model/README_zh.md new file mode 100644 index 00000000000..5d9870479ac --- /dev/null +++ b/documentation/6.components/device-driver/device_driver_model/README_zh.md @@ -0,0 +1,670 @@ +# RT-Thread 设备驱动模型 (DM) + +## 概述 + +RT-Thread 设备驱动模型(Device Driver Model,简称 DM)是一个全面的框架,提供了标准化的层次化方法来管理硬件设备及其驱动程序。通过 `RT_USING_DM` 配置选项启用,它引入了复杂的设备-驱动匹配机制、总线抽象以及与设备树(Open Firmware)的无缝集成。 + +DM 框架显著增强了 RT-Thread 处理复杂硬件系统的能力,特别是那些需要动态设备发现、电源管理、时钟控制和其他硬件资源管理的系统。 + +### 主要特性 + +- **总线-驱动-设备架构**:硬件组件的层次化组织 +- **设备树集成**:完整的 Open Firmware (OFW) 支持用于硬件描述 +- **动态设备管理**:运行时设备发现和驱动绑定 +- **资源管理**:统一的时钟、电压调节器、复位、电源域等 API +- **平台设备模型**:内存映射和不可发现设备的抽象 +- **引用计数**:安全的资源生命周期管理 +- **可扩展框架**:轻松添加新的总线类型和设备类 + +## 架构 + +### 核心组件 + +DM 框架由几个关键组件组成: + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 应用层 │ +│ (使用设备 API: clk, regulator, pinctrl, gpio 等) │ +└────────────────────┬────────────────────────────────────────┘ + │ +┌────────────────────┴────────────────────────────────────────┐ +│ 设备驱动模型 (DM) │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ 总线 │ │ 驱动 │ │ 设备 │ │ 平台 │ │ +│ │ 子系统 │ │ 子系统 │ │ 子系统 │ │ 层 │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────────┐ │ +│ │ OFW (开放固件/设备树) │ │ +│ │ - FDT 解析 - 属性读取 - IRQ 映射 │ │ +│ └──────────────────────────────────────────────────────┘ │ +└────────────────────┬────────────────────────────────────────┘ + │ +┌────────────────────┴────────────────────────────────────────┐ +│ 硬件资源 │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ 时钟 │ │ 调节器 │ │ 复位 │ │ GPIO │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ +│ │ 引脚控制 │ │ DMA │ │ 中断 │ │ 内存 │ │ +│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +#### 1. 总线子系统 + +总线子系统(`rt_bus`)为组织设备和驱动程序提供了基础: + +- **总线注册**:总线在系统中注册并维护设备和驱动程序列表 +- **设备-驱动匹配**:每个总线实现匹配函数以配对兼容的驱动程序和设备 +- **探测/移除**:总线协调设备-驱动绑定的生命周期 +- **锁管理**:设备和驱动程序列表的线程安全操作 + +**核心结构**: +```c +struct rt_bus { + struct rt_object parent; + const char *name; + rt_list_t list; + rt_list_t dev_list; /* 此总线上的设备列表 */ + rt_list_t drv_list; /* 此总线上的驱动程序列表 */ + rt_bool_t (*match)(rt_driver_t drv, rt_device_t dev); + rt_err_t (*probe)(rt_device_t dev); + rt_err_t (*remove)(rt_device_t dev); + rt_err_t (*shutdown)(rt_device_t dev); +}; +``` + +#### 2. 驱动子系统 + +驱动程序实现硬件特定功能并绑定到兼容的设备: + +- **驱动注册**:驱动程序向特定总线注册 +- **设备操作**:标准操作(init、open、close、read、write、control) +- **探测/移除回调**:绑定到设备时的初始化和清理 +- **引用计数**:跟踪活动设备绑定的数量 + +**核心结构**: +```c +struct rt_driver { + struct rt_object parent; + struct rt_bus *bus; + rt_list_t node; + rt_uint32_t ref_count; + + /* 设备操作 */ + rt_err_t (*init)(rt_device_t dev); + rt_err_t (*open)(rt_device_t dev, rt_uint16_t oflag); + rt_err_t (*close)(rt_device_t dev); + rt_ssize_t (*read)(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size); + rt_ssize_t (*write)(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size); + rt_err_t (*control)(rt_device_t dev, int cmd, void *args); + + /* 生命周期回调 */ + int (*probe)(struct rt_device *dev); + int (*remove)(struct rt_device *dev); + int (*shutdown)(struct rt_device *dev); +}; +``` + +#### 3. 平台设备模型 + +平台设备模型是 RT-Thread 中 DM 最常见的用法,处理内存映射设备: + +- **平台总线**:用于非物理总线上设备的虚拟总线 +- **设备树集成**:从 FDT 节点自动创建设备 +- **资源抽象**:统一访问内存区域、中断、时钟等 +- **驱动绑定**:基于 compatible 字符串的自动匹配 + +**核心结构**: +```c +struct rt_platform_device { + struct rt_device parent; + int dev_id; + const char *name; + const struct rt_ofw_node_id *id; + void *priv; +}; + +struct rt_platform_driver { + struct rt_driver parent; + const char *name; + const struct rt_ofw_node_id *ids; /* Compatible 字符串 */ + + rt_err_t (*probe)(struct rt_platform_device *pdev); + rt_err_t (*remove)(struct rt_platform_device *pdev); + rt_err_t (*shutdown)(struct rt_platform_device *pdev); +}; +``` + +#### 4. OFW(开放固件)支持 + +OFW 子系统提供设备树支持: + +- **FDT 解析**:扁平设备树(FDT)二进制格式解析 +- **设备节点管理**:层次化设备树节点表示 +- **属性读取**:读取设备树属性的 API +- **资源映射**:将 DT 资源转换为系统资源(IRQ、内存等) + +### 设备生命周期 + +DM 框架中设备的典型生命周期: + +``` + 设备树 平台 驱动 + │ │ │ + │ 解析 DT │ │ + ├──────────────────>│ │ + │ │ │ + │ 创建平台 │ │ + │ 设备 │ │ + │ │ │ + │ 在平台总线上 │ │ + │ 注册设备 │ │ + │ │ │ + │ │ 匹配设备 │ + │ │ 与驱动 │ + │ ├─────────────────>│ + │ │ │ + │ │ 驱动探测 │ + │ │<─────────────────┤ + │ │ │ + │ │ 初始化硬件 │ + │ │ │ + │ │ 注册 API │ + │ │ │ + │ │ 运行中 │ + │ │ │ + │ │ 驱动移除 │ + │ ├─────────────────>│ + │ │ │ + │ │ 清理 │ + │ │<─────────────────┤ + │ │ │ +``` + +## Kconfig 配置 + +### 主 DM 选项 + +通过主 Kconfig 选项启用设备驱动模型: + +```kconfig +config RT_USING_DM + bool "Enable device driver model with device tree" + default n + help + Enable device driver model with device tree (FDT). It will use more memory + to parse and support device tree feature. +``` + +**在 menuconfig 中的位置**:`RT-Thread Components → Device Drivers → Enable device driver model with device tree` + +### 相关选项 + +#### 设备总线 +```kconfig +config RT_USING_DEV_BUS + bool "Using Device Bus device drivers" + default y if RT_USING_SMART + default n +``` + +#### 开放固件(OFW) +```kconfig +menuconfig RT_USING_OFW + bool "Using Open Firmware (OFW)" + select RT_USING_ADT + select RT_USING_ADT_REF + select RT_USING_ADT_BITMAP + select RT_USING_MEMBLOCK + depends on RT_USING_DM + default n +``` + +**位置**:`RT-Thread Components → Device Drivers → Using Open Firmware (OFW)` + +#### 内置 FDT +```kconfig +config RT_USING_BUILTIN_FDT + bool "Using builtin fdt in kernel" + depends on RT_USING_OFW + default n + +config RT_BUILTIN_FDT_PATH + string "Builtin fdt path, will rebuild if have dts" + depends on RT_USING_BUILTIN_FDT + default "rtthread.dtb" +``` + +## 设备树集成 + +### 设备树基础 + +当启用 `RT_USING_DM` 和 `RT_USING_OFW` 时,RT-Thread 可以解析和使用设备树(FDT)来描述硬件: + +#### 设备树节点示例 + +```dts +/ { + compatible = "myboard,example"; + #address-cells = <1>; + #size-cells = <1>; + + clocks { + osc24M: osc24M_clk { + #clock-cells = <0>; + compatible = "fixed-clock"; + clock-frequency = <24000000>; + clock-output-names = "osc24M"; + }; + }; + + soc { + compatible = "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + ranges; + + uart0: serial@10000000 { + compatible = "myvendor,uart"; + reg = <0x10000000 0x1000>; + interrupts = <32>; + clocks = <&osc24M>; + clock-names = "baudclk"; + status = "okay"; + }; + }; + + regulators { + vcc_3v3: regulator-vcc-3v3 { + compatible = "regulator-fixed"; + regulator-name = "vcc-3v3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + regulator-boot-on; + }; + }; +}; +``` + +### 在驱动中使用设备树 + +#### 获取设备节点 + +```c +#include + +struct rt_ofw_node *np = dev->ofw_node; +``` + +#### 读取属性 + +```c +/* 读取字符串属性 */ +const char *status; +rt_ofw_prop_read_string(np, "status", &status); + +/* 读取 u32 属性 */ +rt_uint32_t freq; +rt_ofw_prop_read_u32(np, "clock-frequency", &freq); + +/* 读取数组 */ +rt_uint32_t reg[2]; +rt_ofw_prop_read_u32_array(np, "reg", reg, 2); +``` + +#### 获取资源 + +```c +/* 获取内存资源 */ +rt_uint64_t addr, size; +rt_dm_dev_get_address(dev, 0, &addr, &size); + +/* 获取 IRQ */ +int irq = rt_dm_dev_get_irq(dev, 0); + +/* 映射 MMIO */ +void *base = rt_dm_dev_iomap(dev, 0); + +/* 获取时钟 */ +struct rt_clk *clk = rt_clk_get_by_name(dev, "baudclk"); +``` + +## DM 依赖模块 + +设备驱动模型支持众多硬件子系统。每个模块为应用层和驱动实现提供标准化的 API: + +| 模块 | 描述 | 文档 | +|------|------|------| +| **clk** | 时钟管理框架 | [clk.md](clk/README_zh.md) | +| **regulator** | 电压/电流调节 | [regulator.md](regulator/README_zh.md) | +| **pinctrl** | 引脚复用和配置 | [pinctrl.md](pinctrl/README_zh.md) | +| **reset** | 复位控制器管理 | [reset.md](reset/README_zh.md) | +| **pmdomain** | 电源域管理 | [pmdomain.md](pmdomain/README_zh.md) | +| **pic** | 平台中断控制器 | [pic.md](pic/README_zh.md) | +| **nvmem** | 非易失性内存框架 | [nvmem.md](nvmem/README_zh.md) | +| **mailbox** | 邮箱/门铃通信 | [mailbox.md](mailbox/README_zh.md) | +| **thermal** | 热管理 | [thermal.md](thermal/README_zh.md) | +| **mfd** | 多功能设备 | [mfd.md](mfd/README_zh.md) | +| **dma** | DMA 引擎管理 | [dma.md](dma/README_zh.md) | +| **iio** | 工业 I/O 子系统 | [iio.md](iio/README_zh.md) | +| **phy** | PHY(物理层)框架 | [phy.md](phy/README_zh.md) | +| **phye** | 以太网 PHY 框架 | [phye.md](phye/README_zh.md) | +| **pci** | PCI 总线支持 | [pci.md](pci/README_zh.md) | +| **ofw** | 开放固件/设备树 | [ofw.md](ofw/README_zh.md) | + +## 常用 API + +### 设备管理 + +```c +/* 通过 master_id 和 device_id 查找设备 */ +rt_device_t rt_dm_device_find(int master_id, int device_id); + +/* 设置设备名称 */ +int rt_dm_dev_set_name(rt_device_t dev, const char *format, ...); +int rt_dm_dev_set_name_auto(rt_device_t dev, const char *prefix); + +/* 获取设备名称 */ +const char *rt_dm_dev_get_name(rt_device_t dev); +int rt_dm_dev_get_name_id(rt_device_t dev); +``` + +### 设备树 API + +```c +/* 获取地址资源 */ +int rt_dm_dev_get_address_count(rt_device_t dev); +rt_err_t rt_dm_dev_get_address(rt_device_t dev, int index, + rt_uint64_t *out_address, rt_uint64_t *out_size); +rt_err_t rt_dm_dev_get_address_by_name(rt_device_t dev, const char *name, + rt_uint64_t *out_address, rt_uint64_t *out_size); + +/* 映射 MMIO 区域 */ +void *rt_dm_dev_iomap(rt_device_t dev, int index); +void *rt_dm_dev_iomap_by_name(rt_device_t dev, const char *name); + +/* 获取 IRQ 资源 */ +int rt_dm_dev_get_irq_count(rt_device_t dev); +int rt_dm_dev_get_irq(rt_device_t dev, int index); +int rt_dm_dev_get_irq_by_name(rt_device_t dev, const char *name); +``` + +### 属性读取 + +```c +/* 读取各种属性类型 */ +int rt_dm_dev_prop_read_u8_array_index(rt_device_t dev, const char *propname, + int index, int nr, rt_uint8_t *out_values); +int rt_dm_dev_prop_read_u32_array_index(rt_device_t dev, const char *propname, + int index, int nr, rt_uint32_t *out_values); +int rt_dm_dev_prop_read_string_index(rt_device_t dev, const char *propname, + int index, const char **out_string); + +/* 简化的单值读取 */ +#define rt_dm_dev_prop_read_u32(dev, propname, out_value) \ + rt_dm_dev_prop_read_u32_array_index(dev, propname, 0, 1, out_value) +``` + +### 平台设备 API + +```c +/* 注册平台驱动 */ +rt_err_t rt_platform_driver_register(struct rt_platform_driver *pdrv); + +/* 注册平台设备 */ +rt_err_t rt_platform_device_register(struct rt_platform_device *pdev); + +/* 从设备树创建平台设备 */ +rt_err_t rt_platform_ofw_device_probe_child(struct rt_ofw_node *np); +``` + +## 编写平台驱动 + +### 基本驱动结构 + +```c +#include +#include +#include + +struct mydevice_data { + void *base; + int irq; + struct rt_clk *clk; +}; + +static rt_err_t mydevice_probe(struct rt_platform_device *pdev) +{ + struct mydevice_data *data; + struct rt_device *dev = &pdev->parent; + + /* 分配私有数据 */ + data = rt_calloc(1, sizeof(*data)); + if (!data) + return -RT_ENOMEM; + + /* 映射 MMIO 区域 */ + data->base = rt_dm_dev_iomap(dev, 0); + if (!data->base) { + rt_free(data); + return -RT_ERROR; + } + + /* 获取 IRQ */ + data->irq = rt_dm_dev_get_irq(dev, 0); + + /* 获取时钟 */ + data->clk = rt_clk_get_by_name(dev, "baudclk"); + if (data->clk) { + rt_clk_prepare_enable(data->clk); + } + + /* 存储私有数据 */ + pdev->priv = data; + + /* 初始化硬件 */ + /* ... */ + + return RT_EOK; +} + +static rt_err_t mydevice_remove(struct rt_platform_device *pdev) +{ + struct mydevice_data *data = pdev->priv; + + /* 清理硬件 */ + /* ... */ + + /* 释放资源 */ + if (data->clk) { + rt_clk_disable_unprepare(data->clk); + rt_clk_put(data->clk); + } + + rt_free(data); + + return RT_EOK; +} + +static const struct rt_ofw_node_id mydevice_ofw_ids[] = { + { .compatible = "myvendor,mydevice" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver mydevice_driver = { + .name = "mydevice", + .ids = mydevice_ofw_ids, + .probe = mydevice_probe, + .remove = mydevice_remove, +}; + +/* 启动时自动注册驱动 */ +RT_PLATFORM_DRIVER_EXPORT(mydevice_driver); +``` + +### 设备树绑定 + +```dts +mydev: mydevice@10000000 { + compatible = "myvendor,mydevice"; + reg = <0x10000000 0x1000>; + interrupts = <32>; + clocks = <&osc24M>; + clock-names = "baudclk"; + status = "okay"; +}; +``` + +## 最佳实践 + +### 1. 资源管理 + +- 获取资源时始终检查返回值 +- 对共享资源(时钟、调节器)使用引用计数 +- 在移除回调中清理资源 +- 处理依赖项的探测延迟 + +### 2. 设备树 + +- 尽可能使用标准绑定 +- 在驱动文档中记录自定义属性 +- 使用标签进行 phandle 引用 +- 保持设备树和驱动同步 + +### 3. 错误处理 + +```c +static rt_err_t mydevice_probe(struct rt_platform_device *pdev) +{ + rt_err_t ret; + struct mydevice_data *data; + + data = rt_calloc(1, sizeof(*data)); + if (!data) + return -RT_ENOMEM; + + data->base = rt_dm_dev_iomap(&pdev->parent, 0); + if (!data->base) { + ret = -RT_ERROR; + goto err_free; + } + + data->clk = rt_clk_get_by_name(&pdev->parent, "baudclk"); + if (!data->clk) { + ret = -RT_ERROR; + goto err_unmap; + } + + ret = rt_clk_prepare_enable(data->clk); + if (ret) + goto err_put_clk; + + /* 成功 */ + pdev->priv = data; + return RT_EOK; + +err_put_clk: + rt_clk_put(data->clk); +err_unmap: + rt_iounmap(data->base); +err_free: + rt_free(data); + return ret; +} +``` + +### 4. 线程安全 + +- 对硬件寄存器访问使用自旋锁 +- 对长操作使用互斥锁 +- 在 ISR 中考虑中断上下文 +- 保护共享数据结构 + +## 性能考虑 + +### 内存使用 + +DM 增加了内存开销: +- 设备树解析需要 FDT 和节点结构的内存 +- 每个设备/驱动添加对象开销 +- 资源管理的额外元数据 + +**建议**: +- 仅为需要它的系统启用 DM +- 设计设备树时考虑内存限制 +- 尽可能使用静态分配 + +### 启动时间 + +- 设备树解析在启动早期进行 +- 驱动探测可以延迟 +- 尽可能并行初始化 + +## 调试 + +### 启用调试输出 + +```c +#define DBG_TAG "mydriver" +#define DBG_LVL DBG_INFO +#include + +LOG_D("调试消息"); +LOG_I("信息消息"); +LOG_W("警告消息"); +LOG_E("错误消息"); +``` + +### DM 特定调试 + +``` +/* 在 menuconfig 中启用 OFW 调试 */ +RT-Thread Components → Device Drivers → Using Open Firmware (OFW) +``` + +### 常见问题 + +1. **设备未探测**:检查 compatible 字符串是否匹配 +2. **资源未找到**:验证设备树属性 +3. **探测延迟循环**:检查循环依赖 +4. **内存访问错误**:验证 MMIO 映射正确 + +## 迁移指南 + +### 从传统 RT-Thread 驱动 + +传统 RT-Thread 驱动: +```c +rt_device_t dev = rt_device_find("uart0"); +rt_device_open(dev, RT_DEVICE_OFLAG_RDWR); +``` + +使用 DM: +```c +/* 设备从设备树自动创建 */ +rt_device_t dev = rt_device_find("uart0"); +rt_device_open(dev, RT_DEVICE_OFLAG_RDWR); +/* 用户 API 保持不变 */ +``` + +驱动实现变化: +- 添加平台驱动结构 +- 实现探测/移除回调 +- 使用 DM API 进行资源管理 +- 添加设备树绑定 + +## 相关文档 + +- [开放固件(OFW)文档](../ofw/dtc.md) +- [设备框架](../framework/device.md) +- 有关模块特定文档,请参阅 DM 依赖模块部分中的链接 + +## 参考资料 + +- [设备树规范](https://devicetree-specification.readthedocs.io/) +- [Linux 内核驱动模型](https://www.kernel.org/doc/html/latest/driver-api/driver-model/index.html) +- RT-Thread 源代码:`components/drivers/core/`、`components/drivers/ofw/` diff --git a/documentation/6.components/device-driver/device_driver_model/SUMMARY.md b/documentation/6.components/device-driver/device_driver_model/SUMMARY.md new file mode 100644 index 00000000000..90ab4442c7e --- /dev/null +++ b/documentation/6.components/device-driver/device_driver_model/SUMMARY.md @@ -0,0 +1,464 @@ +# Device Driver Model Documentation - Project Summary + +## Overview + +This document summarizes the work completed for creating comprehensive documentation for RT-Thread's Device Driver Model (DM) and provides guidance for completing the remaining modules. + +## Completed Work + +### 1. Core DM Documentation + +#### Main Documentation Files (English) +**File**: `README.md` + +**Content** (20,353 characters): +- Comprehensive overview of RT-Thread Device Driver Model +- Architecture explanation with component diagrams (text-based) +- Core components: + - Bus subsystem (rt_bus) + - Driver subsystem (rt_driver) + - Platform device/driver model + - OFW (Open Firmware/Device Tree) support +- Complete Kconfig configuration documentation +- Device tree integration guide with examples +- Common DM APIs reference +- Platform driver writing guide with complete example +- Best practices and debugging tips +- Migration guide from traditional RT-Thread drivers +- Performance considerations + +#### Main Documentation Files (Chinese) +**File**: `README_zh.md` + +**Content** (15,840 characters): +- Complete Chinese translation of core DM concepts +- All sections translated with cultural and technical accuracy +- Examples adapted for Chinese-speaking developers + +### 2. Module Documentation Template + +#### Regulator Framework (Complete) + +**English Documentation**: `regulator/README.md` (34,634 characters) + +**Sections**: +1. **Introduction** + - General voltage regulator concepts + - RT-Thread implementation overview + - Architecture diagram (text-based) + +2. **Kconfig Configuration** + - Main regulator framework option + - Fixed regulator driver + - GPIO regulator driver + - SCMI regulator driver + - Complete menuconfig path documentation + +3. **Device Tree Bindings** + - All standard regulator properties documented + - Fixed regulator examples (simple, GPIO-controlled, chained) + - GPIO regulator examples + - Consumer usage examples (UART, MMC/SD) + +4. **Application Layer API** + - Complete API reference with all functions + - rt_regulator_get/put + - rt_regulator_enable/disable + - rt_regulator_set_voltage/get_voltage + - rt_regulator_set_mode/get_mode + - Notifier API + - Each function includes: + - Function signature + - Parameter descriptions + - Return values + - Detailed notes + - Code examples + +5. **Complete Application Example** + - Real-world MMC/SD host controller driver + - ~150 lines of well-commented code + - Demonstrates: + - Resource acquisition + - Power sequencing + - Voltage switching + - Error handling patterns + +6. **Driver Implementation Guide** + - Key structures explained (rt_regulator_node, rt_regulator_param, rt_regulator_ops) + - Complete driver example (~150 lines) + - Device tree parsing + - Registration process + - Best practices + +7. **Additional Sections** + - Best practices for consumers and providers + - Common usage patterns + - Troubleshooting guide + - Performance considerations + - Related modules + - References + +**Chinese Documentation**: `regulator/README_zh.md` (16,956 characters) +- Complete translation of all sections +- Culturally appropriate examples +- Technical terminology properly translated + +### 3. Navigation and Status + +**File**: `INDEX.md` (8,627 characters) + +**Content**: +- Complete module inventory (26 DM-dependent modules identified) +- Documentation status for each module +- Documentation standards template +- Priority ranking +- Contributing guidelines +- Status legend +- Cross-references to related documentation + +**Updated**: `../INDEX.md` +- Added DM documentation entry to device driver index + +## DM-Dependent Modules Identified + +Through analysis of `components/drivers/*/Kconfig`, the following 26 modules were identified as depending on `RT_USING_DM`: + +### Power Management (5 modules) +1. **regulator** ✅ - Voltage/current regulation (DOCUMENTED) +2. **clk** - Clock management +3. **pinctrl** - Pin multiplexing and configuration +4. **reset** - Reset controller +5. **pmdomain** - Power domain management + +### Interrupt and Timing (2 modules) +6. **pic** - Platform interrupt controller +7. **hwtimer** - Hardware timer (DM support) + +### Storage and Memory (3 modules) +8. **nvmem** - Non-volatile memory +9. **mtd** - Memory technology device (DM support) +10. **block** - Block device layer + +### Communication (2 modules) +11. **mailbox** - Mailbox/doorbell communication +12. **dma** - DMA engine + +### Bus Controllers (2 modules) +13. **pci** - PCI bus +14. **scsi** - SCSI subsystem + +### Specialized Hardware (6 modules) +15. **thermal** - Thermal management +16. **mfd** - Multi-function device +17. **iio** - Industrial I/O +18. **phy** - Generic PHY framework +19. **phye** - Ethernet PHY +20. **graphic** - Graphics/display + +### System Support (6 modules) +21. **ofw** - Open Firmware/Device Tree ⚠️ (Partially documented) +22. **firmware** - Firmware framework (SCMI, etc.) +23. **hwcache** - Hardware cache management +24. **hwspinlock** - Hardware spinlock +25. **input** - Input device framework +26. **led** - LED framework + +### Other +27. **ata** - ATA/AHCI +28. **nvme** - NVMe +29. **rtc** - Real-time clock (DM support) +30. **watchdog** - Watchdog timer (DM support) + +## Documentation Structure Established + +Each module should follow this structure: + +``` +device_driver_model/ +├── README.md # Core DM documentation (EN) +├── README_zh.md # Core DM documentation (CN) +├── INDEX.md # Navigation and status +└── [module]/ + ├── README.md # Module documentation (EN) + ├── README_zh.md # Module documentation (CN) + └── [module]-architecture.svg # Optional: Architecture diagram +``` + +## Documentation Template + +Based on the regulator module, each module documentation should include: + +### Required Sections + +1. **Introduction** (~1000-1500 words) + - General technology overview + - RT-Thread specific implementation + - Architecture with text-based or SVG diagram + +2. **Kconfig Configuration** (~500-1000 words) + - All configuration options + - Dependencies + - Menuconfig paths + - Default values and recommendations + +3. **Device Tree Bindings** (~1000-2000 words) + - All standard properties + - Multiple complete examples + - Consumer usage patterns + - Binding references + +4. **Application Layer API** (~3000-5000 words) + - Complete function reference + - Each function documented with: + - Signature + - Parameters + - Return values + - Notes + - Code examples + - Grouped by functionality + +5. **Complete Examples** (~1000-2000 words of code) + - Real-world usage scenario + - Complete, working code + - Well-commented + - Error handling + - Resource management + +6. **Driver Implementation Guide** (~2000-3000 words) + - Key structures + - Operations to implement + - Complete driver example + - Helper functions + - Registration process + +7. **Best Practices** (~500-1000 words) + - For consumers + - For providers + - Common patterns + - Pitfalls to avoid + +8. **Troubleshooting** (~500-1000 words) + - Common issues + - Debugging tips + - Debug logging + - FAQ + +9. **Performance Considerations** (~300-500 words) + - Memory usage + - Timing considerations + - Optimization tips + +10. **Related Modules** (~200-300 words) + - Cross-references + - Integration points + +11. **References** (~100-200 words) + - Source code locations + - External documentation + - Specifications + +### Target Documentation Size + +- **English**: 20,000-35,000 characters per module +- **Chinese**: 15,000-20,000 characters per module (more compact due to language) + +## Code Analysis Methodology + +For each module, the following files should be analyzed: + +1. **Kconfig** - Configuration options + - Location: `components/drivers/[module]/Kconfig` + - Extract all options, dependencies, defaults + +2. **Header Files** - API definition + - Location: `components/drivers/include/drivers/[module].h` + - Document all public functions, structures, macros + +3. **Implementation** - Behavior and patterns + - Location: `components/drivers/[module]/[module].c` + - Understand registration, operations, error handling + +4. **Example Drivers** - Reference implementation + - Location: `components/drivers/[module]/[module]-*.c` + - Extract patterns for driver guide + +5. **Device Tree Bindings** - If available + - Check for documentation or examples in source comments + +## Remaining Work + +### High Priority Modules + +Based on usage frequency and importance: + +1. **clk** - Clock management framework + - Critical for power management and performance + - Complex hierarchy and operations + - Many consumers depend on it + +2. **pinctrl** - Pin control framework + - Essential for hardware configuration + - Interacts with many peripherals + - Complex multiplexing scenarios + +3. **reset** - Reset controller + - Common in hardware initialization + - Simple but important + - Good second example after regulator + +4. **ofw** - Complete the OFW documentation + - Partial documentation exists + - Need API reference and internals + - Foundation for all DM usage + +5. **pic** - Platform interrupt controller + - Important for interrupt handling + - IRQ domain management complex + +### Medium Priority Modules + +6. **dma** - DMA engine +7. **nvmem** - Non-volatile memory +8. **mailbox** - Inter-processor communication +9. **thermal** - Thermal management +10. **mfd** - Multi-function device + +### Lower Priority Modules + +Remaining modules based on specific hardware support needs. + +## SVG Diagram Requirements + +### Core DM Diagrams Needed + +1. **DM Architecture Overview** + - Bus-Driver-Device relationships + - OFW integration + - Resource flow + +2. **Platform Device Model** + - Platform bus structure + - Device-driver matching + - Probe sequence + +3. **Device Lifecycle** + - State transitions + - Registration flow + - Probe/remove sequence + +### Per-Module Diagrams + +Each module should include: + +1. **Module Architecture** + - Components and relationships + - Data structures + - API layers + +2. **Usage Flow** (optional) + - Typical usage sequence + - State machines + - Interaction with hardware + +### SVG Standards + +- Use clear, professional styling +- Proper alignment and spacing +- Readable text (12pt minimum) +- Consistent color scheme +- Valid SVG syntax +- No escape character issues +- Ortho-linear connections (折线方式) + +## Tools and Workflow + +### Documentation Tools + +1. **Text Editor**: Any with markdown support +2. **SVG Editor**: Inkscape, draw.io, or code-generated SVG +3. **Code Formatting**: Follow RT-Thread style guide + +### Validation + +1. **Markdown**: Check rendering in GitHub +2. **Code Examples**: Verify compilation (if possible) +3. **SVG**: Validate with XML validator +4. **Links**: Check all cross-references + +### Translation Workflow + +1. Complete English version first +2. Translate to Chinese +3. Ensure technical terms are consistent +4. Adapt examples if culturally relevant +5. Review both versions together + +## Estimated Effort + +Based on the regulator module experience: + +- **Research/Analysis**: 2-3 hours per module +- **English Documentation**: 4-6 hours per module +- **Chinese Translation**: 2-3 hours per module +- **SVG Diagrams**: 2-4 hours per module +- **Review/Refinement**: 1-2 hours per module + +**Total per module**: ~11-18 hours + +**For 25 remaining modules**: ~275-450 hours of work + +## Recommendations + +### Phased Approach + +**Phase 1** (Immediate): High priority modules +- clk (1-2 weeks) +- pinctrl (1 week) +- reset (3-5 days) +- ofw completion (1 week) + +**Phase 2** (Short term): Medium priority modules +- pic, dma, nvmem, mailbox, thermal (4-6 weeks) + +**Phase 3** (Long term): Remaining modules +- Based on community requests and hardware support needs + +### Community Involvement + +Consider: +- Opening issues for specific module documentation +- Accepting community contributions +- Providing documentation template/guide +- Code review for consistency + +### Automation Opportunities + +- Script to extract function signatures from headers +- Template generator for new modules +- Automated Kconfig documentation extraction +- SVG template generator + +## Conclusion + +A solid foundation has been established: + +1. ✅ Core DM documentation complete (EN/CN) +2. ✅ Comprehensive template created (regulator module) +3. ✅ All DM modules identified and categorized +4. ✅ Documentation standards defined +5. ✅ Navigation structure established + +The regulator documentation serves as an excellent template that can be followed for the remaining 25+ modules. The structure, depth, and quality are appropriate for professional technical documentation. + +## Contact and Maintenance + +- **Documentation Location**: `documentation/6.components/device-driver/device_driver_model/` +- **Status Tracking**: `INDEX.md` +- **Issue Tracking**: GitHub Issues with `documentation` label +- **Updates**: Keep in sync with source code changes + +--- + +**Created**: 2026-01-02 +**Author**: GitHub Copilot (with user guidance) +**Status**: Foundation Complete, Ongoing Development diff --git a/documentation/6.components/device-driver/device_driver_model/clk/README.md b/documentation/6.components/device-driver/device_driver_model/clk/README.md new file mode 100644 index 00000000000..9d921481493 --- /dev/null +++ b/documentation/6.components/device-driver/device_driver_model/clk/README.md @@ -0,0 +1,1117 @@ +# Clock Framework + +## Introduction + +The Clock Framework in RT-Thread provides a comprehensive infrastructure for managing hardware clocks in embedded systems. It implements a hierarchical clock tree model with support for clock gating, frequency scaling, parent switching, and phase adjustment - all essential for power management and system performance tuning. + +### General Overview + +Clock management is fundamental to embedded systems for: + +- **Power Management**: Gating unused clocks to save power +- **Performance Tuning**: Adjusting clock frequencies for optimal performance +- **Device Synchronization**: Ensuring correct timing relationships +- **Dynamic Voltage and Frequency Scaling (DVFS)**: Coordinating voltage with frequency +- **Clock Domain Management**: Managing multiple clock sources and their relationships + +Common clock types include: +- **Fixed-Rate Clocks**: Crystal oscillators, fixed PLLs +- **Gates**: Enable/disable control +- **Dividers**: Frequency division +- **Multiplexers**: Parent selection +- **PLLs**: Phase-locked loops for frequency multiplication +- **Composite Clocks**: Combinations of above types + +### RT-Thread Implementation + +The RT-Thread clock framework, located in `components/drivers/clk/`, provides: + +1. **Consumer API**: Simple interface for device drivers to manage clocks +2. **Provider API**: Framework for implementing clock drivers +3. **Clock Tree**: Hierarchical parent-child relationships +4. **Device Tree Integration**: Automatic configuration from FDT +5. **Reference Counting**: Safe enable/disable with multiple consumers +6. **Notifier Chains**: Event notification for rate changes +7. **Rate Constraints**: Min/max rate management per consumer + +**Architecture**: +``` +┌─────────────────────────────────────────────────────────┐ +│ Consumer Drivers │ +│ (UART, SPI, MMC, CPU, Peripheral drivers) │ +└────────────────────┬────────────────────────────────────┘ + │ Consumer API + │ (get, enable, set_rate, etc.) +┌────────────────────┴────────────────────────────────────┐ +│ Clock Framework Core │ +│ - Clock Tree Management │ +│ - Reference Counting (prepare/enable) │ +│ - Rate Calculation and Propagation │ +│ - Parent Switching │ +│ - Notifier Chains │ +└────────────────────┬────────────────────────────────────┘ + │ Provider API + │ (ops callbacks) +┌────────────────────┴────────────────────────────────────┐ +│ Clock Drivers │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │Fixed-Rate │ │ Gate │ │ PLL │ │ +│ │ Clock │ │ Clock │ │ Clock │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Divider │ │ Multiplexer│ │ Composite │ │ +│ │ Clock │ │ Clock │ │ Clock │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +└────────────────────┬────────────────────────────────────┘ + │ +┌────────────────────┴────────────────────────────────────┐ +│ Hardware Clock Sources │ +│ (Oscillators, PLLs, Clock Controllers) │ +└──────────────────────────────────────────────────────────┘ +``` + +## Kconfig Configuration + +### Main Configuration + +```kconfig +menuconfig RT_USING_CLK + bool "Using Common Clock Framework (CLK)" + depends on RT_USING_DM + select RT_USING_ADT_REF + default y +``` + +**Location in menuconfig**: +``` +RT-Thread Components → Device Drivers → Using Common Clock Framework (CLK) +``` + +**Dependencies**: +- `RT_USING_DM`: Must be enabled first +- `RT_USING_ADT_REF`: Reference counting support (automatic) + +**Default**: Enabled when DM is enabled + +### SCMI Clock Driver + +```kconfig +config RT_CLK_SCMI + bool "Clock driver controlled via SCMI interface" + depends on RT_USING_CLK + depends on RT_FIRMWARE_ARM_SCMI + default n +``` + +Supports clocks controlled through ARM System Control and Management Interface (SCMI). + +**Dependencies**: +- `RT_FIRMWARE_ARM_SCMI`: ARM SCMI firmware interface + +### Vendor-Specific Options + +```kconfig +if RT_USING_CLK + osource "$(SOC_DM_CLK_DIR)/Kconfig" +endif +``` + +Allows SoC-specific clock drivers to add their own Kconfig options via the `SOC_DM_CLK_DIR` variable. + +## Device Tree Bindings + +### Clock Provider Properties + +Clock providers export clocks using these properties: + +```dts +#clock-cells = ; /* Number of cells in clock specifier */ +clock-output-names = "name1", "name2"; /* Names of output clocks */ +``` + +### Clock Consumer Properties + +Devices reference clocks using: + +```dts +clocks = <&clk_provider idx>; /* Clock phandle and index */ +clock-names = "name"; /* Clock connection names */ +``` + +### Fixed-Rate Clock Example + +```dts +clocks { + /* Simple fixed-rate oscillator */ + osc24M: oscillator-24M { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "osc24M"; + }; + + /* Fixed-rate with accuracy specification */ + osc32k: oscillator-32k { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <32768>; + clock-accuracy = <50>; /* ±50 PPM */ + clock-output-names = "osc32k"; + }; +}; +``` + +### Clock Controller Example + +```dts +ccu: clock-controller@1c20000 { + compatible = "vendor,clock-controller"; + reg = <0x1c20000 0x400>; + #clock-cells = <1>; + + /* Parent clocks */ + clocks = <&osc24M>, <&osc32k>; + clock-names = "hosc", "losc"; + + /* Output clock names (optional) */ + clock-output-names = "pll-cpu", "pll-ddr", "ahb1", "apb1", + "bus-uart0", "bus-uart1"; +}; +``` + +### Consumer Usage Example + +```dts +/* Single clock consumer */ +uart0: serial@1c28000 { + compatible = "vendor,uart"; + reg = <0x1c28000 0x400>; + interrupts = <0 0 4>; + + clocks = <&ccu 64>; /* Clock index 64 */ + clock-names = "baudclk"; + + status = "okay"; +}; + +/* Multiple clocks consumer */ +mmc0: mmc@1c0f000 { + compatible = "vendor,mmc"; + reg = <0x1c0f000 0x1000>; + interrupts = <0 32 4>; + + clocks = <&ccu 8>, <&ccu 9>; + clock-names = "ahb", "mmc"; + + status = "okay"; +}; + +/* Clock with parent reference */ +cpu0: cpu@0 { + compatible = "arm,cortex-a7"; + device_type = "cpu"; + reg = <0>; + + clocks = <&ccu 0>; + clock-names = "cpu"; + + operating-points-v2 = <&cpu_opp_table>; +}; +``` + +## Application Layer API + +### Overview + +The consumer API provides functions for device drivers to manage their clocks. The framework uses a two-level prepare/enable model similar to Linux: +- **prepare**: May sleep, can configure PLLs +- **enable**: Atomic operation, must not sleep + +### Getting and Releasing Clocks + +#### rt_clk_get_by_name + +```c +struct rt_clk *rt_clk_get_by_name(struct rt_device *dev, const char *name); +``` + +Get a clock by connection name. + +**Parameters**: +- `dev`: Device structure pointer +- `name`: Clock connection name (matches `clock-names` in device tree) + +**Returns**: +- Pointer to clock on success +- NULL on failure + +**Example**: +```c +struct rt_device *dev = &pdev->parent; +struct rt_clk *clk; + +/* Get the "baudclk" clock */ +clk = rt_clk_get_by_name(dev, "baudclk"); +if (!clk) { + LOG_E("Failed to get baudclk"); + return -RT_ERROR; +} +``` + +#### rt_clk_get_by_index + +```c +struct rt_clk *rt_clk_get_by_index(struct rt_device *dev, int index); +``` + +Get a clock by index in the `clocks` property. + +**Parameters**: +- `dev`: Device structure pointer +- `index`: Clock index (0-based) + +**Returns**: +- Pointer to clock on success +- NULL on failure + +**Example**: +```c +/* Get the first clock */ +struct rt_clk *clk = rt_clk_get_by_index(dev, 0); +``` + +#### rt_clk_get_array + +```c +struct rt_clk_array *rt_clk_get_array(struct rt_device *dev); +``` + +Get all clocks for a device as an array. + +**Parameters**: +- `dev`: Device structure pointer + +**Returns**: +- Pointer to clock array on success +- Error pointer on failure (check with `rt_is_err()`) + +**Example**: +```c +struct rt_clk_array *clk_arr = rt_clk_get_array(dev); +if (rt_is_err(clk_arr)) { + return rt_ptr_err(clk_arr); +} + +/* Access individual clocks */ +for (int i = 0; i < clk_arr->count; i++) { + struct rt_clk *clk = clk_arr->clks[i]; + /* ... */ +} +``` + +#### rt_clk_put + +```c +void rt_clk_put(struct rt_clk *clk); +``` + +Release a clock reference. + +**Parameters**: +- `clk`: Clock pointer obtained from get functions + +#### rt_clk_array_put + +```c +void rt_clk_array_put(struct rt_clk_array *clk_arr); +``` + +Release a clock array. + +**Parameters**: +- `clk_arr`: Clock array pointer + +### Prepare and Enable + +#### rt_clk_prepare + +```c +rt_err_t rt_clk_prepare(struct rt_clk *clk); +``` + +Prepare a clock for enabling. This may sleep and can configure PLLs or perform other operations that cannot be done atomically. + +**Parameters**: +- `clk`: Clock pointer + +**Returns**: +- `RT_EOK` on success +- Error code on failure + +**Notes**: +- May sleep - don't call from atomic context +- Must be called before `rt_clk_enable()` +- Use reference counting + +#### rt_clk_unprepare + +```c +void rt_clk_unprepare(struct rt_clk *clk); +``` + +Unprepare a previously prepared clock. + +**Parameters**: +- `clk`: Clock pointer + +#### rt_clk_enable + +```c +rt_err_t rt_clk_enable(struct rt_clk *clk); +``` + +Enable a clock. This is an atomic operation that must not sleep. + +**Parameters**: +- `clk`: Clock pointer + +**Returns**: +- `RT_EOK` on success +- Error code on failure + +**Notes**: +- Must not sleep - safe to call from atomic context +- Must be preceded by `rt_clk_prepare()` +- Uses reference counting + +#### rt_clk_disable + +```c +void rt_clk_disable(struct rt_clk *clk); +``` + +Disable a previously enabled clock. + +**Parameters**: +- `clk`: Clock pointer + +#### rt_clk_prepare_enable + +```c +rt_err_t rt_clk_prepare_enable(struct rt_clk *clk); +``` + +Convenience function that prepares and enables a clock. + +**Parameters**: +- `clk`: Clock pointer + +**Returns**: +- `RT_EOK` on success +- Error code on failure + +**Example**: +```c +/* Typical usage */ +ret = rt_clk_prepare_enable(clk); +if (ret != RT_EOK) { + LOG_E("Failed to enable clock: %d", ret); + return ret; +} +``` + +#### rt_clk_disable_unprepare + +```c +void rt_clk_disable_unprepare(struct rt_clk *clk); +``` + +Convenience function that disables and unprepares a clock. + +**Parameters**: +- `clk`: Clock pointer + +### Clock Array Operations + +#### rt_clk_array_prepare_enable + +```c +rt_err_t rt_clk_array_prepare_enable(struct rt_clk_array *clk_arr); +``` + +Prepare and enable all clocks in an array. + +**Parameters**: +- `clk_arr`: Clock array pointer + +**Returns**: +- `RT_EOK` on success +- Error code on failure (partial enable handled internally) + +#### rt_clk_array_disable_unprepare + +```c +void rt_clk_array_disable_unprepare(struct rt_clk_array *clk_arr); +``` + +Disable and unprepare all clocks in an array. + +**Parameters**: +- `clk_arr`: Clock array pointer + +### Rate Management + +#### rt_clk_set_rate + +```c +rt_err_t rt_clk_set_rate(struct rt_clk *clk, rt_ubase_t rate); +``` + +Set clock frequency. + +**Parameters**: +- `clk`: Clock pointer +- `rate`: Desired frequency in Hz + +**Returns**: +- `RT_EOK` on success +- Error code on failure + +**Notes**: +- Actual rate may differ due to hardware limitations +- Use `rt_clk_get_rate()` to verify actual rate +- May trigger notifier callbacks + +**Example**: +```c +/* Set UART clock to 48MHz */ +ret = rt_clk_set_rate(uart_clk, 48000000); +if (ret != RT_EOK) { + LOG_E("Failed to set clock rate: %d", ret); +} + +/* Verify actual rate */ +rt_ubase_t actual_rate = rt_clk_get_rate(uart_clk); +LOG_I("Clock rate: %u Hz", actual_rate); +``` + +#### rt_clk_get_rate + +```c +rt_ubase_t rt_clk_get_rate(struct rt_clk *clk); +``` + +Get current clock frequency. + +**Parameters**: +- `clk`: Clock pointer + +**Returns**: +- Current frequency in Hz +- 0 on error + +#### rt_clk_round_rate + +```c +rt_base_t rt_clk_round_rate(struct rt_clk *clk, rt_ubase_t rate); +``` + +Get the closest supported rate without changing the clock. + +**Parameters**: +- `clk`: Clock pointer +- `rate`: Desired frequency in Hz + +**Returns**: +- Closest supported rate +- Negative error code on failure + +**Example**: +```c +/* Check if desired rate is supported */ +rt_base_t rounded = rt_clk_round_rate(clk, 50000000); +if (rounded < 0) { + LOG_E("Cannot determine supported rate"); +} else { + LOG_I("Closest supported rate: %d Hz", rounded); +} +``` + +#### rt_clk_set_rate_range + +```c +rt_err_t rt_clk_set_rate_range(struct rt_clk *clk, rt_ubase_t min, rt_ubase_t max); +``` + +Set acceptable rate range for this consumer. + +**Parameters**: +- `clk`: Clock pointer +- `min`: Minimum acceptable rate in Hz +- `max`: Maximum acceptable rate in Hz + +**Returns**: +- `RT_EOK` on success +- Error code on failure + +**Example**: +```c +/* UART requires 48MHz ±2% */ +rt_clk_set_rate_range(uart_clk, 47040000, 48960000); +``` + +### Parent Management + +#### rt_clk_set_parent + +```c +rt_err_t rt_clk_set_parent(struct rt_clk *clk, struct rt_clk *parent); +``` + +Change clock parent (for multiplexer clocks). + +**Parameters**: +- `clk`: Clock pointer +- `parent`: New parent clock + +**Returns**: +- `RT_EOK` on success +- Error code on failure + +#### rt_clk_get_parent + +```c +struct rt_clk *rt_clk_get_parent(struct rt_clk *clk); +``` + +Get current parent clock. + +**Parameters**: +- `clk`: Clock pointer + +**Returns**: +- Pointer to parent clock +- NULL if no parent or error + +### Phase Control + +#### rt_clk_set_phase + +```c +rt_err_t rt_clk_set_phase(struct rt_clk *clk, int degrees); +``` + +Set clock phase in degrees. + +**Parameters**: +- `clk`: Clock pointer +- `degrees`: Phase in degrees (0-359) + +**Returns**: +- `RT_EOK` on success +- `-RT_ENOSYS` if not supported +- Error code on failure + +#### rt_clk_get_phase + +```c +rt_base_t rt_clk_get_phase(struct rt_clk *clk); +``` + +Get current clock phase. + +**Parameters**: +- `clk`: Clock pointer + +**Returns**: +- Phase in degrees (0-359) +- Negative error code on failure + +### Notifier API + +#### rt_clk_notifier_register + +```c +rt_err_t rt_clk_notifier_register(struct rt_clk *clk, + struct rt_clk_notifier *notifier); +``` + +Register a notifier for clock events. + +**Parameters**: +- `clk`: Clock pointer +- `notifier`: Notifier structure with callback + +**Returns**: +- `RT_EOK` on success +- Error code on failure + +**Notifier Structure**: +```c +struct rt_clk_notifier { + rt_list_t list; + struct rt_clk *clk; + rt_clk_notifier_callback callback; + void *priv; +}; + +typedef rt_err_t (*rt_clk_notifier_callback)( + struct rt_clk_notifier *notifier, + rt_ubase_t msg, + rt_ubase_t old_rate, + rt_ubase_t new_rate); +``` + +**Event Messages**: +- `RT_CLK_MSG_PRE_RATE_CHANGE`: Before rate change +- `RT_CLK_MSG_POST_RATE_CHANGE`: After successful rate change +- `RT_CLK_MSG_ABORT_RATE_CHANGE`: Rate change aborted + +**Example**: +```c +static rt_err_t clk_notifier_cb(struct rt_clk_notifier *notifier, + rt_ubase_t msg, + rt_ubase_t old_rate, + rt_ubase_t new_rate) +{ + if (msg == RT_CLK_MSG_PRE_RATE_CHANGE) { + LOG_I("Clock rate changing: %u -> %u Hz", old_rate, new_rate); + /* Prepare for rate change */ + } else if (msg == RT_CLK_MSG_POST_RATE_CHANGE) { + LOG_I("Clock rate changed successfully"); + /* Update hardware configuration */ + } + return RT_EOK; +} + +struct rt_clk_notifier my_notifier = { + .callback = clk_notifier_cb, + .priv = NULL, +}; + +rt_clk_notifier_register(clk, &my_notifier); +``` + +## Complete Application Example + +### Example: UART Driver with Clock Management + +```c +#include +#include +#include +#include + +struct uart_device { + void *base; + int irq; + struct rt_clk *clk; + struct rt_serial_device serial; +}; + +static rt_err_t uart_configure(struct rt_serial_device *serial, + struct serial_configure *cfg) +{ + struct uart_device *uart = rt_container_of(serial, + struct uart_device, serial); + rt_ubase_t clk_rate; + rt_uint32_t divisor; + + /* Get clock rate */ + clk_rate = rt_clk_get_rate(uart->clk); + if (clk_rate == 0) { + LOG_E("Invalid clock rate"); + return -RT_ERROR; + } + + /* Calculate baud rate divisor */ + divisor = clk_rate / (16 * cfg->baud_rate); + + /* Configure hardware */ + writel(divisor & 0xFF, uart->base + UART_DLL); + writel((divisor >> 8) & 0xFF, uart->base + UART_DLH); + + LOG_D("UART: clk=%u Hz, baud=%u, divisor=%u", + clk_rate, cfg->baud_rate, divisor); + + return RT_EOK; +} + +static rt_err_t uart_probe(struct rt_platform_device *pdev) +{ + rt_err_t ret; + struct rt_device *dev = &pdev->parent; + struct uart_device *uart; + + /* Allocate device structure */ + uart = rt_calloc(1, sizeof(*uart)); + if (!uart) + return -RT_ENOMEM; + + /* Map MMIO region */ + uart->base = rt_dm_dev_iomap(dev, 0); + if (!uart->base) { + ret = -RT_ERROR; + goto err_free; + } + + /* Get IRQ */ + uart->irq = rt_dm_dev_get_irq(dev, 0); + if (uart->irq < 0) { + ret = uart->irq; + goto err_unmap; + } + + /* Get clock */ + uart->clk = rt_clk_get_by_name(dev, "baudclk"); + if (!uart->clk) { + LOG_E("Failed to get baudclk"); + ret = -RT_ERROR; + goto err_unmap; + } + + /* Prepare and enable clock */ + ret = rt_clk_prepare_enable(uart->clk); + if (ret != RT_EOK) { + LOG_E("Failed to enable clock: %d", ret); + goto err_put_clk; + } + + /* Log clock rate */ + LOG_I("UART clock rate: %u Hz", rt_clk_get_rate(uart->clk)); + + /* Initialize serial device */ + uart->serial.ops = &uart_ops; + uart->serial.config = RT_SERIAL_CONFIG_DEFAULT; + + /* Register serial device */ + ret = rt_hw_serial_register(&uart->serial, + rt_dm_dev_get_name(dev), + RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX, + uart); + if (ret != RT_EOK) { + goto err_disable_clk; + } + + pdev->priv = uart; + LOG_I("UART device registered"); + + return RT_EOK; + +err_disable_clk: + rt_clk_disable_unprepare(uart->clk); +err_put_clk: + rt_clk_put(uart->clk); +err_unmap: + rt_iounmap(uart->base); +err_free: + rt_free(uart); + return ret; +} + +static rt_err_t uart_remove(struct rt_platform_device *pdev) +{ + struct uart_device *uart = pdev->priv; + + /* Unregister serial device */ + rt_device_unregister(&uart->serial.parent); + + /* Disable and release clock */ + rt_clk_disable_unprepare(uart->clk); + rt_clk_put(uart->clk); + + /* Release resources */ + rt_iounmap(uart->base); + rt_free(uart); + + return RT_EOK; +} + +static const struct rt_ofw_node_id uart_ofw_ids[] = { + { .compatible = "vendor,uart" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver uart_driver = { + .name = "uart", + .ids = uart_ofw_ids, + .probe = uart_probe, + .remove = uart_remove, +}; + +RT_PLATFORM_DRIVER_EXPORT(uart_driver); +``` + +## Driver Implementation Guide + +### Key Structures + +#### rt_clk_cell + +```c +struct rt_clk_cell { + struct rt_clk_node *clk_np; /* Parent clock node */ + const char *name; /* Clock name */ + const struct rt_clk_ops *ops; /* Operations */ + + rt_uint8_t parents_nr; /* Number of parents */ + union { + const char *parent_name; /* Single parent */ + const char *const *parent_names; /* Multiple parents */ + }; + + rt_ubase_t rate; /* Cached rate */ + struct rt_clk *clk; /* Consumer reference */ + struct rt_clk *parent; /* Current parent */ + + int prepare_count; /* Prepare reference count */ + int enable_count; /* Enable reference count */ + + rt_uint32_t flags; /* Clock flags */ + void *priv; /* Private data */ +}; +``` + +#### rt_clk_ops + +```c +struct rt_clk_ops { + /* Prepare/unprepare (may sleep) */ + rt_err_t (*prepare)(struct rt_clk_cell *cell); + void (*unprepare)(struct rt_clk_cell *cell); + rt_bool_t (*is_prepared)(struct rt_clk_cell *cell); + + /* Enable/disable (atomic) */ + rt_err_t (*enable)(struct rt_clk_cell *cell); + void (*disable)(struct rt_clk_cell *cell); + rt_bool_t (*is_enabled)(struct rt_clk_cell *cell); + + /* Rate control */ + rt_ubase_t (*recalc_rate)(struct rt_clk_cell *cell, rt_ubase_t parent_rate); + rt_base_t (*round_rate)(struct rt_clk_cell *cell, rt_ubase_t drate, rt_ubase_t *prate); + rt_err_t (*set_rate)(struct rt_clk_cell *cell, rt_ubase_t rate, rt_ubase_t parent_rate); + + /* Parent control */ + rt_err_t (*set_parent)(struct rt_clk_cell *cell, rt_uint8_t idx); + rt_uint8_t (*get_parent)(struct rt_clk_cell *cell); + + /* Phase control */ + rt_err_t (*set_phase)(struct rt_clk_cell *cell, int degrees); + rt_base_t (*get_phase)(struct rt_clk_cell *cell); +}; +``` + +### Example: Fixed-Rate Clock Driver + +```c +#include +#include +#include + +struct clk_fixed { + struct rt_clk_node parent; + struct rt_clk_fixed_rate fcell; + struct rt_clk_cell *cells[1]; +}; + +static rt_ubase_t fixed_clk_recalc_rate(struct rt_clk_cell *cell, + rt_ubase_t parent_rate) +{ + struct rt_clk_fixed_rate *fr = rt_container_of(cell, + struct rt_clk_fixed_rate, + cell); + return fr->fixed_rate; +} + +static struct rt_clk_ops fixed_clk_ops = { + .recalc_rate = fixed_clk_recalc_rate, +}; + +static rt_err_t fixed_clk_probe(struct rt_platform_device *pdev) +{ + rt_err_t err; + rt_uint32_t val; + struct rt_device *dev = &pdev->parent; + struct clk_fixed *cf; + + /* Allocate driver structure */ + cf = rt_calloc(1, sizeof(*cf)); + if (!cf) + return -RT_ENOMEM; + + /* Read clock frequency from device tree */ + if ((err = rt_dm_dev_prop_read_u32(dev, "clock-frequency", &val))) { + LOG_E("Missing clock-frequency property"); + goto _fail; + } + cf->fcell.fixed_rate = val; + + /* Read optional accuracy */ + val = 0; + rt_dm_dev_prop_read_u32(dev, "clock-accuracy", &val); + cf->fcell.fixed_accuracy = val; + + /* Read optional clock name */ + rt_dm_dev_prop_read_string(dev, "clock-output-names", + &cf->fcell.cell.name); + + /* Initialize clock node */ + cf->parent.dev = dev; + cf->parent.cells_nr = 1; + cf->parent.cells = cf->cells; + cf->cells[0] = &cf->fcell.cell; + cf->fcell.cell.ops = &fixed_clk_ops; + + /* Register with framework */ + if ((err = rt_clk_register(&cf->parent))) { + LOG_E("Failed to register clock: %d", err); + goto _fail; + } + + LOG_I("Fixed clock '%s' registered: %u Hz", + cf->fcell.cell.name, cf->fcell.fixed_rate); + + return RT_EOK; + +_fail: + rt_free(cf); + return err; +} + +static const struct rt_ofw_node_id fixed_clk_ofw_ids[] = { + { .compatible = "fixed-clock" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver fixed_clk_driver = { + .name = "clk-fixed-rate", + .ids = fixed_clk_ofw_ids, + .probe = fixed_clk_probe, +}; + +static int fixed_clk_drv_register(void) +{ + rt_platform_driver_register(&fixed_clk_driver); + return 0; +} +INIT_SUBSYS_EXPORT(fixed_clk_drv_register); +``` + +## Best Practices + +### For Consumer Drivers + +1. **Always use prepare_enable/disable_unprepare**: Simpler and safer +2. **Check return values**: Clock operations can fail +3. **Balance enable/disable**: Match every enable with a disable +4. **Order matters**: Enable clocks before using hardware +5. **Handle rate changes**: Use notifiers if rate changes affect operation +6. **Set rate constraints**: Use `rt_clk_set_rate_range()` when needed + +### For Provider Drivers + +1. **Implement only supported operations**: Leave unsupported ops NULL +2. **Cache rates when possible**: Avoid hardware access in `get_rate()` +3. **Handle reference counting**: Framework manages prepare/enable counts +4. **Propagate rate changes**: Update cached rates in children +5. **Support multiple parents**: For multiplexer clocks +6. **Document constraints**: Min/max rates, parent relationships + +### Common Patterns + +#### Simple Clock Usage + +```c +/* Get and enable clock */ +struct rt_clk *clk = rt_clk_get_by_name(dev, "clk"); +if (!clk) + return -RT_ERROR; + +ret = rt_clk_prepare_enable(clk); +if (ret != RT_EOK) { + rt_clk_put(clk); + return ret; +} + +/* Use hardware */ + +/* Cleanup */ +rt_clk_disable_unprepare(clk); +rt_clk_put(clk); +``` + +#### Dynamic Frequency Scaling + +```c +/* Change frequency based on workload */ +switch (perf_level) { +case PERF_HIGH: + rt_clk_set_rate(cpu_clk, 1000000000); /* 1GHz */ + break; +case PERF_NORMAL: + rt_clk_set_rate(cpu_clk, 800000000); /* 800MHz */ + break; +case PERF_LOW: + rt_clk_set_rate(cpu_clk, 400000000); /* 400MHz */ + break; +} +``` + +## Troubleshooting + +### Common Issues + +1. **Clock not found** + - Check device tree: Ensure `clocks` and `clock-names` properties exist + - Check compatible string: Verify clock driver is loaded + - Check Kconfig: Enable clock framework and drivers + +2. **Enable fails** + - Check parent clocks: Parents must be enabled first + - Check prepare: Must prepare before enable + - Check hardware: Verify clock controller is accessible + +3. **Wrong frequency** + - Check parent rate: Parent must be correct frequency + - Check dividers: Hardware may have limited divider values + - Use `rt_clk_round_rate()`: Verify supported rates + +4. **System instability** + - Check critical clocks: Some clocks must never be disabled + - Check rate constraints: Don't exceed hardware limits + - Check dependencies: Some clocks depend on others + +## Performance Considerations + +### Memory Usage + +- Each clock cell: ~80-100 bytes +- Each consumer reference: ~40 bytes +- Clock tree overhead: Depends on hierarchy depth + +### Timing + +- prepare/enable: Can be slow (ms for PLLs) +- Rate changes: May be slow, use notifiers +- get_rate: Usually fast (cached) + +### Optimization Tips + +1. **Cache clock pointers**: Don't call get/put repeatedly +2. **Batch operations**: Use clock arrays when possible +3. **Avoid unnecessary rate changes**: Check current rate first +4. **Use prepare_enable**: Combines two operations + +## Related Modules + +- **regulator**: Power supply management, coordinate with clocks +- **pinctrl**: Pin configuration, may need clock enable +- **reset**: Reset control, coordinate with clock enable +- **pmdomain**: Power domain, higher-level power management + +## References + +- RT-Thread Source: `components/drivers/clk/` +- Header File: `components/drivers/include/drivers/clk.h` +- Device Tree Bindings: [Linux Clock Bindings](https://www.kernel.org/doc/Documentation/devicetree/bindings/clock/) +- [RT-Thread DM Documentation](../README.md) diff --git a/documentation/6.components/device-driver/device_driver_model/clk/README_zh.md b/documentation/6.components/device-driver/device_driver_model/clk/README_zh.md new file mode 100644 index 00000000000..e8c209b5720 --- /dev/null +++ b/documentation/6.components/device-driver/device_driver_model/clk/README_zh.md @@ -0,0 +1,869 @@ +# 时钟框架 + +## 简介 + +RT-Thread 的时钟框架为嵌入式系统中的硬件时钟管理提供了全面的基础设施。它实现了层次化时钟树模型,支持时钟门控、频率调节、父时钟切换和相位调整——这些都是电源管理和系统性能调优的关键。 + +### 概述 + +时钟管理对嵌入式系统至关重要: + +- **电源管理**:关闭未使用的时钟以节省功耗 +- **性能调优**:调整时钟频率以获得最佳性能 +- **设备同步**:确保正确的时序关系 +- **动态电压频率调节(DVFS)**:协调电压与频率 +- **时钟域管理**:管理多个时钟源及其关系 + +常见时钟类型包括: +- **固定频率时钟**:晶振、固定 PLL +- **门控时钟**:启用/禁用控制 +- **分频器**:频率分频 +- **多路复用器**:父时钟选择 +- **PLL**:锁相环,用于频率倍增 +- **复合时钟**:上述类型的组合 + +### RT-Thread 实现 + +RT-Thread 时钟框架位于 `components/drivers/clk/`,提供: + +1. **消费者 API**:设备驱动程序管理时钟的简单接口 +2. **提供者 API**:实现时钟驱动程序的框架 +3. **时钟树**:层次化的父子关系 +4. **设备树集成**:从 FDT 自动配置 +5. **引用计数**:多消费者安全启用/禁用 +6. **通知链**:频率变化的事件通知 +7. **频率约束**:每个消费者的最小/最大频率管理 + +## Kconfig 配置 + +### 主配置 + +```kconfig +menuconfig RT_USING_CLK + bool "Using Common Clock Framework (CLK)" + depends on RT_USING_DM + select RT_USING_ADT_REF + default y +``` + +**在 menuconfig 中的位置**: +``` +RT-Thread Components → Device Drivers → Using Common Clock Framework (CLK) +``` + +**依赖项**: +- `RT_USING_DM`:必须首先启用 +- `RT_USING_ADT_REF`:引用计数支持(自动) + +**默认值**:启用 DM 时默认启用 + +### SCMI 时钟驱动 + +```kconfig +config RT_CLK_SCMI + bool "Clock driver controlled via SCMI interface" + depends on RT_USING_CLK + depends on RT_FIRMWARE_ARM_SCMI + default n +``` + +支持通过 ARM 系统控制和管理接口(SCMI)控制的时钟。 + +## 设备树绑定 + +### 时钟提供者属性 + +时钟提供者使用这些属性导出时钟: + +```dts +#clock-cells = ; /* 时钟说明符中的单元数 */ +clock-output-names = "name1", "name2"; /* 输出时钟名称 */ +``` + +### 时钟消费者属性 + +设备使用以下方式引用时钟: + +```dts +clocks = <&clk_provider idx>; /* 时钟 phandle 和索引 */ +clock-names = "name"; /* 时钟连接名称 */ +``` + +### 固定频率时钟示例 + +```dts +clocks { + /* 简单固定频率振荡器 */ + osc24M: oscillator-24M { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-output-names = "osc24M"; + }; + + /* 带精度规范的固定频率 */ + osc32k: oscillator-32k { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <32768>; + clock-accuracy = <50>; /* ±50 PPM */ + clock-output-names = "osc32k"; + }; +}; +``` + +### 时钟控制器示例 + +```dts +ccu: clock-controller@1c20000 { + compatible = "vendor,clock-controller"; + reg = <0x1c20000 0x400>; + #clock-cells = <1>; + + /* 父时钟 */ + clocks = <&osc24M>, <&osc32k>; + clock-names = "hosc", "losc"; + + /* 输出时钟名称(可选)*/ + clock-output-names = "pll-cpu", "pll-ddr", "ahb1", "apb1"; +}; +``` + +### 消费者使用示例 + +```dts +/* 单时钟消费者 */ +uart0: serial@1c28000 { + compatible = "vendor,uart"; + reg = <0x1c28000 0x400>; + interrupts = <0 0 4>; + + clocks = <&ccu 64>; /* 时钟索引 64 */ + clock-names = "baudclk"; + + status = "okay"; +}; + +/* 多时钟消费者 */ +mmc0: mmc@1c0f000 { + compatible = "vendor,mmc"; + reg = <0x1c0f000 0x1000>; + + clocks = <&ccu 8>, <&ccu 9>; + clock-names = "ahb", "mmc"; + + status = "okay"; +}; +``` + +## 应用层 API + +### 概述 + +消费者 API 为设备驱动程序提供管理时钟的函数。框架使用类似 Linux 的两级 prepare/enable 模型: +- **prepare**:可能休眠,可以配置 PLL +- **enable**:原子操作,不能休眠 + +### 获取和释放时钟 + +#### rt_clk_get_by_name + +```c +struct rt_clk *rt_clk_get_by_name(struct rt_device *dev, const char *name); +``` + +通过连接名称获取时钟。 + +**参数**: +- `dev`:设备结构指针 +- `name`:时钟连接名称(匹配设备树中的 `clock-names`) + +**返回值**: +- 成功时返回时钟指针 +- 失败时返回 NULL + +**示例**: +```c +struct rt_clk *clk = rt_clk_get_by_name(dev, "baudclk"); +if (!clk) { + LOG_E("获取 baudclk 失败"); + return -RT_ERROR; +} +``` + +#### rt_clk_get_by_index + +```c +struct rt_clk *rt_clk_get_by_index(struct rt_device *dev, int index); +``` + +通过 `clocks` 属性中的索引获取时钟。 + +**参数**: +- `dev`:设备结构指针 +- `index`:时钟索引(从 0 开始) + +#### rt_clk_get_array + +```c +struct rt_clk_array *rt_clk_get_array(struct rt_device *dev); +``` + +获取设备的所有时钟作为数组。 + +**参数**: +- `dev`:设备结构指针 + +**返回值**: +- 成功时返回时钟数组指针 +- 失败时返回错误指针(使用 `rt_is_err()` 检查) + +#### rt_clk_put + +```c +void rt_clk_put(struct rt_clk *clk); +``` + +释放时钟引用。 + +### Prepare 和 Enable + +#### rt_clk_prepare + +```c +rt_err_t rt_clk_prepare(struct rt_clk *clk); +``` + +为启用准备时钟。这可能会休眠,可以配置 PLL 或执行其他不能原子完成的操作。 + +**参数**: +- `clk`:时钟指针 + +**返回值**: +- 成功时返回 `RT_EOK` +- 失败时返回错误代码 + +**注意**: +- 可能休眠——不要从原子上下文调用 +- 必须在 `rt_clk_enable()` 之前调用 +- 使用引用计数 + +#### rt_clk_unprepare + +```c +void rt_clk_unprepare(struct rt_clk *clk); +``` + +取消准备先前已准备的时钟。 + +#### rt_clk_enable + +```c +rt_err_t rt_clk_enable(struct rt_clk *clk); +``` + +启用时钟。这是一个不能休眠的原子操作。 + +**参数**: +- `clk`:时钟指针 + +**返回值**: +- 成功时返回 `RT_EOK` +- 失败时返回错误代码 + +**注意**: +- 不能休眠——可以从原子上下文安全调用 +- 必须在 `rt_clk_prepare()` 之后 +- 使用引用计数 + +#### rt_clk_disable + +```c +void rt_clk_disable(struct rt_clk *clk); +``` + +禁用先前启用的时钟。 + +#### rt_clk_prepare_enable + +```c +rt_err_t rt_clk_prepare_enable(struct rt_clk *clk); +``` + +便捷函数,准备并启用时钟。 + +**示例**: +```c +/* 典型用法 */ +ret = rt_clk_prepare_enable(clk); +if (ret != RT_EOK) { + LOG_E("启用时钟失败: %d", ret); + return ret; +} +``` + +#### rt_clk_disable_unprepare + +```c +void rt_clk_disable_unprepare(struct rt_clk *clk); +``` + +便捷函数,禁用并取消准备时钟。 + +### 频率管理 + +#### rt_clk_set_rate + +```c +rt_err_t rt_clk_set_rate(struct rt_clk *clk, rt_ubase_t rate); +``` + +设置时钟频率。 + +**参数**: +- `clk`:时钟指针 +- `rate`:期望频率(Hz) + +**返回值**: +- 成功时返回 `RT_EOK` +- 失败时返回错误代码 + +**注意**: +- 由于硬件限制,实际频率可能不同 +- 使用 `rt_clk_get_rate()` 验证实际频率 +- 可能触发通知器回调 + +**示例**: +```c +/* 将 UART 时钟设置为 48MHz */ +ret = rt_clk_set_rate(uart_clk, 48000000); +if (ret != RT_EOK) { + LOG_E("设置时钟频率失败: %d", ret); +} + +/* 验证实际频率 */ +rt_ubase_t actual_rate = rt_clk_get_rate(uart_clk); +LOG_I("时钟频率: %u Hz", actual_rate); +``` + +#### rt_clk_get_rate + +```c +rt_ubase_t rt_clk_get_rate(struct rt_clk *clk); +``` + +获取当前时钟频率。 + +**参数**: +- `clk`:时钟指针 + +**返回值**: +- 当前频率(Hz) +- 错误时返回 0 + +#### rt_clk_round_rate + +```c +rt_base_t rt_clk_round_rate(struct rt_clk *clk, rt_ubase_t rate); +``` + +获取最接近的支持频率,但不更改时钟。 + +**参数**: +- `clk`:时钟指针 +- `rate`:期望频率(Hz) + +**返回值**: +- 最接近的支持频率 +- 失败时返回负错误代码 + +#### rt_clk_set_rate_range + +```c +rt_err_t rt_clk_set_rate_range(struct rt_clk *clk, rt_ubase_t min, rt_ubase_t max); +``` + +为此消费者设置可接受的频率范围。 + +**参数**: +- `clk`:时钟指针 +- `min`:最小可接受频率(Hz) +- `max`:最大可接受频率(Hz) + +**示例**: +```c +/* UART 需要 48MHz ±2% */ +rt_clk_set_rate_range(uart_clk, 47040000, 48960000); +``` + +### 父时钟管理 + +#### rt_clk_set_parent + +```c +rt_err_t rt_clk_set_parent(struct rt_clk *clk, struct rt_clk *parent); +``` + +更改时钟父时钟(用于多路复用器时钟)。 + +**参数**: +- `clk`:时钟指针 +- `parent`:新的父时钟 + +#### rt_clk_get_parent + +```c +struct rt_clk *rt_clk_get_parent(struct rt_clk *clk); +``` + +获取当前父时钟。 + +### 相位控制 + +#### rt_clk_set_phase + +```c +rt_err_t rt_clk_set_phase(struct rt_clk *clk, int degrees); +``` + +以度为单位设置时钟相位。 + +**参数**: +- `clk`:时钟指针 +- `degrees`:相位(度,0-359) + +**返回值**: +- 成功时返回 `RT_EOK` +- 不支持时返回 `-RT_ENOSYS` + +#### rt_clk_get_phase + +```c +rt_base_t rt_clk_get_phase(struct rt_clk *clk); +``` + +获取当前时钟相位。 + +### 通知器 API + +#### rt_clk_notifier_register + +```c +rt_err_t rt_clk_notifier_register(struct rt_clk *clk, + struct rt_clk_notifier *notifier); +``` + +为时钟事件注册通知器。 + +**通知器结构**: +```c +struct rt_clk_notifier { + rt_list_t list; + struct rt_clk *clk; + rt_clk_notifier_callback callback; + void *priv; +}; + +typedef rt_err_t (*rt_clk_notifier_callback)( + struct rt_clk_notifier *notifier, + rt_ubase_t msg, + rt_ubase_t old_rate, + rt_ubase_t new_rate); +``` + +**事件消息**: +- `RT_CLK_MSG_PRE_RATE_CHANGE`:频率变化前 +- `RT_CLK_MSG_POST_RATE_CHANGE`:频率成功变化后 +- `RT_CLK_MSG_ABORT_RATE_CHANGE`:频率变化中止 + +**示例**: +```c +static rt_err_t clk_notifier_cb(struct rt_clk_notifier *notifier, + rt_ubase_t msg, + rt_ubase_t old_rate, + rt_ubase_t new_rate) +{ + if (msg == RT_CLK_MSG_PRE_RATE_CHANGE) { + LOG_I("时钟频率变化: %u -> %u Hz", old_rate, new_rate); + /* 为频率变化做准备 */ + } + return RT_EOK; +} + +struct rt_clk_notifier my_notifier = { + .callback = clk_notifier_cb, +}; + +rt_clk_notifier_register(clk, &my_notifier); +``` + +## 完整应用示例 + +### 示例:带时钟管理的 UART 驱动 + +```c +#include +#include +#include + +struct uart_device { + void *base; + int irq; + struct rt_clk *clk; + struct rt_serial_device serial; +}; + +static rt_err_t uart_configure(struct rt_serial_device *serial, + struct serial_configure *cfg) +{ + struct uart_device *uart = rt_container_of(serial, + struct uart_device, serial); + rt_ubase_t clk_rate; + rt_uint32_t divisor; + + /* 获取时钟频率 */ + clk_rate = rt_clk_get_rate(uart->clk); + if (clk_rate == 0) { + LOG_E("无效的时钟频率"); + return -RT_ERROR; + } + + /* 计算波特率分频器 */ + divisor = clk_rate / (16 * cfg->baud_rate); + + /* 配置硬件 */ + writel(divisor & 0xFF, uart->base + UART_DLL); + writel((divisor >> 8) & 0xFF, uart->base + UART_DLH); + + return RT_EOK; +} + +static rt_err_t uart_probe(struct rt_platform_device *pdev) +{ + rt_err_t ret; + struct rt_device *dev = &pdev->parent; + struct uart_device *uart; + + /* 分配设备结构 */ + uart = rt_calloc(1, sizeof(*uart)); + if (!uart) + return -RT_ENOMEM; + + /* 映射 MMIO 区域 */ + uart->base = rt_dm_dev_iomap(dev, 0); + if (!uart->base) { + ret = -RT_ERROR; + goto err_free; + } + + /* 获取 IRQ */ + uart->irq = rt_dm_dev_get_irq(dev, 0); + + /* 获取时钟 */ + uart->clk = rt_clk_get_by_name(dev, "baudclk"); + if (!uart->clk) { + LOG_E("获取 baudclk 失败"); + ret = -RT_ERROR; + goto err_unmap; + } + + /* 准备并启用时钟 */ + ret = rt_clk_prepare_enable(uart->clk); + if (ret != RT_EOK) { + LOG_E("启用时钟失败: %d", ret); + goto err_put_clk; + } + + /* 注册串口设备 */ + ret = rt_hw_serial_register(&uart->serial, + rt_dm_dev_get_name(dev), + RT_DEVICE_FLAG_RDWR, + uart); + if (ret != RT_EOK) { + goto err_disable_clk; + } + + pdev->priv = uart; + return RT_EOK; + +err_disable_clk: + rt_clk_disable_unprepare(uart->clk); +err_put_clk: + rt_clk_put(uart->clk); +err_unmap: + rt_iounmap(uart->base); +err_free: + rt_free(uart); + return ret; +} + +static rt_err_t uart_remove(struct rt_platform_device *pdev) +{ + struct uart_device *uart = pdev->priv; + + /* 取消注册串口设备 */ + rt_device_unregister(&uart->serial.parent); + + /* 禁用并释放时钟 */ + rt_clk_disable_unprepare(uart->clk); + rt_clk_put(uart->clk); + + /* 释放资源 */ + rt_iounmap(uart->base); + rt_free(uart); + + return RT_EOK; +} + +static const struct rt_ofw_node_id uart_ofw_ids[] = { + { .compatible = "vendor,uart" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver uart_driver = { + .name = "uart", + .ids = uart_ofw_ids, + .probe = uart_probe, + .remove = uart_remove, +}; + +RT_PLATFORM_DRIVER_EXPORT(uart_driver); +``` + +## 驱动实现指南 + +### 核心结构 + +#### rt_clk_ops + +```c +struct rt_clk_ops { + /* Prepare/unprepare (可能休眠) */ + rt_err_t (*prepare)(struct rt_clk_cell *cell); + void (*unprepare)(struct rt_clk_cell *cell); + rt_bool_t (*is_prepared)(struct rt_clk_cell *cell); + + /* Enable/disable (原子操作) */ + rt_err_t (*enable)(struct rt_clk_cell *cell); + void (*disable)(struct rt_clk_cell *cell); + rt_bool_t (*is_enabled)(struct rt_clk_cell *cell); + + /* 频率控制 */ + rt_ubase_t (*recalc_rate)(struct rt_clk_cell *cell, rt_ubase_t parent_rate); + rt_base_t (*round_rate)(struct rt_clk_cell *cell, rt_ubase_t drate, rt_ubase_t *prate); + rt_err_t (*set_rate)(struct rt_clk_cell *cell, rt_ubase_t rate, rt_ubase_t parent_rate); + + /* 父时钟控制 */ + rt_err_t (*set_parent)(struct rt_clk_cell *cell, rt_uint8_t idx); + rt_uint8_t (*get_parent)(struct rt_clk_cell *cell); +}; +``` + +### 示例:固定频率时钟驱动 + +```c +#include +#include +#include + +struct clk_fixed { + struct rt_clk_node parent; + struct rt_clk_fixed_rate fcell; + struct rt_clk_cell *cells[1]; +}; + +static rt_ubase_t fixed_clk_recalc_rate(struct rt_clk_cell *cell, + rt_ubase_t parent_rate) +{ + struct rt_clk_fixed_rate *fr = rt_container_of(cell, + struct rt_clk_fixed_rate, + cell); + return fr->fixed_rate; +} + +static struct rt_clk_ops fixed_clk_ops = { + .recalc_rate = fixed_clk_recalc_rate, +}; + +static rt_err_t fixed_clk_probe(struct rt_platform_device *pdev) +{ + rt_err_t err; + rt_uint32_t val; + struct rt_device *dev = &pdev->parent; + struct clk_fixed *cf; + + /* 分配驱动结构 */ + cf = rt_calloc(1, sizeof(*cf)); + if (!cf) + return -RT_ENOMEM; + + /* 从设备树读取时钟频率 */ + if ((err = rt_dm_dev_prop_read_u32(dev, "clock-frequency", &val))) { + LOG_E("缺少 clock-frequency 属性"); + goto _fail; + } + cf->fcell.fixed_rate = val; + + /* 读取可选的精度 */ + val = 0; + rt_dm_dev_prop_read_u32(dev, "clock-accuracy", &val); + cf->fcell.fixed_accuracy = val; + + /* 读取可选的时钟名称 */ + rt_dm_dev_prop_read_string(dev, "clock-output-names", + &cf->fcell.cell.name); + + /* 初始化时钟节点 */ + cf->parent.dev = dev; + cf->parent.cells_nr = 1; + cf->parent.cells = cf->cells; + cf->cells[0] = &cf->fcell.cell; + cf->fcell.cell.ops = &fixed_clk_ops; + + /* 向框架注册 */ + if ((err = rt_clk_register(&cf->parent))) { + LOG_E("注册时钟失败: %d", err); + goto _fail; + } + + LOG_I("固定时钟 '%s' 已注册: %u Hz", + cf->fcell.cell.name, cf->fcell.fixed_rate); + + return RT_EOK; + +_fail: + rt_free(cf); + return err; +} + +static const struct rt_ofw_node_id fixed_clk_ofw_ids[] = { + { .compatible = "fixed-clock" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver fixed_clk_driver = { + .name = "clk-fixed-rate", + .ids = fixed_clk_ofw_ids, + .probe = fixed_clk_probe, +}; + +static int fixed_clk_drv_register(void) +{ + rt_platform_driver_register(&fixed_clk_driver); + return 0; +} +INIT_SUBSYS_EXPORT(fixed_clk_drv_register); +``` + +## 最佳实践 + +### 对于消费者驱动程序 + +1. **始终使用 prepare_enable/disable_unprepare**:更简单、更安全 +2. **检查返回值**:时钟操作可能失败 +3. **平衡 enable/disable**:每次启用都要匹配一次禁用 +4. **顺序很重要**:在使用硬件之前启用时钟 +5. **处理频率变化**:如果频率变化影响操作,使用通知器 +6. **设置频率约束**:需要时使用 `rt_clk_set_rate_range()` + +### 对于提供者驱动程序 + +1. **仅实现支持的操作**:不支持的操作留 NULL +2. **尽可能缓存频率**:避免在 `get_rate()` 中访问硬件 +3. **处理引用计数**:框架管理 prepare/enable 计数 +4. **传播频率变化**:更新子时钟的缓存频率 +5. **支持多个父时钟**:用于多路复用器时钟 + +### 常见模式 + +#### 简单时钟使用 + +```c +/* 获取并启用时钟 */ +struct rt_clk *clk = rt_clk_get_by_name(dev, "clk"); +if (!clk) + return -RT_ERROR; + +ret = rt_clk_prepare_enable(clk); +if (ret != RT_EOK) { + rt_clk_put(clk); + return ret; +} + +/* 使用硬件 */ + +/* 清理 */ +rt_clk_disable_unprepare(clk); +rt_clk_put(clk); +``` + +#### 动态频率调节 + +```c +/* 根据工作负载更改频率 */ +switch (perf_level) { +case PERF_HIGH: + rt_clk_set_rate(cpu_clk, 1000000000); /* 1GHz */ + break; +case PERF_NORMAL: + rt_clk_set_rate(cpu_clk, 800000000); /* 800MHz */ + break; +case PERF_LOW: + rt_clk_set_rate(cpu_clk, 400000000); /* 400MHz */ + break; +} +``` + +## 故障排除 + +### 常见问题 + +1. **找不到时钟** + - 检查设备树:确保 `clocks` 和 `clock-names` 属性存在 + - 检查 compatible 字符串:验证时钟驱动已加载 + - 检查 Kconfig:启用时钟框架和驱动程序 + +2. **启用失败** + - 检查父时钟:父时钟必须先启用 + - 检查 prepare:必须在 enable 之前 prepare + - 检查硬件:验证时钟控制器是否可访问 + +3. **频率错误** + - 检查父频率:父时钟必须是正确的频率 + - 检查分频器:硬件可能有有限的分频器值 + - 使用 `rt_clk_round_rate()`:验证支持的频率 + +## 性能考虑 + +### 内存使用 + +- 每个时钟单元:约 80-100 字节 +- 每个消费者引用:约 40 字节 +- 时钟树开销:取决于层次深度 + +### 时序 + +- prepare/enable:可能很慢(PLL 需要 ms) +- 频率变化:可能很慢,使用通知器 +- get_rate:通常很快(已缓存) + +### 优化技巧 + +1. **缓存时钟指针**:不要重复调用 get/put +2. **批量操作**:尽可能使用时钟数组 +3. **避免不必要的频率变化**:先检查当前频率 +4. **使用 prepare_enable**:组合两个操作 + +## 相关模块 + +- **regulator**:电源管理,与时钟协调 +- **pinctrl**:引脚配置,可能需要启用时钟 +- **reset**:复位控制,与时钟启用协调 +- **pmdomain**:电源域,更高级别的电源管理 + +## 参考资料 + +- RT-Thread 源代码:`components/drivers/clk/` +- 头文件:`components/drivers/include/drivers/clk.h` +- 设备树绑定:[Linux Clock Bindings](https://www.kernel.org/doc/Documentation/devicetree/bindings/clock/) +- [RT-Thread DM 文档](../README_zh.md) diff --git a/documentation/6.components/device-driver/device_driver_model/nvmem/README.md b/documentation/6.components/device-driver/device_driver_model/nvmem/README.md new file mode 100644 index 00000000000..a9a0cd0d3b1 --- /dev/null +++ b/documentation/6.components/device-driver/device_driver_model/nvmem/README.md @@ -0,0 +1,859 @@ +# NVMEM (Non-Volatile Memory) Framework + +## Overview + +### What is NVMEM? + +Non-Volatile Memory (NVMEM) refers to computer memory that can retain stored information even when power is removed. Common examples include: + +- **EEPROM** (Electrically Erasable Programmable Read-Only Memory) +- **OTP** (One-Time Programmable memory) +- **eFuse** (Electronic fuse) +- **Battery-backed SRAM** +- **Flash memory regions** (for configuration storage) + +NVMEM devices are commonly used to store: +- Device configuration and calibration data +- MAC addresses and serial numbers +- Hardware identifiers and unique IDs +- Manufacturing test data +- Secure key storage + +### NVMEM Framework in RT-Thread + +RT-Thread's NVMEM framework provides a unified abstraction layer for accessing various types of non-volatile memory devices. The framework supports: + +- **Unified Access Interface**: Common APIs for reading/writing NVMEM regardless of underlying hardware +- **Cell-Based Organization**: NVMEM data organized into named cells (regions) +- **Device Tree Integration**: Hardware description through device tree +- **Write Protection**: Hardware write-protect pin support +- **Bit-Level Access**: Support for bit-addressable memory regions +- **Reference Counting**: Automatic resource management + +The framework is located in: +- Header: `components/drivers/include/drivers/nvmem.h` +- Implementation: `components/drivers/nvmem/nvmem.c` + +## Kconfig Configuration + +### Enable NVMEM Framework + +``` +RT-Thread Components + → Device Drivers + → Using Device Driver Model (RT_USING_DM) + → Using Open Firmware (OF) API (RT_USING_OFW) + → Using Non Volatile Memory (NVMEM) device drivers (RT_USING_NVMEM) +``` + +### Configuration Options + +#### RT_USING_NVMEM + +```kconfig +menuconfig RT_USING_NVMEM + bool "Using Non Volatile Memory (NVMEM) device drivers" + depends on RT_USING_DM + depends on RT_USING_OFW + depends on RT_USING_PIN + select RT_USING_ADT + select RT_USING_ADT_REF + default n +``` + +**Dependencies**: +- `RT_USING_DM`: Device Driver Model must be enabled +- `RT_USING_OFW`: Open Firmware (Device Tree) support required +- `RT_USING_PIN`: PIN driver framework for write-protect pins + +**Description**: Enables the NVMEM framework providing unified access to non-volatile memory devices like EEPROM, OTP, eFuse, etc. + +#### SOC-Specific NVMEM Drivers + +```kconfig +if RT_USING_NVMEM + osource "$(SOC_DM_NVMEM_DIR)/Kconfig" +endif +``` + +SoC vendors can provide their specific NVMEM driver configurations through the `SOC_DM_NVMEM_DIR` directory. + +## Device Tree Bindings + +### NVMEM Provider Node + +```dts +eeprom: eeprom@50 { + compatible = "atmel,24c256"; + reg = <0x50>; + + #address-cells = <1>; + #size-cells = <1>; + + /* Optional properties */ + read-only; /* Memory is read-only */ + wp-gpios = <&gpio 10 GPIO_ACTIVE_HIGH>; /* Write protect pin */ + + /* Define memory cells */ + mac_address: mac@0 { + reg = <0x00 6>; /* Offset 0x00, 6 bytes */ + }; + + serial_number: serial@10 { + reg = <0x10 16>; /* Offset 0x10, 16 bytes */ + }; + + calibration: calib@100 { + reg = <0x100 32>; /* Offset 0x100, 32 bytes */ + bits = <4 12>; /* Bit offset 4, 12 bits wide */ + }; +}; +``` + +### NVMEM Consumer Node + +```dts +ethernet@40000000 { + compatible = "vendor,ethernet"; + reg = <0x40000000 0x10000>; + + /* Reference NVMEM cells */ + nvmem-cells = <&mac_address>; + nvmem-cell-names = "mac-address"; +}; + +device@50000000 { + compatible = "vendor,device"; + reg = <0x50000000 0x1000>; + + /* Multiple NVMEM references */ + nvmem-cells = <&serial_number>, <&calibration>; + nvmem-cell-names = "serial", "calibration-data"; +}; +``` + +### Device Tree Properties + +#### NVMEM Provider Properties + +| Property | Type | Description | +|----------|------|-------------| +| `#address-cells` | u32 | Number of cells for cell address (usually 1) | +| `#size-cells` | u32 | Number of cells for cell size (usually 1) | +| `read-only` | bool | Memory is read-only, writes not allowed | +| `wp-gpios` | phandle | GPIO for hardware write protection | + +#### NVMEM Cell Properties + +| Property | Type | Description | +|----------|------|-------------| +| `reg` | u32 array | `` - Memory region for this cell | +| `bits` | u32 array | `` - Bit-level addressing | + +#### NVMEM Consumer Properties + +| Property | Type | Description | +|----------|------|-------------| +| `nvmem-cells` | phandle | Reference to NVMEM cells | +| `nvmem-cell-names` | string array | Names for referenced cells | + +## Application Layer API + +### Data Structures + +#### struct rt_nvmem_device + +```c +struct rt_nvmem_device { + struct rt_device parent; /* Parent device */ + + int cells_nr; /* Number of cells */ + rt_list_t cell_nodes; /* List of cells */ + + /* Read/Write callbacks */ + rt_ssize_t (*reg_read)(struct rt_nvmem_device *, int offset, + void *val, rt_size_t bytes); + rt_ssize_t (*reg_write)(struct rt_nvmem_device *, int offset, + void *val, rt_size_t bytes); + + rt_ssize_t size; /* Total size in bytes */ + int word_size; /* Word size (1, 2, 4 bytes) */ + int stride; /* Minimum access stride */ + + rt_bool_t read_only; /* Read-only flag */ + rt_bool_t ignore_wp; /* Ignore WP pin */ + rt_base_t wp_pin; /* Write protect GPIO */ + rt_uint8_t wp_pin_active; /* WP active level */ + + struct rt_ref ref; /* Reference counter */ + struct rt_spinlock spinlock; /* Spinlock for protection */ + + void *priv; /* Private data */ +}; +``` + +#### struct rt_nvmem_cell + +```c +struct rt_nvmem_cell { + rt_list_t list; /* List node */ + + int index; /* Cell index */ + const char *id; /* Cell identifier */ + const rt_bool_t free_able; /* Can be freed */ + + rt_uint32_t offset; /* Offset in bytes */ + rt_uint32_t bytes; /* Size in bytes */ + rt_uint32_t bit_offset; /* Bit offset within byte */ + rt_uint32_t nbits; /* Number of bits */ + + struct rt_ref ref; /* Reference counter */ + + struct rt_ofw_node *np; /* Device tree node */ + struct rt_nvmem_device *nvmem; /* Parent NVMEM device */ +}; +``` + +### Consumer APIs + +#### Get NVMEM Cell + +```c +struct rt_nvmem_cell *rt_nvmem_get_cell_by_name(struct rt_device *dev, + const char *id); +struct rt_nvmem_cell *rt_nvmem_get_cell_by_index(struct rt_device *dev, + int index); +``` + +**Parameters**: +- `dev`: Consumer device +- `id`: Cell name from device tree `nvmem-cell-names` +- `index`: Cell index (0-based) + +**Returns**: Pointer to `rt_nvmem_cell` on success, NULL on failure + +**Description**: Obtain a reference to an NVMEM cell for reading/writing. Must be released with `rt_nvmem_put_cell()`. + +**Example**: +```c +struct rt_nvmem_cell *cell; + +/* Get by name */ +cell = rt_nvmem_get_cell_by_name(dev, "mac-address"); +if (!cell) { + rt_kprintf("Failed to get MAC address cell\n"); + return -RT_ERROR; +} + +/* Get by index */ +cell = rt_nvmem_get_cell_by_index(dev, 0); +``` + +#### Release NVMEM Cell + +```c +void rt_nvmem_put_cell(struct rt_nvmem_cell *cell); +``` + +**Parameters**: +- `cell`: NVMEM cell to release + +**Description**: Release a reference to an NVMEM cell obtained with `rt_nvmem_get_cell_*()`. + +#### Read NVMEM Cell + +```c +rt_ssize_t rt_nvmem_cell_read(struct rt_nvmem_cell *cell, + void *buffer, rt_size_t len); +``` + +**Parameters**: +- `cell`: NVMEM cell to read +- `buffer`: Buffer to store read data +- `len`: Number of bytes to read + +**Returns**: Number of bytes read on success, negative error code on failure + +**Description**: Read data from an NVMEM cell. Supports bit-level access if configured. + +#### Write NVMEM Cell + +```c +rt_ssize_t rt_nvmem_cell_write(struct rt_nvmem_cell *cell, + void *buffer, rt_size_t len); +``` + +**Parameters**: +- `cell`: NVMEM cell to write +- `buffer`: Data to write +- `len`: Number of bytes to write + +**Returns**: Number of bytes written on success, negative error code on failure + +**Description**: Write data to an NVMEM cell. Returns error if memory is read-only. + +#### Typed Read Functions + +```c +rt_ssize_t rt_nvmem_cell_read_u8(struct rt_nvmem_cell *cell, + rt_uint8_t *out_val); +rt_ssize_t rt_nvmem_cell_read_u16(struct rt_nvmem_cell *cell, + rt_uint16_t *out_val); +rt_ssize_t rt_nvmem_cell_read_u32(struct rt_nvmem_cell *cell, + rt_uint32_t *out_val); +rt_ssize_t rt_nvmem_cell_read_u64(struct rt_nvmem_cell *cell, + rt_uint64_t *out_val); +``` + +**Parameters**: +- `cell`: NVMEM cell to read +- `out_val`: Pointer to store the value + +**Returns**: Number of bytes read on success, negative error code on failure + +**Description**: Convenience functions to read specific integer types from NVMEM cells. + +### Provider APIs + +#### Register NVMEM Device + +```c +rt_err_t rt_nvmem_device_register(struct rt_nvmem_device *ndev); +``` + +**Parameters**: +- `ndev`: NVMEM device to register + +**Returns**: `RT_EOK` on success, negative error code on failure + +**Description**: Register an NVMEM device with the framework. Initializes reference counting, parses device tree, and configures write-protect pin if present. + +#### Unregister NVMEM Device + +```c +rt_err_t rt_nvmem_device_unregister(struct rt_nvmem_device *ndev); +``` + +**Parameters**: +- `ndev`: NVMEM device to unregister + +**Returns**: `RT_EOK` on success, `-RT_EBUSY` if device is still in use + +**Description**: Unregister an NVMEM device. Fails if there are outstanding references. + +#### Append Cell to Device + +```c +rt_err_t rt_nvmem_device_append_cell(struct rt_nvmem_device *ndev, + struct rt_nvmem_cell *cell); +``` + +**Parameters**: +- `ndev`: NVMEM device +- `cell`: Cell to append + +**Returns**: `RT_EOK` on success, negative error code on failure + +**Description**: Add a cell to an NVMEM device. Used for runtime cell registration. + +## Complete Example: Ethernet Driver with MAC Address + +### Device Tree + +```dts +/* EEPROM device with MAC address storage */ +i2c0: i2c@40000000 { + compatible = "vendor,i2c"; + reg = <0x40000000 0x1000>; + + eeprom@50 { + compatible = "atmel,24c32"; + reg = <0x50>; + + #address-cells = <1>; + #size-cells = <1>; + + /* MAC address cell */ + eth_mac: mac@0 { + reg = <0x00 6>; + }; + + /* Device configuration */ + eth_config: config@10 { + reg = <0x10 16>; + }; + }; +}; + +/* Ethernet controller referencing MAC address */ +ethernet@50000000 { + compatible = "vendor,ethernet"; + reg = <0x50000000 0x10000>; + interrupts = <32 IRQ_TYPE_LEVEL_HIGH>; + + clocks = <&cru CLK_EMAC>; + clock-names = "stmmaceth"; + + /* Reference NVMEM for MAC address */ + nvmem-cells = <ð_mac>; + nvmem-cell-names = "mac-address"; +}; +``` + +### Ethernet Driver Implementation + +```c +#include +#include +#include + +struct ethernet_device { + struct rt_device parent; + void *base; + int irq; + rt_uint8_t mac_addr[6]; + /* Other fields... */ +}; + +static rt_err_t ethernet_load_mac_address(struct ethernet_device *eth_dev) +{ + struct rt_nvmem_cell *cell; + rt_ssize_t ret; + + /* Get MAC address cell from device tree */ + cell = rt_nvmem_get_cell_by_name(ð_dev->parent, "mac-address"); + if (!cell) { + rt_kprintf("[ETH] No MAC address in NVMEM, using default\n"); + /* Use default or random MAC */ + eth_dev->mac_addr[0] = 0x00; + eth_dev->mac_addr[1] = 0x11; + eth_dev->mac_addr[2] = 0x22; + eth_dev->mac_addr[3] = 0x33; + eth_dev->mac_addr[4] = 0x44; + eth_dev->mac_addr[5] = 0x55; + return RT_EOK; + } + + /* Read MAC address from NVMEM */ + ret = rt_nvmem_cell_read(cell, eth_dev->mac_addr, 6); + if (ret != 6) { + rt_kprintf("[ETH] Failed to read MAC address: %d\n", ret); + rt_nvmem_put_cell(cell); + return -RT_ERROR; + } + + /* Release cell */ + rt_nvmem_put_cell(cell); + + rt_kprintf("[ETH] MAC Address: %02X:%02X:%02X:%02X:%02X:%02X\n", + eth_dev->mac_addr[0], eth_dev->mac_addr[1], + eth_dev->mac_addr[2], eth_dev->mac_addr[3], + eth_dev->mac_addr[4], eth_dev->mac_addr[5]); + + return RT_EOK; +} + +static rt_err_t ethernet_write_mac_address(struct ethernet_device *eth_dev, + const rt_uint8_t *mac) +{ + struct rt_nvmem_cell *cell; + rt_ssize_t ret; + + /* Get MAC address cell */ + cell = rt_nvmem_get_cell_by_name(ð_dev->parent, "mac-address"); + if (!cell) { + rt_kprintf("[ETH] No MAC address cell available\n"); + return -RT_ERROR; + } + + /* Write new MAC address */ + ret = rt_nvmem_cell_write(cell, (void *)mac, 6); + if (ret != 6) { + rt_kprintf("[ETH] Failed to write MAC address: %d\n", ret); + rt_nvmem_put_cell(cell); + return -RT_ERROR; + } + + rt_nvmem_put_cell(cell); + + /* Update local copy */ + rt_memcpy(eth_dev->mac_addr, mac, 6); + + rt_kprintf("[ETH] MAC Address updated successfully\n"); + + return RT_EOK; +} + +static rt_err_t ethernet_probe(struct rt_platform_device *pdev) +{ + struct ethernet_device *eth_dev; + rt_err_t ret; + + eth_dev = rt_calloc(1, sizeof(*eth_dev)); + if (!eth_dev) { + return -RT_ENOMEM; + } + + /* Initialize device */ + eth_dev->parent.ofw_node = pdev->parent.ofw_node; + + /* Get hardware resources */ + eth_dev->base = rt_dm_dev_get_address(&pdev->parent); + if (!eth_dev->base) { + rt_free(eth_dev); + return -RT_ERROR; + } + + /* Load MAC address from NVMEM */ + ret = ethernet_load_mac_address(eth_dev); + if (ret != RT_EOK) { + rt_kprintf("[ETH] Warning: MAC address load failed\n"); + } + + /* Configure hardware with MAC address */ + /* ... hardware initialization ... */ + + rt_kprintf("[ETH] Ethernet initialized\n"); + + return RT_EOK; +} + +static const struct rt_ofw_node_id ethernet_ofw_match[] = { + { .compatible = "vendor,ethernet" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver ethernet_driver = { + .name = "vendor-ethernet", + .ids = ethernet_ofw_match, + .probe = ethernet_probe, +}; +RT_PLATFORM_DRIVER_EXPORT(ethernet_driver); +``` + +## NVMEM Provider Driver Example + +### I2C EEPROM Driver + +```c +#include +#include +#include +#include + +struct eeprom_device { + struct rt_nvmem_device nvmem; + struct rt_i2c_bus_device *i2c_bus; + rt_uint16_t i2c_addr; + rt_size_t size; + rt_uint32_t page_size; +}; + +static rt_ssize_t eeprom_reg_read(struct rt_nvmem_device *ndev, + int offset, void *val, rt_size_t bytes) +{ + struct eeprom_device *eeprom; + struct rt_i2c_msg msgs[2]; + rt_uint8_t addr_buf[2]; + rt_ssize_t ret; + + eeprom = rt_container_of(ndev, struct eeprom_device, nvmem); + + /* Check bounds */ + if (offset + bytes > eeprom->size) { + return -RT_EINVAL; + } + + /* Prepare address (big-endian) */ + addr_buf[0] = (offset >> 8) & 0xFF; + addr_buf[1] = offset & 0xFF; + + /* Write address */ + msgs[0].addr = eeprom->i2c_addr; + msgs[0].flags = RT_I2C_WR; + msgs[0].buf = addr_buf; + msgs[0].len = 2; + + /* Read data */ + msgs[1].addr = eeprom->i2c_addr; + msgs[1].flags = RT_I2C_RD; + msgs[1].buf = val; + msgs[1].len = bytes; + + ret = rt_i2c_transfer(eeprom->i2c_bus, msgs, 2); + if (ret != 2) { + return -RT_ERROR; + } + + return bytes; +} + +static rt_ssize_t eeprom_reg_write(struct rt_nvmem_device *ndev, + int offset, void *val, rt_size_t bytes) +{ + struct eeprom_device *eeprom; + struct rt_i2c_msg msg; + rt_uint8_t *write_buf; + rt_size_t written = 0; + rt_ssize_t ret; + + eeprom = rt_container_of(ndev, struct eeprom_device, nvmem); + + /* Check bounds */ + if (offset + bytes > eeprom->size) { + return -RT_EINVAL; + } + + write_buf = rt_malloc(eeprom->page_size + 2); + if (!write_buf) { + return -RT_ENOMEM; + } + + /* Write page by page */ + while (written < bytes) { + rt_uint32_t page_offset = offset % eeprom->page_size; + rt_size_t chunk = RT_MIN(bytes - written, + eeprom->page_size - page_offset); + + /* Prepare write buffer: address + data */ + write_buf[0] = (offset >> 8) & 0xFF; + write_buf[1] = offset & 0xFF; + rt_memcpy(&write_buf[2], (rt_uint8_t *)val + written, chunk); + + msg.addr = eeprom->i2c_addr; + msg.flags = RT_I2C_WR; + msg.buf = write_buf; + msg.len = chunk + 2; + + ret = rt_i2c_transfer(eeprom->i2c_bus, &msg, 1); + if (ret != 1) { + rt_free(write_buf); + return -RT_ERROR; + } + + /* Wait for write cycle to complete (typ. 5ms for EEPROM) */ + rt_thread_mdelay(5); + + written += chunk; + offset += chunk; + } + + rt_free(write_buf); + + return written; +} + +static rt_err_t eeprom_probe(struct rt_platform_device *pdev) +{ + struct eeprom_device *eeprom; + struct rt_device *dev = &pdev->parent; + rt_err_t ret; + rt_uint32_t size; + + eeprom = rt_calloc(1, sizeof(*eeprom)); + if (!eeprom) { + return -RT_ENOMEM; + } + + /* Get I2C bus */ + eeprom->i2c_bus = rt_i2c_bus_device_find( + rt_dm_dev_get_name_id(dev, NULL, 0, "i2c-bus")); + if (!eeprom->i2c_bus) { + rt_kprintf("[EEPROM] I2C bus not found\n"); + rt_free(eeprom); + return -RT_ERROR; + } + + /* Get I2C address */ + if (rt_dm_dev_prop_read_u32(dev, "reg", &eeprom->i2c_addr) != RT_EOK) { + rt_kprintf("[EEPROM] No I2C address specified\n"); + rt_free(eeprom); + return -RT_ERROR; + } + + /* Get EEPROM size (default 4KB for AT24C32) */ + if (rt_dm_dev_prop_read_u32(dev, "size", &size) != RT_EOK) { + size = 4096; /* Default 4KB */ + } + eeprom->size = size; + + /* Get page size (default 32 bytes) */ + if (rt_dm_dev_prop_read_u32(dev, "pagesize", + &eeprom->page_size) != RT_EOK) { + eeprom->page_size = 32; + } + + /* Initialize NVMEM device */ + eeprom->nvmem.parent.ofw_node = dev->ofw_node; + eeprom->nvmem.reg_read = eeprom_reg_read; + eeprom->nvmem.reg_write = eeprom_reg_write; + eeprom->nvmem.size = eeprom->size; + eeprom->nvmem.word_size = 1; + eeprom->nvmem.stride = 1; + eeprom->nvmem.priv = eeprom; + + /* Register NVMEM device */ + ret = rt_nvmem_device_register(&eeprom->nvmem); + if (ret != RT_EOK) { + rt_kprintf("[EEPROM] Failed to register NVMEM device\n"); + rt_free(eeprom); + return ret; + } + + rt_kprintf("[EEPROM] Registered %d bytes at I2C address 0x%02X\n", + eeprom->size, eeprom->i2c_addr); + + return RT_EOK; +} + +static const struct rt_ofw_node_id eeprom_ofw_match[] = { + { .compatible = "atmel,24c32" }, + { .compatible = "atmel,24c64" }, + { .compatible = "atmel,24c256" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver eeprom_driver = { + .name = "atmel-eeprom", + .ids = eeprom_ofw_match, + .probe = eeprom_probe, +}; +RT_PLATFORM_DRIVER_EXPORT(eeprom_driver); +``` + +## Best Practices + +### For Consumers + +1. **Always Check Return Values** + ```c + cell = rt_nvmem_get_cell_by_name(dev, "calibration"); + if (!cell) { + /* Handle error - use default values */ + } + ``` + +2. **Release Resources** + ```c + cell = rt_nvmem_get_cell_by_name(dev, "data"); + if (cell) { + rt_nvmem_cell_read(cell, buffer, size); + rt_nvmem_put_cell(cell); /* Always release */ + } + ``` + +3. **Handle Read-Only Memory** + ```c + ret = rt_nvmem_cell_write(cell, data, len); + if (ret < 0) { + if (ret == -RT_ENOSYS) { + rt_kprintf("Memory is read-only\n"); + } + } + ``` + +4. **Use Typed Reads for Simple Values** + ```c + rt_uint32_t serial_number; + + cell = rt_nvmem_get_cell_by_name(dev, "serial"); + if (cell) { + rt_nvmem_cell_read_u32(cell, &serial_number); + rt_nvmem_put_cell(cell); + } + ``` + +### For Providers + +1. **Implement Proper Bounds Checking** + ```c + if (offset + bytes > nvmem->size) { + return -RT_EINVAL; + } + ``` + +2. **Support Write Protection** + ```c + /* Framework handles wp-gpios automatically */ + /* Just don't set ignore_wp flag */ + nvmem->ignore_wp = RT_FALSE; + ``` + +3. **Handle Page-Based Writes** + ```c + /* For devices with page write limits */ + /* Write in page-sized chunks and wait for completion */ + rt_thread_mdelay(write_cycle_time_ms); + ``` + +4. **Initialize All Fields** + ```c + nvmem->size = total_size; + nvmem->word_size = 1; /* Byte-addressable */ + nvmem->stride = 1; /* Minimum access unit */ + nvmem->reg_read = my_read; + nvmem->reg_write = my_write; + ``` + +## Troubleshooting + +### Cell Not Found + +**Problem**: `rt_nvmem_get_cell_by_name()` returns NULL + +**Solutions**: +1. Check device tree `nvmem-cells` and `nvmem-cell-names` properties match +2. Verify NVMEM provider device is probed before consumer +3. Check `#address-cells` and `#size-cells` in provider node + +### Write Fails + +**Problem**: `rt_nvmem_cell_write()` returns error + +**Solutions**: +1. Check if memory is marked `read-only` in device tree +2. Verify write-protect pin is not active +3. Check provider's `reg_write` callback is implemented +4. Verify offset and size are within bounds + +### Data Corruption + +**Problem**: Read data doesn't match written data + +**Solutions**: +1. Check page-write boundaries for page-based devices +2. Verify write cycle delays are sufficient +3. Add proper synchronization if accessing from multiple threads +4. Check bit-offset calculations for bit-addressable cells + +## Performance Considerations + +### Read Performance + +- **Caching**: Cache frequently-read values (e.g., MAC address) after first read +- **Batch Reads**: Read multiple cells in one operation if possible +- **Async Access**: Use separate thread for slow NVMEM operations + +### Write Performance + +- **Write Caching**: Buffer writes and flush periodically +- **Wear Leveling**: Distribute writes across memory for EEPROMs +- **Write Coalescing**: Combine multiple small writes into larger operations + +### Resource Usage + +- **Cell References**: Release cells promptly with `rt_nvmem_put_cell()` +- **Memory**: Consider memory cost of buffering for page-based devices +- **I/O**: EEPROM writes can take several milliseconds per page + +## Related Modules + +- **OFW (Open Firmware)**: Device tree parsing for NVMEM configuration +- **PIN**: Write-protect GPIO handling +- **I2C/SPI**: Common buses for NVMEM devices + +## References + +- NVMEM header: `components/drivers/include/drivers/nvmem.h` +- NVMEM implementation: `components/drivers/nvmem/nvmem.c` +- Linux NVMEM framework: Documentation reference for compatible design diff --git a/documentation/6.components/device-driver/device_driver_model/nvmem/README_zh.md b/documentation/6.components/device-driver/device_driver_model/nvmem/README_zh.md new file mode 100644 index 00000000000..ab7985183e5 --- /dev/null +++ b/documentation/6.components/device-driver/device_driver_model/nvmem/README_zh.md @@ -0,0 +1,883 @@ +# NVMEM 框架 + +## 1. 概述 + +### 1.1 什么是 NVMEM? + +NVMEM(Non-Volatile Memory)是一个用于访问非易失性存储器的框架,提供了统一的接口来读写各种类型的非易失性存储设备,如: +- EEPROM(I2C、SPI) +- OTP(One-Time Programmable)存储器 +- eFuse +- NVRAM +- 电池支持的 SRAM +- Flash 存储器的特定区域 + +### 1.2 RT-Thread 中的实现 + +RT-Thread 的 NVMEM 框架基于 Linux 内核的 NVMEM 子系统设计,提供: +- **基于 Cell 的组织**:将存储器划分为命名的单元(cells) +- **设备树集成**:通过设备树描述存储器布局 +- **消费者/提供者模型**:驱动程序可以是 NVMEM 的消费者或提供者 +- **类型化访问**:支持 u8/u16/u32/u64 的直接读取 +- **位级访问**:支持比特级的精确访问 +- **写保护支持**:GPIO 控制的写保护 + +## 2. Kconfig 配置 + +### 2.1 启用 NVMEM 支持 + +``` +Device Drivers ---> + [*] Using NVMEM (Non-Volatile Memory) device drivers +``` + +**配置选项**: +- `RT_USING_NVMEM`:启用 NVMEM 框架支持 + +### 2.2 在 menuconfig 中的位置 + +``` +RT-Thread Configuration + → Device Drivers + → Using NVMEM (Non-Volatile Memory) device drivers +``` + +## 3. 设备树绑定 + +### 3.1 NVMEM 提供者 + +定义一个 NVMEM 设备(如 EEPROM): + +```dts +eeprom@50 { + compatible = "atmel,24c256"; + reg = <0x50>; + #address-cells = <1>; + #size-cells = <1>; + + /* 定义 NVMEM cells */ + mac_address: mac-addr@0 { + reg = <0x0 0x6>; /* 偏移 0x0,长度 6 字节 */ + }; + + board_id: board-id@6 { + reg = <0x6 0x2>; /* 偏移 0x6,长度 2 字节 */ + }; + + serial_number: serial@8 { + reg = <0x8 0x10>; /* 偏移 0x8,长度 16 字节 */ + }; + + calibration: calib@20 { + reg = <0x20 0x10>; /* 偏移 0x20,长度 16 字节 */ + bits = <0 32>; /* 位偏移 0,位长度 32 */ + }; +}; +``` + +### 3.2 NVMEM 消费者 + +引用 NVMEM cells: + +```dts +ethernet@40028000 { + compatible = "vendor,eth"; + reg = <0x40028000 0x1000>; + + /* 引用 MAC 地址 cell */ + nvmem-cells = <&mac_address>; + nvmem-cell-names = "mac-address"; +}; + +adc@40012000 { + compatible = "vendor,adc"; + reg = <0x40012000 0x400>; + + /* 引用校准数据 */ + nvmem-cells = <&calibration>; + nvmem-cell-names = "calibration"; +}; +``` + +### 3.3 写保护 GPIO + +带写保护引脚的 EEPROM: + +```dts +eeprom@50 { + compatible = "atmel,24c256"; + reg = <0x50>; + + /* 写保护 GPIO */ + wp-gpios = <&gpio1 10 GPIO_ACTIVE_HIGH>; + + mac_address: mac-addr@0 { + reg = <0x0 0x6>; + }; +}; +``` + +## 4. 应用层 API + +### 4.1 Cell 获取操作 + +#### 4.1.1 rt_nvmem_get_cell_by_name + +通过名称获取 NVMEM cell。 + +```c +struct rt_nvmem_cell *rt_nvmem_get_cell_by_name(struct rt_device *dev, + const char *name); +``` + +**参数**: +- `dev`:设备指针 +- `name`:cell 名称(设备树中的 nvmem-cell-names) + +**返回值**: +- 成功:cell 指针 +- 失败:RT_NULL + +**示例**: +```c +struct rt_nvmem_cell *cell; + +cell = rt_nvmem_get_cell_by_name(dev, "mac-address"); +if (!cell) { + rt_kprintf("Failed to get NVMEM cell\n"); + return -RT_ERROR; +} +``` + +#### 4.1.2 rt_nvmem_get_cell_by_index + +通过索引获取 NVMEM cell。 + +```c +struct rt_nvmem_cell *rt_nvmem_get_cell_by_index(struct rt_device *dev, + rt_uint32_t index); +``` + +**参数**: +- `dev`:设备指针 +- `index`:cell 索引(从 0 开始) + +**返回值**: +- 成功:cell 指针 +- 失败:RT_NULL + +#### 4.1.3 rt_nvmem_put_cell + +释放 NVMEM cell 引用。 + +```c +void rt_nvmem_put_cell(struct rt_nvmem_cell *cell); +``` + +**参数**: +- `cell`:要释放的 cell 指针 + +### 4.2 数据访问操作 + +#### 4.2.1 rt_nvmem_cell_read + +从 cell 读取数据。 + +```c +rt_ssize_t rt_nvmem_cell_read(struct rt_nvmem_cell *cell, + rt_off_t *offset, + void *buf, + rt_size_t size); +``` + +**参数**: +- `cell`:cell 指针 +- `offset`:读取偏移(可为 RT_NULL,使用 cell 的偏移) +- `buf`:数据缓冲区 +- `size`:要读取的字节数 + +**返回值**: +- 成功:实际读取的字节数 +- 失败:负错误码 + +**示例**: +```c +rt_uint8_t mac[6]; +rt_ssize_t ret; + +ret = rt_nvmem_cell_read(cell, RT_NULL, mac, sizeof(mac)); +if (ret != sizeof(mac)) { + rt_kprintf("Failed to read MAC address\n"); + return -RT_ERROR; +} +``` + +#### 4.2.2 rt_nvmem_cell_write + +向 cell 写入数据。 + +```c +rt_ssize_t rt_nvmem_cell_write(struct rt_nvmem_cell *cell, + rt_off_t *offset, + const void *buf, + rt_size_t size); +``` + +**参数**: +- `cell`:cell 指针 +- `offset`:写入偏移(可为 RT_NULL) +- `buf`:要写入的数据 +- `size`:要写入的字节数 + +**返回值**: +- 成功:实际写入的字节数 +- 失败:负错误码 + +**注意**: +- 写操作可能受写保护 GPIO 限制 +- 某些 NVMEM 设备可能是只读的 + +### 4.3 类型化读取操作 + +#### 4.3.1 rt_nvmem_cell_read_u8 + +读取 8 位无符号整数。 + +```c +rt_err_t rt_nvmem_cell_read_u8(struct rt_nvmem_cell *cell, + rt_off_t *offset, + rt_uint8_t *val); +``` + +#### 4.3.2 rt_nvmem_cell_read_u16 + +读取 16 位无符号整数。 + +```c +rt_err_t rt_nvmem_cell_read_u16(struct rt_nvmem_cell *cell, + rt_off_t *offset, + rt_uint16_t *val); +``` + +#### 4.3.3 rt_nvmem_cell_read_u32 + +读取 32 位无符号整数。 + +```c +rt_err_t rt_nvmem_cell_read_u32(struct rt_nvmem_cell *cell, + rt_off_t *offset, + rt_uint32_t *val); +``` + +#### 4.3.4 rt_nvmem_cell_read_u64 + +读取 64 位无符号整数。 + +```c +rt_err_t rt_nvmem_cell_read_u64(struct rt_nvmem_cell *cell, + rt_off_t *offset, + rt_uint64_t *val); +``` + +**参数**: +- `cell`:cell 指针 +- `offset`:读取偏移(可为 RT_NULL) +- `val`:存储读取值的指针 + +**返回值**: +- `RT_EOK`:成功 +- 负错误码:失败 + +**示例**: +```c +rt_uint32_t board_id; + +if (rt_nvmem_cell_read_u32(cell, RT_NULL, &board_id) == RT_EOK) { + rt_kprintf("Board ID: 0x%08x\n", board_id); +} +``` + +## 5. 应用层使用示例 + +### 5.1 以太网驱动:从 EEPROM 读取 MAC 地址 + +```c +#include +#include +#include +#include + +struct eth_device { + struct rt_device parent; + struct rt_device_phy phy; + rt_uint8_t mac_addr[6]; + /* 其他字段... */ +}; + +static rt_err_t eth_load_mac_address(struct eth_device *eth, + struct rt_device *dev) +{ + struct rt_nvmem_cell *cell; + rt_ssize_t ret; + + /* 通过名称获取 MAC 地址 cell */ + cell = rt_nvmem_get_cell_by_name(dev, "mac-address"); + if (!cell) { + rt_kprintf("No MAC address in NVMEM\n"); + return -RT_ERROR; + } + + /* 读取 MAC 地址 */ + ret = rt_nvmem_cell_read(cell, RT_NULL, eth->mac_addr, 6); + if (ret != 6) { + rt_kprintf("Failed to read MAC address: %d\n", ret); + rt_nvmem_put_cell(cell); + return -RT_ERROR; + } + + /* 释放 cell */ + rt_nvmem_put_cell(cell); + + /* 验证 MAC 地址 */ + if (eth->mac_addr[0] == 0x00 && eth->mac_addr[1] == 0x00 && + eth->mac_addr[2] == 0x00 && eth->mac_addr[3] == 0x00 && + eth->mac_addr[4] == 0x00 && eth->mac_addr[5] == 0x00) { + rt_kprintf("Invalid MAC address (all zeros)\n"); + return -RT_ERROR; + } + + rt_kprintf("MAC Address: %02x:%02x:%02x:%02x:%02x:%02x\n", + eth->mac_addr[0], eth->mac_addr[1], eth->mac_addr[2], + eth->mac_addr[3], eth->mac_addr[4], eth->mac_addr[5]); + + return RT_EOK; +} + +static rt_err_t eth_probe(struct rt_platform_device *pdev) +{ + struct eth_device *eth; + rt_err_t ret; + + eth = rt_calloc(1, sizeof(*eth)); + if (!eth) { + return -RT_ENOMEM; + } + + /* 从 NVMEM 加载 MAC 地址 */ + ret = eth_load_mac_address(eth, &pdev->parent); + if (ret != RT_EOK) { + /* 使用默认或随机 MAC 地址 */ + rt_kprintf("Using default MAC address\n"); + eth->mac_addr[0] = 0x00; + eth->mac_addr[1] = 0x11; + eth->mac_addr[2] = 0x22; + eth->mac_addr[3] = 0x33; + eth->mac_addr[4] = 0x44; + eth->mac_addr[5] = 0x55; + } + + /* 将 MAC 地址写入硬件寄存器 */ + /* ... */ + + rt_platform_device_set_data(pdev, eth); + + return RT_EOK; +} + +static struct rt_platform_driver eth_driver = { + .name = "eth-driver", + .ids = (struct rt_ofw_node_id[]) { + { .compatible = "vendor,ethernet" }, + { /* sentinel */ } + }, + .probe = eth_probe, +}; +``` + +### 5.2 读取板卡 ID 和序列号 + +```c +static rt_err_t board_read_info(struct rt_device *dev) +{ + struct rt_nvmem_cell *board_id_cell, *serial_cell; + rt_uint16_t board_id; + char serial[17] = {0}; /* 16 字节 + null */ + rt_err_t ret; + + /* 读取板卡 ID */ + board_id_cell = rt_nvmem_get_cell_by_name(dev, "board-id"); + if (board_id_cell) { + ret = rt_nvmem_cell_read_u16(board_id_cell, RT_NULL, &board_id); + if (ret == RT_EOK) { + rt_kprintf("Board ID: %u\n", board_id); + } + rt_nvmem_put_cell(board_id_cell); + } + + /* 读取序列号 */ + serial_cell = rt_nvmem_get_cell_by_name(dev, "serial-number"); + if (serial_cell) { + ret = rt_nvmem_cell_read(serial_cell, RT_NULL, serial, 16); + if (ret > 0) { + rt_kprintf("Serial Number: %s\n", serial); + } + rt_nvmem_put_cell(serial_cell); + } + + return RT_EOK; +} +``` + +## 6. 驱动实现接口 + +### 6.1 NVMEM 设备注册 + +#### 6.1.1 rt_nvmem_device_register + +注册 NVMEM 设备。 + +```c +rt_err_t rt_nvmem_device_register(struct rt_nvmem_device *nvmem); +``` + +**参数**: +- `nvmem`:NVMEM 设备结构体 + +**返回值**: +- `RT_EOK`:成功 +- 负错误码:失败 + +#### 6.1.2 rt_nvmem_device_unregister + +注销 NVMEM 设备。 + +```c +rt_err_t rt_nvmem_device_unregister(struct rt_nvmem_device *nvmem); +``` + +#### 6.1.3 rt_nvmem_device_append_cell + +向 NVMEM 设备添加 cell。 + +```c +rt_err_t rt_nvmem_device_append_cell(struct rt_nvmem_device *nvmem, + struct rt_nvmem_cell_info *info); +``` + +### 6.2 NVMEM 设备结构 + +```c +struct rt_nvmem_device { + struct rt_device parent; + const struct rt_nvmem_ops *ops; + rt_size_t size; /* 总大小 */ + rt_size_t word_size; /* 字大小 */ + rt_size_t stride; /* 访问步长 */ + rt_bool_t read_only; /* 只读标志 */ + struct rt_ofw_node *np; /* 设备树节点 */ + void *priv; /* 私有数据 */ +}; +``` + +### 6.3 NVMEM 操作接口 + +```c +struct rt_nvmem_ops { + rt_ssize_t (*read)(struct rt_nvmem_device *nvmem, rt_off_t offset, + void *buf, rt_size_t size); + rt_ssize_t (*write)(struct rt_nvmem_device *nvmem, rt_off_t offset, + const void *buf, rt_size_t size); +}; +``` + +### 6.4 I2C EEPROM 驱动示例 + +```c +#include +#include +#include +#include +#include + +struct eeprom_priv { + struct rt_i2c_client *client; + struct rt_nvmem_device nvmem; + struct rt_gpio_pin *wp_gpio; /* 写保护 GPIO */ +}; + +static rt_ssize_t eeprom_read(struct rt_nvmem_device *nvmem, + rt_off_t offset, + void *buf, + rt_size_t size) +{ + struct eeprom_priv *priv = nvmem->priv; + struct rt_i2c_msg msgs[2]; + rt_uint8_t addr_buf[2]; + rt_ssize_t ret; + + /* 设置地址(大端序) */ + addr_buf[0] = (offset >> 8) & 0xFF; + addr_buf[1] = offset & 0xFF; + + /* 写地址 */ + msgs[0].addr = priv->client->client_addr; + msgs[0].flags = RT_I2C_WR; + msgs[0].buf = addr_buf; + msgs[0].len = 2; + + /* 读数据 */ + msgs[1].addr = priv->client->client_addr; + msgs[1].flags = RT_I2C_RD; + msgs[1].buf = buf; + msgs[1].len = size; + + ret = rt_i2c_transfer(priv->client->bus, msgs, 2); + if (ret != 2) { + return -RT_ERROR; + } + + return size; +} + +static rt_ssize_t eeprom_write(struct rt_nvmem_device *nvmem, + rt_off_t offset, + const void *buf, + rt_size_t size) +{ + struct eeprom_priv *priv = nvmem->priv; + struct rt_i2c_msg msg; + rt_uint8_t *write_buf; + rt_ssize_t ret; + rt_size_t written = 0; + rt_size_t page_size = 64; /* EEPROM 页大小 */ + + /* 禁用写保护 */ + if (priv->wp_gpio) { + rt_pin_write(priv->wp_gpio->pin, PIN_LOW); + rt_thread_mdelay(1); + } + + write_buf = rt_malloc(page_size + 2); + if (!write_buf) { + ret = -RT_ENOMEM; + goto out; + } + + /* 逐页写入 */ + while (size > 0) { + rt_size_t chunk = RT_MIN(size, page_size); + rt_size_t page_offset = offset % page_size; + + /* 如果不是页对齐,调整写入大小 */ + if (page_offset + chunk > page_size) { + chunk = page_size - page_offset; + } + + /* 准备写缓冲区:地址 + 数据 */ + write_buf[0] = (offset >> 8) & 0xFF; + write_buf[1] = offset & 0xFF; + rt_memcpy(&write_buf[2], buf + written, chunk); + + /* 写入 */ + msg.addr = priv->client->client_addr; + msg.flags = RT_I2C_WR; + msg.buf = write_buf; + msg.len = chunk + 2; + + ret = rt_i2c_transfer(priv->client->bus, &msg, 1); + if (ret != 1) { + rt_kprintf("EEPROM write failed at offset %d\n", offset); + ret = -RT_ERROR; + goto cleanup; + } + + /* 等待写周期完成(~5ms) */ + rt_thread_mdelay(5); + + offset += chunk; + written += chunk; + size -= chunk; + } + + ret = written; + +cleanup: + rt_free(write_buf); + +out: + /* 重新启用写保护 */ + if (priv->wp_gpio) { + rt_pin_write(priv->wp_gpio->pin, PIN_HIGH); + } + + return ret; +} + +static const struct rt_nvmem_ops eeprom_ops = { + .read = eeprom_read, + .write = eeprom_write, +}; + +static rt_err_t eeprom_probe(struct rt_platform_device *pdev) +{ + struct eeprom_priv *priv; + struct rt_ofw_node *np = pdev->parent.ofw_node; + rt_uint32_t size; + rt_err_t ret; + + priv = rt_calloc(1, sizeof(*priv)); + if (!priv) { + return -RT_ENOMEM; + } + + /* 获取 I2C 客户端 */ + priv->client = (struct rt_i2c_client *)pdev->parent.parent; + + /* 获取 EEPROM 大小 */ + if (rt_ofw_prop_read_u32(np, "size", &size)) { + size = 32768; /* 默认 32KB */ + } + + /* 获取写保护 GPIO */ + priv->wp_gpio = rt_ofw_get_named_pin(np, "wp-gpios", 0, NULL, NULL); + if (priv->wp_gpio) { + rt_pin_mode(priv->wp_gpio->pin, PIN_MODE_OUTPUT); + rt_pin_write(priv->wp_gpio->pin, PIN_HIGH); /* 初始启用写保护 */ + } + + /* 初始化 NVMEM 设备 */ + priv->nvmem.parent = pdev->parent; + priv->nvmem.ops = &eeprom_ops; + priv->nvmem.size = size; + priv->nvmem.word_size = 1; + priv->nvmem.stride = 1; + priv->nvmem.read_only = RT_FALSE; + priv->nvmem.np = np; + priv->nvmem.priv = priv; + + /* 注册 NVMEM 设备 */ + ret = rt_nvmem_device_register(&priv->nvmem); + if (ret != RT_EOK) { + rt_kprintf("Failed to register NVMEM device\n"); + rt_free(priv); + return ret; + } + + rt_platform_device_set_data(pdev, priv); + + rt_kprintf("EEPROM registered: %u bytes\n", size); + + return RT_EOK; +} + +static struct rt_platform_driver eeprom_driver = { + .name = "i2c-eeprom", + .ids = (struct rt_ofw_node_id[]) { + { .compatible = "atmel,24c256" }, + { .compatible = "microchip,24lc256" }, + { /* sentinel */ } + }, + .probe = eeprom_probe, +}; +``` + +## 7. 最佳实践 + +### 7.1 消费者最佳实践 + +1. **总是检查返回值**: +```c +cell = rt_nvmem_get_cell_by_name(dev, "mac-address"); +if (!cell) { + /* 处理错误 */ +} +``` + +2. **及时释放 cells**: +```c +rt_nvmem_put_cell(cell); +``` + +3. **验证数据有效性**: +```c +if (rt_nvmem_cell_read(cell, RT_NULL, data, size) == size) { + /* 检查数据是否有效 */ + if (is_data_valid(data)) { + use_data(data); + } +} +``` + +4. **提供默认值**: +```c +if (rt_nvmem_cell_read_u32(cell, RT_NULL, &value) != RT_EOK) { + value = DEFAULT_VALUE; +} +``` + +### 7.2 提供者最佳实践 + +1. **实现错误处理**: +```c +static rt_ssize_t nvmem_read(struct rt_nvmem_device *nvmem, + rt_off_t offset, void *buf, rt_size_t size) +{ + /* 检查参数 */ + if (offset + size > nvmem->size) { + return -RT_EINVAL; + } + + /* 执行读取 */ + /* ... */ +} +``` + +2. **处理写保护**: +```c +static rt_ssize_t nvmem_write(struct rt_nvmem_device *nvmem, + rt_off_t offset, const void *buf, rt_size_t size) +{ + struct priv *priv = nvmem->priv; + + /* 禁用写保护 */ + if (priv->wp_gpio) { + rt_pin_write(priv->wp_gpio->pin, PIN_LOW); + } + + /* 执行写入 */ + /* ... */ + + /* 重新启用写保护 */ + if (priv->wp_gpio) { + rt_pin_write(priv->wp_gpio->pin, PIN_HIGH); + } + + return size; +} +``` + +3. **实现页写入**: +```c +/* 对于 EEPROM,尊重页边界 */ +while (size > 0) { + rt_size_t chunk = RT_MIN(size, page_size); + /* 写入块 */ + /* 等待写周期 */ + rt_thread_mdelay(write_cycle_time); +} +``` + +## 8. 故障排除 + +### 8.1 常见问题 + +**问题:获取 cell 失败** +``` +错误:rt_nvmem_get_cell_by_name() 返回 RT_NULL +``` +**解决方案**: +- 检查设备树中的 nvmem-cells 和 nvmem-cell-names 属性 +- 验证 NVMEM 提供者已正确注册 +- 确认 cell 名称拼写正确 + +**问题:读取操作返回错误** +``` +错误:rt_nvmem_cell_read() 返回负值 +``` +**解决方案**: +- 检查 NVMEM 设备是否可访问 +- 验证偏移和大小是否在范围内 +- 检查总线通信(I2C、SPI) + +**问题:写入操作失败** +``` +错误:rt_nvmem_cell_write() 失败 +``` +**解决方案**: +- 检查设备是否为只读 +- 验证写保护 GPIO 是否正确配置 +- 确认写入时序(等待写周期) + +### 8.2 调试技巧 + +1. **启用 NVMEM 调试日志**: +```c +#define NVMEM_DEBUG +``` + +2. **验证设备树配置**: +```bash +# 检查 NVMEM 节点 +cat /proc/device-tree/eeprom@50/compatible +``` + +3. **测试读写操作**: +```c +/* 读取测试 */ +rt_uint8_t test_data[16]; +ret = rt_nvmem_cell_read(cell, RT_NULL, test_data, sizeof(test_data)); +rt_kprintf("Read %d bytes\n", ret); + +/* 转储数据 */ +for (int i = 0; i < ret; i++) { + rt_kprintf("%02x ", test_data[i]); +} +rt_kprintf("\n"); +``` + +## 9. 性能考虑 + +### 9.1 读取优化 + +1. **批量读取**: +```c +/* 好:一次读取所有数据 */ +rt_nvmem_cell_read(cell, RT_NULL, buffer, total_size); + +/* 差:多次小读取 */ +for (i = 0; i < count; i++) { + rt_nvmem_cell_read(cell, &offset, &buffer[i], 1); +} +``` + +2. **缓存数据**: +```c +/* 在初始化时读取,然后缓存 */ +static rt_uint8_t cached_mac[6]; +static rt_bool_t mac_loaded = RT_FALSE; + +if (!mac_loaded) { + rt_nvmem_cell_read(cell, RT_NULL, cached_mac, 6); + mac_loaded = RT_TRUE; +} +``` + +### 9.2 写入优化 + +1. **页对齐写入**: +```c +/* 对齐到页边界以获得最佳性能 */ +offset = ALIGN(offset, page_size); +``` + +2. **最小化写周期**: +```c +/* 仅写入更改的数据 */ +if (memcmp(old_data, new_data, size) != 0) { + rt_nvmem_cell_write(cell, RT_NULL, new_data, size); +} +``` + +## 10. 相关模块 + +- **OFW**:设备树解析和 cell 查找 +- **I2C/SPI**:EEPROM 设备的总线接口 +- **GPIO**:写保护引脚控制 +- **Platform**:NVMEM 驱动注册 + +## 11. 参考资料 + +- `components/drivers/include/drivers/nvmem.h`:NVMEM API 头文件 +- `components/drivers/nvmem/`:NVMEM 核心实现 +- Linux Kernel NVMEM 子系统文档 +- 设备树规范 - NVMEM 绑定 diff --git a/documentation/6.components/device-driver/device_driver_model/pic/README.md b/documentation/6.components/device-driver/device_driver_model/pic/README.md new file mode 100644 index 00000000000..1cc483f0f31 --- /dev/null +++ b/documentation/6.components/device-driver/device_driver_model/pic/README.md @@ -0,0 +1,966 @@ +# Platform Interrupt Controller (PIC) Framework + +## Introduction + +The Platform Interrupt Controller (PIC) framework provides a unified abstraction layer for managing hardware interrupts in RT-Thread's Device Driver Model. It supports various interrupt controllers including ARM GIC (Generic Interrupt Controller) v1/v2/v3, and provides comprehensive interrupt management capabilities including cascading, MSI support, and inter-processor interrupts (IPI). + +### Key Features + +- **Unified Interrupt Management**: Abstract interface for all interrupt controllers +- **Interrupt Cascading**: Support for hierarchical interrupt routing +- **CPU Affinity**: Configure which CPUs can handle specific interrupts +- **Priority Control**: Set interrupt priorities for different IRQ sources +- **Trigger Modes**: Support edge/level triggered interrupts +- **IPI Support**: Inter-processor interrupt mechanisms for multi-core systems +- **MSI/MSI-X**: Message Signaled Interrupts for PCIe devices +- **Statistics**: Optional ISR execution time tracking + +### Architecture + +The PIC framework consists of: + +1. **PIC Core** (`pic.c`): Central interrupt management and routing +2. **PIC IRQ** (`struct rt_pic_irq`): Individual interrupt descriptor +3. **PIC Operations** (`struct rt_pic_ops`): Hardware-specific callbacks +4. **ISR Management**: Interrupt Service Routine registration and execution + +``` +┌──────────────────────────────────────────┐ +│ Application/Drivers │ +│ (Call rt_pic_attach_irq, enable, etc.) │ +└──────────────────┬───────────────────────┘ + │ +┌──────────────────▼───────────────────────┐ +│ PIC Framework (pic.c) │ +│ - IRQ allocation & management │ +│ - ISR dispatch │ +│ - Cascading support │ +│ - Affinity & priority management │ +└──────────────────┬───────────────────────┘ + │ +┌──────────────────▼───────────────────────┐ +│ PIC Operations (rt_pic_ops) │ +│ - irq_enable/disable │ +│ - irq_mask/unmask │ +│ - irq_set_priority │ +│ - irq_set_affinity │ +│ - irq_send_ipi (for multi-core) │ +└──────────────────┬───────────────────────┘ + │ +┌──────────────────▼───────────────────────┐ +│ Hardware Interrupt Controller │ +│ (GICv2, GICv3, etc.) │ +└──────────────────────────────────────────┘ +``` + +## Kconfig Configuration + +### Location in menuconfig + +``` +RT-Thread Components + └── Device Drivers + └── Using Device Driver Model with DeviceTree (RT_USING_DM) + └── Using Programmable Interrupt Controller (PIC) (RT_USING_PIC) +``` + +### Configuration Options + +#### RT_USING_PIC +- **Type**: bool +- **Default**: n +- **Description**: Enable the Platform Interrupt Controller framework +- **Dependencies**: RT_USING_DM, RT_USING_ADT, RT_USING_ADT_BITMAP +- **Purpose**: Main switch for PIC support + +#### RT_USING_PIC_STATISTICS +- **Type**: bool +- **Default**: n +- **Description**: Enable ISR execution time statistics +- **Dependencies**: RT_USING_PIC, RT_USING_KTIME, RT_USING_INTERRUPT_INFO +- **Purpose**: Track min/max/average IRQ handling times for profiling + +#### MAX_HANDLERS +- **Type**: int +- **Range**: 1-4294967294 +- **Default**: 256 +- **Description**: Maximum number of interrupt handlers the system can register +- **Purpose**: Defines the size of the global IRQ hash table + +#### RT_PIC_ARM_GIC +- **Type**: bool +- **Default**: n +- **Description**: Enable ARM Generic Interrupt Controller v1/v2 support +- **Dependencies**: RT_USING_PIC, RT_USING_OFW +- **Purpose**: Support for ARM GICv1/v2 hardware + +#### RT_PIC_ARM_GIC_V2M +- **Type**: bool +- **Default**: n +- **Description**: Enable ARM GIC V2M (MSI support for GICv2) +- **Dependencies**: RT_PIC_ARM_GIC, RT_PCI_MSI, RT_USING_OFW +- **Purpose**: Message Signaled Interrupts on GICv2 + +#### RT_PIC_ARM_GIC_V3 +- **Type**: bool +- **Default**: n +- **Description**: Enable ARM Generic Interrupt Controller v3 support +- **Dependencies**: RT_USING_PIC, RT_USING_OFW +- **Purpose**: Support for ARM GICv3 hardware + +#### RT_PIC_ARM_GIC_V3_ITS +- **Type**: bool +- **Default**: n +- **Description**: Enable ARM GICv3 ITS (Interrupt Translation Service) +- **Dependencies**: RT_PIC_ARM_GIC_V3, RT_PCI_MSI, RT_USING_OFW, RT_USING_ADT_REF +- **Purpose**: MSI/MSI-X support for GICv3 via ITS + +#### RT_PIC_ARM_GIC_V3_ITS_IRQ_MAX +- **Type**: int +- **Default**: 127 (64-bit), 63 (32-bit) +- **Description**: Maximum IRQ number for GICv3 ITS +- **Dependencies**: RT_PIC_ARM_GIC_V3_ITS +- **Purpose**: Limit ITS-managed interrupt range + +## Device Tree Bindings + +### GICv2 Example + +```dts +gic: interrupt-controller@08000000 { + compatible = "arm,cortex-a15-gic", "arm,cortex-a9-gic"; + #interrupt-cells = <3>; + #address-cells = <1>; + interrupt-controller; + reg = <0x08000000 0x1000>, /* Distributor */ + <0x08010000 0x1000>; /* CPU interface */ +}; + +/* Consumer example: UART using GIC */ +uart0: serial@09000000 { + compatible = "arm,pl011", "arm,primecell"; + reg = <0x09000000 0x1000>; + interrupts = <0 5 4>; /* SPI, IRQ 5, IRQ_TYPE_LEVEL_HIGH */ + interrupt-parent = <&gic>; +}; +``` + +### GICv3 Example + +```dts +gic: interrupt-controller@08000000 { + compatible = "arm,gic-v3"; + #interrupt-cells = <3>; + #address-cells = <2>; + #size-cells = <2>; + interrupt-controller; + reg = <0x0 0x08000000 0 0x10000>, /* Distributor */ + <0x0 0x080A0000 0 0xF60000>; /* Redistributor */ + + gic_its: msi-controller@08020000 { + compatible = "arm,gic-v3-its"; + msi-controller; + #msi-cells = <1>; + reg = <0x0 0x08020000 0 0x20000>; + }; +}; + +/* Consumer with interrupts */ +timer { + compatible = "arm,armv8-timer"; + interrupts = <1 13 0xff08>, /* Physical Secure PPI */ + <1 14 0xff08>, /* Physical Non-Secure PPI */ + <1 11 0xff08>, /* Virtual PPI */ + <1 10 0xff08>; /* Hypervisor PPI */ + interrupt-parent = <&gic>; +}; +``` + +### Interrupt Cells Explanation + +For ARM GIC, `interrupts` property has 3 cells: +1. **Type**: 0 = SPI (Shared Peripheral Interrupt), 1 = PPI (Private Peripheral Interrupt) +2. **Number**: IRQ number within the type (SPI: 0-987, PPI: 0-15) +3. **Flags**: Trigger type and priority + - `IRQ_TYPE_EDGE_RISING` = 1 + - `IRQ_TYPE_EDGE_FALLING` = 2 + - `IRQ_TYPE_LEVEL_HIGH` = 4 + - `IRQ_TYPE_LEVEL_LOW` = 8 + +## Application API + +### IRQ Attachment + +#### rt_pic_attach_irq + +```c +rt_err_t rt_pic_attach_irq(int irq, rt_isr_handler_t handler, void *uid, + const char *name, int flags); +``` + +Attach an interrupt service routine to an IRQ. + +**Parameters:** +- `irq`: IRQ number to attach to +- `handler`: ISR function pointer `void (*handler)(int irq, void *param)` +- `uid`: User-defined parameter passed to the handler +- `name`: Descriptive name for the interrupt (for debugging) +- `flags`: Interrupt flags (currently unused, pass 0) + +**Returns:** +- `RT_EOK`: Success +- `-RT_EINVAL`: Invalid IRQ number +- `-RT_ENOMEM`: Out of memory + +**Example:** +```c +void uart_isr(int irq, void *param) +{ + struct uart_device *uart = (struct uart_device *)param; + /* Handle interrupt */ + rt_kprintf("UART interrupt occurred\n"); +} + +/* In driver probe function */ +static rt_err_t uart_probe(struct rt_platform_device *pdev) +{ + struct uart_device *uart; + int irq; + + /* Get IRQ from device tree */ + irq = rt_platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + /* Attach ISR */ + rt_err_t ret = rt_pic_attach_irq(irq, uart_isr, uart, "uart0", 0); + if (ret != RT_EOK) + return ret; + + /* Enable the interrupt */ + rt_pic_irq_enable(irq); + + return RT_EOK; +} +``` + +#### rt_pic_detach_irq + +```c +rt_err_t rt_pic_detach_irq(int irq, void *uid); +``` + +Detach an interrupt service routine from an IRQ. + +**Parameters:** +- `irq`: IRQ number +- `uid`: Must match the `uid` passed to `rt_pic_attach_irq` + +**Returns:** +- `RT_EOK`: Success +- `-RT_EINVAL`: Invalid IRQ or UID mismatch + +### IRQ Control + +#### rt_pic_irq_enable / rt_pic_irq_disable + +```c +void rt_pic_irq_enable(int irq); +void rt_pic_irq_disable(int irq); +``` + +Enable or disable an interrupt at the PIC level. + +**Parameters:** +- `irq`: IRQ number + +**Example:** +```c +/* Enable interrupt before starting operation */ +rt_pic_irq_enable(uart_irq); + +/* Disable during critical sections */ +rt_pic_irq_disable(uart_irq); +/* ... critical code ... */ +rt_pic_irq_enable(uart_irq); +``` + +#### rt_pic_irq_mask / rt_pic_irq_unmask + +```c +void rt_pic_irq_mask(int irq); +void rt_pic_irq_unmask(int irq); +``` + +Mask or unmask an interrupt (similar to enable/disable but may have different semantics on some hardware). + +**Parameters:** +- `irq`: IRQ number + +#### rt_pic_irq_ack + +```c +void rt_pic_irq_ack(int irq); +``` + +Acknowledge an interrupt (required for some edge-triggered interrupts). + +**Parameters:** +- `irq`: IRQ number + +#### rt_pic_irq_eoi + +```c +void rt_pic_irq_eoi(int irq); +``` + +Signal End-Of-Interrupt to the PIC. + +**Parameters:** +- `irq`: IRQ number + +### Priority Management + +#### rt_pic_irq_set_priority + +```c +rt_err_t rt_pic_irq_set_priority(int irq, rt_uint32_t priority); +``` + +Set the priority of an interrupt. + +**Parameters:** +- `irq`: IRQ number +- `priority`: Priority value (lower = higher priority typically) + +**Returns:** +- `RT_EOK`: Success +- `-RT_ENOSYS`: Not supported by hardware + +**Example:** +```c +/* Set high priority for critical timer interrupt */ +rt_pic_irq_set_priority(timer_irq, 0); + +/* Set lower priority for UART */ +rt_pic_irq_set_priority(uart_irq, 128); +``` + +#### rt_pic_irq_get_priority + +```c +rt_uint32_t rt_pic_irq_get_priority(int irq); +``` + +Get the current priority of an interrupt. + +**Parameters:** +- `irq`: IRQ number + +**Returns:** Current priority value + +### CPU Affinity (Multi-core) + +#### rt_pic_irq_set_affinity + +```c +rt_err_t rt_pic_irq_set_affinity(int irq, rt_bitmap_t *affinity); +``` + +Set which CPUs can handle a specific interrupt. + +**Parameters:** +- `irq`: IRQ number +- `affinity`: Bitmap of allowed CPUs + +**Returns:** +- `RT_EOK`: Success +- `-RT_ENOSYS`: Not supported + +**Example:** +```c +RT_IRQ_AFFINITY_DECLARE(cpumask); + +/* Route interrupt to CPU 0 and CPU 2 */ +rt_bitmap_clear(cpumask, RT_BITMAP_LEN(RT_CPUS_NR)); +RT_IRQ_AFFINITY_SET(cpumask, 0); +RT_IRQ_AFFINITY_SET(cpumask, 2); +rt_pic_irq_set_affinity(eth_irq, cpumask); +``` + +#### rt_pic_irq_get_affinity + +```c +rt_err_t rt_pic_irq_get_affinity(int irq, rt_bitmap_t *out_affinity); +``` + +Get the current CPU affinity for an interrupt. + +**Parameters:** +- `irq`: IRQ number +- `out_affinity`: Output bitmap + +**Returns:** +- `RT_EOK`: Success +- `-RT_EINVAL`: Invalid IRQ + +### Trigger Mode + +#### rt_pic_irq_set_triger_mode + +```c +rt_err_t rt_pic_irq_set_triger_mode(int irq, rt_uint32_t mode); +``` + +Set the trigger mode for an interrupt. + +**Parameters:** +- `irq`: IRQ number +- `mode`: Trigger mode + - `RT_IRQ_MODE_EDGE_RISING`: Rising edge + - `RT_IRQ_MODE_EDGE_FALLING`: Falling edge + - `RT_IRQ_MODE_EDGE_BOTH`: Both edges + - `RT_IRQ_MODE_LEVEL_HIGH`: High level + - `RT_IRQ_MODE_LEVEL_LOW`: Low level + +**Returns:** +- `RT_EOK`: Success +- `-RT_ENOSYS`: Not supported + +#### rt_pic_irq_get_triger_mode + +```c +rt_uint32_t rt_pic_irq_get_triger_mode(int irq); +``` + +Get the current trigger mode. + +**Parameters:** +- `irq`: IRQ number + +**Returns:** Current trigger mode + +### Inter-Processor Interrupts (IPI) + +#### rt_pic_irq_send_ipi + +```c +void rt_pic_irq_send_ipi(int irq, rt_bitmap_t *cpumask); +``` + +Send an inter-processor interrupt to specified CPUs. + +**Parameters:** +- `irq`: IPI number (typically in range 0-15) +- `cpumask`: Bitmap of target CPUs + +**Example:** +```c +RT_IRQ_AFFINITY_DECLARE(targets); + +/* Send IPI to CPU 1 */ +rt_bitmap_clear(targets, RT_BITMAP_LEN(RT_CPUS_NR)); +RT_IRQ_AFFINITY_SET(targets, 1); +rt_pic_irq_send_ipi(0, targets); /* IPI 0 */ +``` + +### State Management + +#### rt_pic_irq_set_state / rt_pic_irq_get_state + +```c +rt_err_t rt_pic_irq_set_state(int irq, int type, rt_bool_t state); +rt_err_t rt_pic_irq_get_state(int irq, int type, rt_bool_t *out_state); +``` + +Set or get interrupt state (pending, active, masked). + +**Parameters:** +- `irq`: IRQ number +- `type`: State type + - `RT_IRQ_STATE_PENDING`: Interrupt is pending + - `RT_IRQ_STATE_ACTIVE`: Interrupt is being serviced + - `RT_IRQ_STATE_MASKED`: Interrupt is masked +- `state` / `out_state`: State value + +**Returns:** +- `RT_EOK`: Success +- `-RT_ENOSYS`: Not supported + +## Complete Driver Example: GPIO Interrupt Handler + +```c +#include +#include +#include +#include + +struct gpio_int_device +{ + struct rt_device parent; + void *base; + int irq; + rt_uint32_t pin_mask; +}; + +/* GPIO interrupt service routine */ +static void gpio_isr(int irq, void *param) +{ + struct gpio_int_device *gpio = (struct gpio_int_device *)param; + rt_uint32_t status; + + /* Read interrupt status register */ + status = readl(gpio->base + GPIO_INT_STATUS); + + /* Clear interrupt */ + writel(status, gpio->base + GPIO_INT_CLEAR); + + /* Handle each pin */ + for (int i = 0; i < 32; i++) + { + if (status & (1 << i)) + { + rt_kprintf("GPIO pin %d triggered\n", i); + /* Notify application or trigger event */ + } + } + + /* Send EOI to PIC */ + rt_pic_irq_eoi(irq); +} + +static rt_err_t gpio_int_probe(struct rt_platform_device *pdev) +{ + struct gpio_int_device *gpio; + struct rt_device *dev = &pdev->parent; + int irq; + rt_err_t ret; + + gpio = rt_calloc(1, sizeof(*gpio)); + if (!gpio) + return -RT_ENOMEM; + + /* Map registers */ + gpio->base = rt_dm_dev_iomap(dev, 0); + if (!gpio->base) + { + ret = -RT_ERROR; + goto err_free; + } + + /* Get IRQ from device tree */ + irq = rt_platform_get_irq(pdev, 0); + if (irq < 0) + { + ret = irq; + goto err_iounmap; + } + gpio->irq = irq; + + /* Set interrupt trigger mode - rising edge */ + ret = rt_pic_irq_set_triger_mode(irq, RT_IRQ_MODE_EDGE_RISING); + if (ret != RT_EOK && ret != -RT_ENOSYS) + { + rt_kprintf("Failed to set trigger mode: %d\n", ret); + goto err_iounmap; + } + + /* Set interrupt priority (high priority) */ + ret = rt_pic_irq_set_priority(irq, 32); + if (ret != RT_EOK && ret != -RT_ENOSYS) + { + rt_kprintf("Failed to set priority: %d\n", ret); + } + + /* Attach interrupt handler */ + ret = rt_pic_attach_irq(irq, gpio_isr, gpio, "gpio-int", 0); + if (ret != RT_EOK) + { + rt_kprintf("Failed to attach IRQ: %d\n", ret); + goto err_iounmap; + } + + /* Configure GPIO pins for interrupt */ + writel(0xFFFFFFFF, gpio->base + GPIO_INT_ENABLE); /* Enable all pins */ + writel(0x00000000, gpio->base + GPIO_INT_MASK); /* Unmask all */ + + /* Enable interrupt at PIC level */ + rt_pic_irq_enable(irq); + + /* Store device data */ + rt_platform_set_drvdata(pdev, gpio); + + rt_kprintf("GPIO interrupt controller probed successfully (IRQ %d)\n", irq); + return RT_EOK; + +err_iounmap: + rt_dm_dev_iounmap(dev, gpio->base); +err_free: + rt_free(gpio); + return ret; +} + +static rt_err_t gpio_int_remove(struct rt_platform_device *pdev) +{ + struct gpio_int_device *gpio = rt_platform_get_drvdata(pdev); + + /* Disable interrupt */ + rt_pic_irq_disable(gpio->irq); + + /* Detach handler */ + rt_pic_detach_irq(gpio->irq, gpio); + + /* Disable GPIO interrupts */ + writel(0x00000000, gpio->base + GPIO_INT_ENABLE); + + /* Cleanup */ + rt_dm_dev_iounmap(&pdev->parent, gpio->base); + rt_free(gpio); + + return RT_EOK; +} + +static const struct rt_ofw_node_id gpio_int_ofw_ids[] = +{ + { .compatible = "myvendor,gpio-interrupt-controller" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver gpio_int_driver = +{ + .name = "gpio-int", + .ids = gpio_int_ofw_ids, + .probe = gpio_int_probe, + .remove = gpio_int_remove, +}; + +static int gpio_int_drv_register(void) +{ + rt_platform_driver_register(&gpio_int_driver); + return 0; +} +INIT_DEVICE_EXPORT(gpio_int_drv_register); +``` + +## PIC Provider Implementation + +For implementing a custom interrupt controller driver: + +```c +#include +#include + +struct my_pic_data +{ + void *base; + struct rt_pic pic; +}; + +static void my_pic_irq_enable(struct rt_pic_irq *pirq) +{ + struct rt_pic *pic = pirq->pic; + struct my_pic_data *priv = pic->priv_data; + + /* Enable interrupt in hardware */ + writel(1 << pirq->hwirq, priv->base + MY_PIC_ENABLE_REG); +} + +static void my_pic_irq_disable(struct rt_pic_irq *pirq) +{ + struct rt_pic *pic = pirq->pic; + struct my_pic_data *priv = pic->priv_data; + + /* Disable interrupt in hardware */ + writel(1 << pirq->hwirq, priv->base + MY_PIC_DISABLE_REG); +} + +static void my_pic_irq_ack(struct rt_pic_irq *pirq) +{ + struct rt_pic *pic = pirq->pic; + struct my_pic_data *priv = pic->priv_data; + + /* Acknowledge interrupt */ + writel(1 << pirq->hwirq, priv->base + MY_PIC_ACK_REG); +} + +static void my_pic_irq_eoi(struct rt_pic_irq *pirq) +{ + struct rt_pic *pic = pirq->pic; + struct my_pic_data *priv = pic->priv_data; + + /* Send EOI signal */ + writel(1 << pirq->hwirq, priv->base + MY_PIC_EOI_REG); +} + +static rt_err_t my_pic_irq_set_priority(struct rt_pic_irq *pirq, rt_uint32_t priority) +{ + struct rt_pic *pic = pirq->pic; + struct my_pic_data *priv = pic->priv_data; + + /* Set priority in hardware register */ + writel(priority, priv->base + MY_PIC_PRIORITY_REG(pirq->hwirq)); + + return RT_EOK; +} + +static int my_pic_irq_map(struct rt_pic *pic, int hwirq, rt_uint32_t mode) +{ + int irq; + + /* Allocate software IRQ number */ + irq = pic->irq_start + hwirq; + + /* Configure trigger mode in hardware */ + /* ... */ + + return irq; +} + +static rt_err_t my_pic_irq_parse(struct rt_pic *pic, struct rt_ofw_cell_args *args, + struct rt_pic_irq *out_pirq) +{ + if (args->args_count != 2) + return -RT_EINVAL; + + /* Parse: */ + out_pirq->hwirq = args->args[0]; + out_pirq->mode = args->args[1]; + + return RT_EOK; +} + +static const struct rt_pic_ops my_pic_ops = +{ + .name = "my-pic", + .irq_enable = my_pic_irq_enable, + .irq_disable = my_pic_irq_disable, + .irq_ack = my_pic_irq_ack, + .irq_eoi = my_pic_irq_eoi, + .irq_set_priority = my_pic_irq_set_priority, + .irq_map = my_pic_irq_map, + .irq_parse = my_pic_irq_parse, +}; + +static rt_err_t my_pic_probe(struct rt_platform_device *pdev) +{ + struct my_pic_data *priv; + rt_err_t ret; + + priv = rt_calloc(1, sizeof(*priv)); + if (!priv) + return -RT_ENOMEM; + + /* Map registers */ + priv->base = rt_dm_dev_iomap(&pdev->parent, 0); + if (!priv->base) + { + ret = -RT_ERROR; + goto err_free; + } + + /* Initialize PIC structure */ + priv->pic.ops = &my_pic_ops; + priv->pic.priv_data = priv; + + /* Allocate IRQ range (32 interrupts) */ + ret = rt_pic_linear_irq(&priv->pic, 32); + if (ret != RT_EOK) + { + rt_kprintf("Failed to allocate IRQs\n"); + goto err_iounmap; + } + + /* Configure each IRQ */ + for (int i = 0; i < 32; i++) + { + rt_pic_config_irq(&priv->pic, i, i); /* Map 1:1 */ + } + + /* Call user extensions if needed */ + rt_pic_user_extends(&priv->pic); + + /* Initialize hardware */ + writel(0xFFFFFFFF, priv->base + MY_PIC_DISABLE_REG); /* Disable all */ + writel(0xFFFFFFFF, priv->base + MY_PIC_CLEAR_REG); /* Clear pending */ + + rt_platform_set_drvdata(pdev, priv); + rt_kprintf("My PIC initialized with %d IRQs\n", 32); + + return RT_EOK; + +err_iounmap: + rt_dm_dev_iounmap(&pdev->parent, priv->base); +err_free: + rt_free(priv); + return ret; +} + +static const struct rt_ofw_node_id my_pic_ofw_ids[] = +{ + { .compatible = "myvendor,my-pic" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver my_pic_driver = +{ + .name = "my-pic", + .ids = my_pic_ofw_ids, + .probe = my_pic_probe, +}; + +RT_PIC_OFW_DECLARE(my_pic, my_pic_ofw_ids, my_pic_probe); +``` + +## Best Practices + +### For Interrupt Consumers + +1. **Always disable interrupts during cleanup**: + ```c + rt_pic_irq_disable(irq); + rt_pic_detach_irq(irq, uid); + ``` + +2. **Handle spurious interrupts**: + ```c + static void my_isr(int irq, void *param) + { + if (!check_interrupt_source()) + return; /* Spurious interrupt */ + + /* Handle interrupt */ + } + ``` + +3. **Use EOI for level-triggered interrupts**: + ```c + static void my_isr(int irq, void *param) + { + handle_interrupt(); + clear_interrupt_source(); /* Clear in device first */ + rt_pic_irq_eoi(irq); /* Then send EOI to PIC */ + } + ``` + +4. **Set appropriate priorities**: + - Critical real-time interrupts: 0-31 (highest) + - Normal device interrupts: 32-127 + - Low-priority interrupts: 128-255 + +5. **Consider CPU affinity on multi-core systems**: + ```c + /* Pin network interrupt to CPU 0 for better cache locality */ + RT_IRQ_AFFINITY_DECLARE(mask); + rt_bitmap_clear(mask, RT_BITMAP_LEN(RT_CPUS_NR)); + RT_IRQ_AFFINITY_SET(mask, 0); + rt_pic_irq_set_affinity(net_irq, mask); + ``` + +### For PIC Providers + +1. **Implement minimum required operations**: + - `irq_enable`, `irq_disable` + - `irq_ack` or `irq_eoi` + - `irq_map`, `irq_parse` + +2. **Use `rt_pic_linear_irq` for simple IRQ allocation**: + ```c + ret = rt_pic_linear_irq(pic, num_irqs); + ``` + +3. **Support cascading for hierarchical interrupt controllers**: + ```c + struct rt_pic_irq *parent_pirq = rt_pic_find_pirq(parent_pic, parent_irq); + rt_pic_cascade(child_pirq, parent_irq); + ``` + +4. **Implement statistics support** (optional): + ```c + #ifdef RT_USING_PIC_STATISTICS + /* Framework tracks this automatically */ + #endif + ``` + +## Troubleshooting + +### Interrupt Not Firing + +1. **Check interrupt is enabled**: + ```c + rt_pic_irq_enable(irq); + ``` + +2. **Verify device interrupt is not masked**: + - Check device-specific interrupt enable registers + - Ensure interrupt source is configured correctly + +3. **Confirm device tree configuration**: + - Verify `interrupts` property + - Check `interrupt-parent` is correct + +4. **Check trigger mode**: + - Edge vs. level triggered + - Active high vs. active low + +### Interrupt Storms + +1. **Always acknowledge/clear interrupts**: + ```c + /* Clear source BEFORE EOI */ + clear_device_interrupt(); + rt_pic_irq_eoi(irq); + ``` + +2. **For level-triggered, clear condition**: + - Level-triggered interrupts re-assert if condition persists + - Must clear the underlying condition in the device + +### ISR Not Called + +1. **Verify IRQ number**: + ```c + rt_kprintf("Attached to IRQ %d\n", irq); + ``` + +2. **Check MAX_HANDLERS**: + - Ensure IRQ < MAX_HANDLERS + +3. **Confirm attachment succeeded**: + ```c + ret = rt_pic_attach_irq(irq, isr, param, "name", 0); + if (ret != RT_EOK) + rt_kprintf("Attach failed: %d\n", ret); + ``` + +### Priority/Affinity Not Working + +- Some PICs don't support priority or affinity +- Check return value for `-RT_ENOSYS` +- Verify hardware capabilities + +## Performance Considerations + +1. **Keep ISRs short**: Defer heavy processing to threads +2. **Minimize register access**: Cache PIC state when possible +3. **Use shared interrupts carefully**: Check device status first +4. **Consider IRQ coalescing**: For high-rate interrupt sources +5. **Profile with statistics**: + ```c + /* Enable RT_USING_PIC_STATISTICS */ + /* Check /proc or debug output for timing data */ + ``` + +## Related Modules + +- **OFW (Device Tree)**: IRQ parsing and `interrupt-parent` resolution +- **Platform Device**: `rt_platform_get_irq` for IRQ retrieval +- **PCI**: MSI/MSI-X interrupt support via PIC +- **Pinctrl**: GPIO interrupt configuration + +## References + +- ARM GIC Architecture Specification +- Device Tree Interrupt Mapping Specification +- RT-Thread Interrupt Management Documentation +- `components/drivers/pic/pic.c` - Core implementation +- `components/drivers/include/drivers/pic.h` - API header diff --git a/documentation/6.components/device-driver/device_driver_model/pinctrl/README.md b/documentation/6.components/device-driver/device_driver_model/pinctrl/README.md new file mode 100644 index 00000000000..22677199df4 --- /dev/null +++ b/documentation/6.components/device-driver/device_driver_model/pinctrl/README.md @@ -0,0 +1,560 @@ +# Pin Control Framework + +## Introduction + +The Pin Control (pinctrl) framework in RT-Thread provides a standardized way to configure pin multiplexing, electrical properties, and GPIO functionality. It allows device drivers to dynamically configure pins without hard-coding hardware-specific details. + +### General Overview + +Pin control is essential for: + +- **Pin Multiplexing**: Selecting between different functions for the same physical pin (UART, SPI, I2C, GPIO, etc.) +- **Electrical Configuration**: Setting pull-up/pull-down resistors, drive strength, and other electrical properties +- **Dynamic Reconfiguration**: Changing pin configurations at runtime based on system state +- **Power Management**: Configuring pins for low-power modes +- **GPIO Management**: Coordinating GPIO and peripheral pin usage + +### RT-Thread Implementation + +The RT-Thread pinctrl framework, located in `components/drivers/pinctrl/`, integrates with the pin device driver framework and provides: + +1. **Consumer API**: Functions for device drivers to apply pin configurations +2. **Provider API**: Framework for implementing pinctrl drivers +3. **Device Tree Integration**: Automatic pin configuration from DT +4. **Pin States**: Multiple named configurations per device (default, sleep, etc.) +5. **GPIO Request**: Coordination between GPIO and peripheral usage + +## Kconfig Configuration + +### Main Configuration + +```kconfig +menuconfig RT_USING_PINCTRL + bool "Using Pin controllers device drivers" + depends on RT_USING_DM + depends on RT_USING_PIN + default n +``` + +**Location in menuconfig**: +``` +RT-Thread Components → Device Drivers → Using Pin controllers device drivers +``` + +**Dependencies**: +- `RT_USING_DM`: Device driver model required +- `RT_USING_PIN`: Pin device driver framework required + +**Default**: Disabled (opt-in feature) + +### Pinctrl Driver Options + +#### SCMI Pinctrl Driver +```kconfig +config RT_PINCTRL_SCMI + bool "Pinctrl driver via ARM SCMI interface" + depends on RT_USING_PINCTRL + depends on RT_FIRMWARE_ARM_SCMI + default n +``` + +Supports pin control through ARM SCMI interface. + +#### Single-Register Pinctrl Driver +```kconfig +config RT_PINCTRL_SINGLE + bool "Single Pinctrl driver" + depends on RT_USING_PINCTRL + default n +``` + +Supports simple register-based pin controllers. + +## Device Tree Bindings + +### Pinctrl Provider Properties + +Pin controllers export their configuration capability using: + +```dts +#pinctrl-cells = ; /* Number of cells in pinctrl specifier */ +``` + +### Pinctrl Consumer Properties + +Devices reference pin configurations using: + +```dts +pinctrl-names = "default", "sleep"; /* State names */ +pinctrl-0 = <&state0_pins>; /* Pins for state 0 (default) */ +pinctrl-1 = <&state1_pins>; /* Pins for state 1 (sleep) */ +``` + +### Example: Pin Controller + +```dts +pio: pinctrl@1c20800 { + compatible = "vendor,pinctrl"; + reg = <0x1c20800 0x400>; + #pinctrl-cells = <1>; + + /* Pin group definitions */ + uart0_pins: uart0-pins { + pins = "PB8", "PB9"; + function = "uart0"; + drive-strength = <40>; + bias-pull-up; + }; + + uart0_sleep_pins: uart0-sleep-pins { + pins = "PB8", "PB9"; + function = "gpio"; + bias-disable; + }; + + spi0_pins: spi0-pins { + pins = "PC0", "PC1", "PC2", "PC3"; + function = "spi0"; + drive-strength = <40>; + }; + + i2c0_pins: i2c0-pins { + pins = "PA11", "PA12"; + function = "i2c0"; + bias-pull-up; + }; +}; +``` + +### Consumer Usage Examples + +```dts +/* UART with pin configuration */ +uart0: serial@1c28000 { + compatible = "vendor,uart"; + reg = <0x1c28000 0x400>; + + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&uart0_pins>; + pinctrl-1 = <&uart0_sleep_pins>; + + status = "okay"; +}; + +/* SPI with single pin state */ +spi0: spi@1c68000 { + compatible = "vendor,spi"; + reg = <0x1c68000 0x1000>; + + pinctrl-names = "default"; + pinctrl-0 = <&spi0_pins>; + + status = "okay"; +}; + +/* I2C with pin configuration */ +i2c0: i2c@1c2ac00 { + compatible = "vendor,i2c"; + reg = <0x1c2ac00 0x400>; + + pinctrl-names = "default"; + pinctrl-0 = <&i2c0_pins>; + + status = "okay"; +}; +``` + +## Application Layer API + +### Overview + +The pinctrl API allows device drivers to apply pin configurations automatically from device tree or manually by name/index. + +### Applying Pin Configurations + +#### rt_pin_ctrl_confs_apply + +```c +rt_err_t rt_pin_ctrl_confs_apply(struct rt_device *device, int index); +``` + +Apply pin configuration by index. + +**Parameters**: +- `device`: Device structure pointer +- `index`: Configuration index (0 for pinctrl-0, 1 for pinctrl-1, etc.) + +**Returns**: +- `RT_EOK` on success +- Error code on failure + +**Example**: +```c +struct rt_device *dev = &pdev->parent; + +/* Apply default pin configuration (pinctrl-0) */ +ret = rt_pin_ctrl_confs_apply(dev, 0); +if (ret != RT_EOK) { + LOG_E("Failed to apply pin configuration: %d", ret); + return ret; +} +``` + +#### rt_pin_ctrl_confs_apply_by_name + +```c +rt_err_t rt_pin_ctrl_confs_apply_by_name(struct rt_device *device, const char *name); +``` + +Apply pin configuration by state name. + +**Parameters**: +- `device`: Device structure pointer +- `name`: State name (matches entry in `pinctrl-names`) + +**Returns**: +- `RT_EOK` on success +- Error code on failure + +**Example**: +```c +/* Apply default pin configuration */ +rt_pin_ctrl_confs_apply_by_name(dev, "default"); + +/* Later, switch to sleep configuration */ +rt_pin_ctrl_confs_apply_by_name(dev, "sleep"); +``` + +#### rt_pin_ctrl_confs_lookup + +```c +rt_ssize_t rt_pin_ctrl_confs_lookup(struct rt_device *device, const char *name); +``` + +Look up the index of a named pin configuration. + +**Parameters**: +- `device`: Device structure pointer +- `name`: State name to look up + +**Returns**: +- Configuration index on success +- Negative error code on failure + +**Example**: +```c +/* Find index of "sleep" configuration */ +rt_ssize_t idx = rt_pin_ctrl_confs_lookup(dev, "sleep"); +if (idx >= 0) { + rt_pin_ctrl_confs_apply(dev, idx); +} +``` + +### GPIO-Related Functions + +#### rt_pin_get_named_pin + +```c +rt_ssize_t rt_pin_get_named_pin(struct rt_device *dev, const char *propname, + int index, rt_uint8_t *out_mode, rt_uint8_t *out_value); +``` + +Get a GPIO pin from device tree property. + +**Parameters**: +- `dev`: Device structure +- `propname`: Property name (e.g., "reset-gpios", "enable-gpios") +- `index`: Pin index in the property +- `out_mode`: Optional output for pin mode +- `out_value`: Optional output for pin value + +**Returns**: +- Pin number on success +- Negative error code on failure + +**Example**: +```c +rt_uint8_t mode, value; +rt_ssize_t reset_pin; + +/* Get reset GPIO from device tree */ +reset_pin = rt_pin_get_named_pin(dev, "reset-gpios", 0, &mode, &value); +if (reset_pin >= 0) { + rt_pin_mode(reset_pin, PIN_MODE_OUTPUT); + rt_pin_write(reset_pin, PIN_LOW); /* Assert reset */ +} +``` + +#### rt_pin_get_named_pin_count + +```c +rt_ssize_t rt_pin_get_named_pin_count(struct rt_device *dev, const char *propname); +``` + +Get the number of GPIOs in a device tree property. + +**Parameters**: +- `dev`: Device structure +- `propname`: Property name + +**Returns**: +- Number of pins +- Negative error code on failure + +## Complete Application Example + +### Example: SPI Driver with Pinctrl + +```c +#include +#include +#include +#include + +struct spi_device { + void *base; + int irq; + struct rt_clk *clk; + rt_ssize_t cs_pin; + struct rt_spi_bus spi_bus; +}; + +static rt_err_t spi_probe(struct rt_platform_device *pdev) +{ + rt_err_t ret; + struct rt_device *dev = &pdev->parent; + struct spi_device *spi; + + /* Allocate device structure */ + spi = rt_calloc(1, sizeof(*spi)); + if (!spi) + return -RT_ENOMEM; + + /* Map MMIO region */ + spi->base = rt_dm_dev_iomap(dev, 0); + if (!spi->base) { + ret = -RT_ERROR; + goto err_free; + } + + /* Apply pin configuration from device tree */ + ret = rt_pin_ctrl_confs_apply_by_name(dev, "default"); + if (ret != RT_EOK) { + LOG_E("Failed to apply pin configuration: %d", ret); + goto err_unmap; + } + + /* Get optional chip select GPIO */ + spi->cs_pin = rt_pin_get_named_pin(dev, "cs-gpios", 0, RT_NULL, RT_NULL); + if (spi->cs_pin >= 0) { + rt_pin_mode(spi->cs_pin, PIN_MODE_OUTPUT); + rt_pin_write(spi->cs_pin, PIN_HIGH); /* Deassert CS */ + LOG_I("Using GPIO %d for CS", spi->cs_pin); + } + + /* Get clock */ + spi->clk = rt_clk_get_by_name(dev, "spi"); + if (!spi->clk) { + LOG_E("Failed to get SPI clock"); + ret = -RT_ERROR; + goto err_unmap; + } + + ret = rt_clk_prepare_enable(spi->clk); + if (ret != RT_EOK) { + goto err_put_clk; + } + + /* Initialize SPI bus */ + spi->spi_bus.parent.user_data = spi; + ret = rt_spi_bus_register(&spi->spi_bus, rt_dm_dev_get_name(dev), &spi_ops); + if (ret != RT_EOK) { + goto err_disable_clk; + } + + pdev->priv = spi; + LOG_I("SPI device registered"); + + return RT_EOK; + +err_disable_clk: + rt_clk_disable_unprepare(spi->clk); +err_put_clk: + rt_clk_put(spi->clk); +err_unmap: + rt_iounmap(spi->base); +err_free: + rt_free(spi); + return ret; +} + +static rt_err_t spi_suspend(struct rt_device *dev) +{ + /* Switch to sleep pin configuration */ + return rt_pin_ctrl_confs_apply_by_name(dev, "sleep"); +} + +static rt_err_t spi_resume(struct rt_device *dev) +{ + /* Restore default pin configuration */ + return rt_pin_ctrl_confs_apply_by_name(dev, "default"); +} + +static const struct rt_ofw_node_id spi_ofw_ids[] = { + { .compatible = "vendor,spi" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver spi_driver = { + .name = "spi", + .ids = spi_ofw_ids, + .probe = spi_probe, +}; + +RT_PLATFORM_DRIVER_EXPORT(spi_driver); +``` + +## Driver Implementation Guide + +### Key Structures + +#### rt_pin_ops (with pinctrl) + +```c +struct rt_pin_ops { + /* Standard pin operations */ + void (*pin_mode)(struct rt_device *device, rt_base_t pin, rt_uint8_t mode); + void (*pin_write)(struct rt_device *device, rt_base_t pin, rt_uint8_t value); + rt_ssize_t (*pin_read)(struct rt_device *device, rt_base_t pin); + + /* Pinctrl-specific operations */ + rt_err_t (*pin_ctrl_confs_apply)(struct rt_device *device, void *fw_conf_np); + rt_err_t (*pin_ctrl_gpio_request)(struct rt_device *device, rt_base_t gpio, rt_uint32_t flags); +}; +``` + +### Pin Configuration Parameters + +Common pin configuration parameters (from `PIN_CONFIG_*` enum): + +- `PIN_CONFIG_BIAS_DISABLE`: Disable bias (no pull-up/down) +- `PIN_CONFIG_BIAS_PULL_UP`: Enable pull-up resistor +- `PIN_CONFIG_BIAS_PULL_DOWN`: Enable pull-down resistor +- `PIN_CONFIG_DRIVE_STRENGTH`: Set output drive strength +- `PIN_CONFIG_INPUT_ENABLE`: Enable input buffer +- `PIN_CONFIG_OUTPUT_ENABLE`: Enable output buffer +- `PIN_CONFIG_INPUT_DEBOUNCE`: Set input debounce time +- `PIN_CONFIG_SLEW_RATE`: Control signal slew rate + +## Best Practices + +### For Consumer Drivers + +1. **Apply pin configuration early**: During probe, before accessing hardware +2. **Use named states**: More maintainable than numeric indices +3. **Handle missing pinctrl gracefully**: Not all platforms require it +4. **Support power management**: Switch pin states for suspend/resume +5. **Check GPIO availability**: Verify GPIO pins exist before using them + +### Common Patterns + +#### Basic Pin Configuration + +```c +/* Apply default pin configuration */ +ret = rt_pin_ctrl_confs_apply_by_name(dev, "default"); +if (ret != RT_EOK && ret != -RT_ENOSYS) { + /* Real error, not just missing pinctrl */ + LOG_E("Pin configuration failed: %d", ret); + return ret; +} +``` + +#### Power Management with Pinctrl + +```c +static rt_err_t device_suspend(struct rt_device *dev) +{ + /* Save device state */ + + /* Apply sleep pin configuration */ + rt_pin_ctrl_confs_apply_by_name(dev, "sleep"); + + /* Disable clocks, etc. */ + return RT_EOK; +} + +static rt_err_t device_resume(struct rt_device *dev) +{ + /* Enable clocks, etc. */ + + /* Restore default pin configuration */ + rt_pin_ctrl_confs_apply_by_name(dev, "default"); + + /* Restore device state */ + return RT_EOK; +} +``` + +#### GPIO with Pinctrl + +```c +/* Get multiple GPIOs */ +rt_ssize_t num_gpios = rt_pin_get_named_pin_count(dev, "reset-gpios"); +for (int i = 0; i < num_gpios; i++) { + rt_ssize_t pin = rt_pin_get_named_pin(dev, "reset-gpios", i, + RT_NULL, RT_NULL); + if (pin >= 0) { + rt_pin_mode(pin, PIN_MODE_OUTPUT); + rt_pin_write(pin, PIN_LOW); + } +} +``` + +## Troubleshooting + +### Common Issues + +1. **Pin configuration not applied** + - Check device tree: Ensure `pinctrl-*` properties exist + - Check compatible: Verify pinctrl driver is loaded + - Check Kconfig: Enable RT_USING_PINCTRL + +2. **GPIO conflicts** + - Check pin multiplexing: Pin may be claimed by peripheral + - Check pinctrl configuration: May conflict with GPIO usage + - Use `pin_ctrl_gpio_request` for coordination + +3. **Pin not working after configuration** + - Check electrical properties: Drive strength, pull resistors + - Check pin function: Verify correct function is selected + - Check hardware: Verify pin is connected correctly + +## Performance Considerations + +### Memory Usage + +- Pin configuration cached in device tree nodes +- Minimal runtime overhead +- Pin states stored per device + +### Timing + +- Configuration application: Usually fast (register writes) +- No complex calculations required +- One-time setup during probe + +## Related Modules + +- **pin**: Core pin device driver framework +- **gpio**: GPIO functionality +- **clk**: May need clocks enabled for pin configuration +- **pmdomain**: Power domain coordination + +## References + +- RT-Thread Source: `components/drivers/pinctrl/` +- Header File: `components/drivers/include/drivers/dev_pin.h` +- Pin Device Driver: `components/drivers/pin/` +- Device Tree Bindings: [Linux Pinctrl Bindings](https://www.kernel.org/doc/Documentation/devicetree/bindings/pinctrl/) +- [RT-Thread DM Documentation](../README.md) diff --git a/documentation/6.components/device-driver/device_driver_model/pinctrl/README_zh.md b/documentation/6.components/device-driver/device_driver_model/pinctrl/README_zh.md new file mode 100644 index 00000000000..2857660bb18 --- /dev/null +++ b/documentation/6.components/device-driver/device_driver_model/pinctrl/README_zh.md @@ -0,0 +1,627 @@ +# Pin Control (Pinctrl) 框架 + +## 概述 + +Pin Control 框架提供了一个统一的接口来管理 SoC 的引脚功能复用和配置。现代 SoC 通常具有多功能引脚,可以配置为不同的功能(如 GPIO、UART、SPI 等)并具有各种电气属性(上拉、下拉、驱动强度等)。 + +### RT-Thread 实现 + +RT-Thread 的 Pinctrl 框架集成了设备驱动模型,允许: + +- 基于设备树的引脚配置 +- 运行时引脚功能切换 +- 引脚状态管理(用于电源管理) +- 与 GPIO 框架协同工作 +- 通过 SCMI 进行引脚控制 + +## Kconfig 配置 + +### 主要配置选项 + +#### RT_USING_PINCTRL + +启用 Pin Control 框架支持。 + +**路径**: `RT-Thread Components` → `Device Drivers` → `Using Pin Control (pinctrl)` + +**说明**: 启用后可以使用设备树配置引脚功能和参数。 + +#### RT_PINCTRL_SCMI + +通过 SCMI (System Control and Management Interface) 协议进行引脚控制。 + +**路径**: `RT-Thread Components` → `Device Drivers` → `Using Pin Control (pinctrl)` → `Enable SCMI pinctrl driver` + +**说明**: 用于带有专用系统控制处理器的系统。 + +#### RT_PINCTRL_SINGLE + +Simple pinctrl driver 支持单寄存器引脚配置。 + +**路径**: `RT-Thread Components` → `Device Drivers` → `Using Pin Control (pinctrl)` → `Enable simple pinctrl driver` + +**说明**: 适用于简单的 SoC,其中每个引脚用单个寄存器配置。 + +## 设备树绑定 + +### Pinctrl 控制器节点 + +```dts +pinctrl: pinctrl@11002000 { + compatible = "vendor,soc-pinctrl"; + reg = <0x11002000 0x1000>; + #pinctrl-cells = <2>; /* 功能, 配置 */ + + /* 引脚组定义 */ + uart0_pins: uart0 { + pins = <10 11>; /* TX, RX 引脚 */ + function = <1>; /* UART 功能 */ + drive-strength = <8>; /* mA */ + bias-pull-up; + }; + + spi0_default: spi0_default { + pins = <20 21 22 23>; /* CLK, MOSI, MISO, CS */ + function = <2>; /* SPI 功能 */ + drive-strength = <12>; + }; + + spi0_sleep: spi0_sleep { + pins = <20 21 22 23>; + function = <0>; /* GPIO 功能 */ + bias-pull-down; + }; +}; +``` + +### 消费者设备 + +```dts +uart0: serial@11003000 { + compatible = "vendor,uart"; + reg = <0x11003000 0x1000>; + + /* 引用引脚配置 */ + pinctrl-names = "default"; + pinctrl-0 = <&uart0_pins>; +}; + +spi0: spi@11004000 { + compatible = "vendor,spi"; + reg = <0x11004000 0x1000>; + + /* 多状态引脚配置 */ + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&spi0_default>; + pinctrl-1 = <&spi0_sleep>; +}; + +/* 具有 GPIO 引用的设备 */ +device@11005000 { + compatible = "vendor,device"; + reg = <0x11005000 0x1000>; + + reset-gpios = <&gpio 10 GPIO_ACTIVE_LOW>; + enable-gpios = <&gpio 11 GPIO_ACTIVE_HIGH>; +}; +``` + +### 引脚配置属性 + +常用的引脚配置属性: + +- `bias-disable`: 禁用上拉/下拉 +- `bias-pull-up`: 启用上拉 +- `bias-pull-down`: 启用下拉 +- `drive-strength`: 驱动强度(mA) +- `input-enable`: 启用输入缓冲器 +- `output-high`: 设置输出为高电平 +- `output-low`: 设置输出为低电平 + +## 应用层 API + +### 引脚配置 API + +#### rt_pin_ctrl_confs_apply + +按索引应用引脚配置。 + +```c +rt_err_t rt_pin_ctrl_confs_apply(struct rt_device *dev, int index); +``` + +**参数**: +- `dev`: 设备指针 +- `index`: 设备树中的配置索引(0 表示 pinctrl-0) + +**返回值**: +- `RT_EOK`: 成功 +- `-RT_EINVAL`: 无效参数 +- `-RT_EIO`: I/O 错误 + +**示例**: +```c +/* 应用默认配置(pinctrl-0) */ +rt_pin_ctrl_confs_apply(spi_dev, 0); + +/* 应用休眠配置(pinctrl-1) */ +rt_pin_ctrl_confs_apply(spi_dev, 1); +``` + +#### rt_pin_ctrl_confs_apply_by_name + +按名称应用引脚配置。 + +```c +rt_err_t rt_pin_ctrl_confs_apply_by_name(struct rt_device *dev, const char *name); +``` + +**参数**: +- `dev`: 设备指针 +- `name`: pinctrl-names 中的配置名称 + +**返回值**: +- `RT_EOK`: 成功 +- `-RT_EINVAL`: 无效参数或未找到名称 +- `-RT_EIO`: I/O 错误 + +**示例**: +```c +/* 在设备初始化时应用默认引脚 */ +rt_pin_ctrl_confs_apply_by_name(dev, "default"); + +/* 在挂起前切换到低功耗引脚 */ +rt_pin_ctrl_confs_apply_by_name(dev, "sleep"); +``` + +#### rt_pin_ctrl_confs_lookup + +在设备树中查找引脚配置索引。 + +```c +int rt_pin_ctrl_confs_lookup(struct rt_device *dev, const char *name); +``` + +**参数**: +- `dev`: 设备指针 +- `name`: pinctrl-names 中的配置名称 + +**返回值**: +- `>= 0`: 配置索引 +- `< 0`: 未找到错误 + +**示例**: +```c +int idx = rt_pin_ctrl_confs_lookup(dev, "idle"); +if (idx >= 0) { + rt_pin_ctrl_confs_apply(dev, idx); +} +``` + +### GPIO 辅助 API + +#### rt_pin_get_named_pin + +从设备树获取 GPIO 引脚编号。 + +```c +rt_ssize_t rt_pin_get_named_pin(struct rt_device *dev, + const char *propname, + int index, + rt_uint8_t *out_mode, + rt_uint8_t *out_value); +``` + +**参数**: +- `dev`: 设备指针 +- `propname`: GPIO 属性名称(如 "reset-gpios") +- `index`: GPIO 数组中的索引(对于单个 GPIO 为 0) +- `out_mode`: 输出 GPIO 模式(可以为 NULL) +- `out_value`: 输出 GPIO 值(可以为 NULL) + +**返回值**: +- `>= 0`: GPIO 引脚编号 +- `< 0`: 错误 + +**示例**: +```c +rt_uint8_t mode, value; +rt_ssize_t pin = rt_pin_get_named_pin(dev, "reset-gpios", 0, &mode, &value); +if (pin >= 0) { + /* 配置和使用 GPIO 引脚 */ + rt_pin_mode(pin, mode); + rt_pin_write(pin, value); +} +``` + +#### rt_pin_get_named_pin_count + +计算 GPIO 属性中的引脚数量。 + +```c +int rt_pin_get_named_pin_count(struct rt_device *dev, const char *propname); +``` + +**参数**: +- `dev`: 设备指针 +- `propname`: GPIO 属性名称 + +**返回值**: +- `>= 0`: GPIO 引脚数量 +- `< 0`: 错误 + +**示例**: +```c +int count = rt_pin_get_named_pin_count(dev, "gpios"); +for (int i = 0; i < count; i++) { + rt_ssize_t pin = rt_pin_get_named_pin(dev, "gpios", i, NULL, NULL); + /* 使用引脚 */ +} +``` + +## 使用示例 + +### SPI 驱动的引脚管理示例 + +```c +#include +#include +#include + +struct my_spi { + struct rt_spi_bus parent; + void *base; + struct rt_device *dev; + + /* GPIO 引脚 */ + rt_ssize_t cs_pin; + rt_ssize_t reset_pin; +}; + +static rt_err_t my_spi_probe(struct rt_platform_device *pdev) +{ + struct my_spi *spi; + struct rt_device *dev = &pdev->parent; + rt_err_t ret; + + spi = rt_calloc(1, sizeof(*spi)); + if (!spi) + return -RT_ENOMEM; + + spi->dev = dev; + + /* 应用默认引脚配置 */ + ret = rt_pin_ctrl_confs_apply_by_name(dev, "default"); + if (ret) { + rt_kprintf("Failed to apply default pinctrl: %d\n", ret); + goto err_free; + } + + /* 获取 GPIO 引脚 */ + spi->cs_pin = rt_pin_get_named_pin(dev, "cs-gpios", 0, NULL, NULL); + if (spi->cs_pin < 0) { + rt_kprintf("Failed to get CS GPIO\n"); + ret = spi->cs_pin; + goto err_free; + } + + spi->reset_pin = rt_pin_get_named_pin(dev, "reset-gpios", 0, NULL, NULL); + if (spi->reset_pin >= 0) { + /* 配置复位引脚 */ + rt_pin_mode(spi->reset_pin, PIN_MODE_OUTPUT); + rt_pin_write(spi->reset_pin, PIN_HIGH); + } + + /* 配置 CS 引脚 */ + rt_pin_mode(spi->cs_pin, PIN_MODE_OUTPUT); + rt_pin_write(spi->cs_pin, PIN_HIGH); + + /* 注册 SPI 总线 */ + ret = rt_spi_bus_register(&spi->parent, "spi0", &spi_ops); + if (ret) + goto err_free; + + rt_platform_set_drvdata(pdev, spi); + + rt_kprintf("SPI driver probed successfully\n"); + return RT_EOK; + +err_free: + rt_free(spi); + return ret; +} + +static rt_err_t my_spi_remove(struct rt_platform_device *pdev) +{ + struct my_spi *spi = rt_platform_get_drvdata(pdev); + + /* 释放 GPIO 引脚 */ + if (spi->reset_pin >= 0) { + rt_pin_write(spi->reset_pin, PIN_LOW); + } + + rt_spi_bus_unregister(&spi->parent); + rt_free(spi); + + return RT_EOK; +} + +/* 电源管理钩子 */ +static int my_spi_suspend(const struct rt_device *dev, rt_uint8_t mode) +{ + /* 切换到休眠引脚配置 */ + return rt_pin_ctrl_confs_apply_by_name((struct rt_device *)dev, "sleep"); +} + +static void my_spi_resume(const struct rt_device *dev, rt_uint8_t mode) +{ + /* 恢复默认引脚配置 */ + rt_pin_ctrl_confs_apply_by_name((struct rt_device *)dev, "default"); +} + +static const struct rt_device_ops spi_ops = { + .suspend = my_spi_suspend, + .resume = my_spi_resume, +}; + +static const struct rt_ofw_node_id my_spi_ids[] = { + { .compatible = "vendor,my-spi" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver my_spi_driver = { + .name = "my-spi", + .ids = my_spi_ids, + .probe = my_spi_probe, + .remove = my_spi_remove, +}; + +static int my_spi_drv_register(void) +{ + rt_platform_driver_register(&my_spi_driver); + return 0; +} +INIT_SUBSYS_EXPORT(my_spi_drv_register); +``` + +## 驱动实现指南 + +### Simple Pinctrl 驱动示例 + +```c +#include +#include + +struct simple_pinctrl { + struct rt_device parent; + void *base; + rt_size_t ngroups; +}; + +static rt_err_t simple_pinctrl_set_mux(struct rt_pinctrl *ctrl, + rt_uint32_t group, + rt_uint32_t function) +{ + struct simple_pinctrl *pinctrl = rt_container_of(ctrl, struct simple_pinctrl, parent); + void *reg = pinctrl->base + (group * 4); + + /* 设置功能复用 */ + rt_uint32_t val = readl(reg); + val &= ~0xFF; + val |= function & 0xFF; + writel(val, reg); + + return RT_EOK; +} + +static rt_err_t simple_pinctrl_set_config(struct rt_pinctrl *ctrl, + rt_uint32_t group, + rt_ubase_t config) +{ + struct simple_pinctrl *pinctrl = rt_container_of(ctrl, struct simple_pinctrl, parent); + void *reg = pinctrl->base + (group * 4); + rt_uint32_t param = RT_PINCTRL_CONFIG_PARAM(config); + rt_uint32_t arg = RT_PINCTRL_CONFIG_ARG(config); + rt_uint32_t val; + + val = readl(reg); + + switch (param) { + case PIN_CONFIG_BIAS_DISABLE: + val &= ~(BIT(8) | BIT(9)); /* 清除上拉/下拉 */ + break; + + case PIN_CONFIG_BIAS_PULL_UP: + val |= BIT(8); + val &= ~BIT(9); + break; + + case PIN_CONFIG_BIAS_PULL_DOWN: + val |= BIT(9); + val &= ~BIT(8); + break; + + case PIN_CONFIG_DRIVE_STRENGTH: + val &= ~(0xF << 16); + val |= (arg & 0xF) << 16; + break; + + default: + return -RT_EINVAL; + } + + writel(val, reg); + return RT_EOK; +} + +static const struct rt_pinctrl_ops simple_pinctrl_ops = { + .set_mux = simple_pinctrl_set_mux, + .set_config = simple_pinctrl_set_config, +}; + +static rt_err_t simple_pinctrl_probe(struct rt_platform_device *pdev) +{ + struct simple_pinctrl *pinctrl; + rt_err_t ret; + + pinctrl = rt_calloc(1, sizeof(*pinctrl)); + if (!pinctrl) + return -RT_ENOMEM; + + /* 映射寄存器 */ + pinctrl->base = rt_dm_dev_iomap(&pdev->parent, 0); + if (!pinctrl->base) { + ret = -RT_EIO; + goto err_free; + } + + /* 获取引脚组数量 */ + rt_dm_dev_prop_read_u32(&pdev->parent, "pinctrl-groups", &pinctrl->ngroups); + + /* 注册 pinctrl 设备 */ + pinctrl->parent.ops = &simple_pinctrl_ops; + ret = rt_pinctrl_register(&pinctrl->parent, &pdev->parent); + if (ret) + goto err_unmap; + + rt_platform_set_drvdata(pdev, pinctrl); + + return RT_EOK; + +err_unmap: + rt_dm_dev_iounmap(&pdev->parent, pinctrl->base); +err_free: + rt_free(pinctrl); + return ret; +} + +static const struct rt_ofw_node_id simple_pinctrl_ids[] = { + { .compatible = "simple-pinctrl" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver simple_pinctrl_driver = { + .name = "simple-pinctrl", + .ids = simple_pinctrl_ids, + .probe = simple_pinctrl_probe, +}; +``` + +## 最佳实践 + +### 消费者最佳实践 + +1. **始终应用默认配置** + ```c + rt_pin_ctrl_confs_apply_by_name(dev, "default"); + ``` + +2. **使用命名状态进行电源管理** + ```c + /* 挂起 */ + rt_pin_ctrl_confs_apply_by_name(dev, "sleep"); + + /* 恢复 */ + rt_pin_ctrl_confs_apply_by_name(dev, "default"); + ``` + +3. **检查 GPIO 可用性** + ```c + rt_ssize_t pin = rt_pin_get_named_pin(dev, "reset-gpios", 0, NULL, NULL); + if (pin >= 0) { + /* GPIO 可用 */ + } + ``` + +4. **使用 pinctrl 进行专用功能,使用 GPIO 进行 GPIO** + - Pinctrl:功能复用、电气配置 + - GPIO:运行时 GPIO 操作 + +### 提供者最佳实践 + +1. **支持所有相关的配置参数** + - 上拉/下拉 + - 驱动强度 + - 输入使能 + - 其他特定于硬件的参数 + +2. **验证引脚组索引** + ```c + if (group >= pinctrl->ngroups) + return -RT_EINVAL; + ``` + +3. **实现原子配置更新** + - 读取-修改-写入序列 + - 考虑寄存器锁定 + +## 故障排除 + +### 引脚配置失败 + +**症状**: rt_pin_ctrl_confs_apply 返回错误 + +**可能原因**: +1. 设备树中的 pinctrl 引用不正确 +2. Pinctrl 驱动未加载 +3. 引脚组索引超出范围 + +**解决方案**: +```c +/* 检查 pinctrl 引用 */ +int idx = rt_pin_ctrl_confs_lookup(dev, "default"); +if (idx < 0) { + rt_kprintf("Pinctrl 'default' not found in DT\n"); +} + +/* 验证 pinctrl 驱动 */ +if (!dev->pinctrl) { + rt_kprintf("No pinctrl controller available\n"); +} +``` + +### GPIO 与 Pinctrl 冲突 + +**症状**: GPIO 操作不起作用或引脚不响应 + +**可能原因**: +- 引脚复用为外设功能而不是 GPIO +- 冲突的引脚配置 + +**解决方案**: +```c +/* 对于 GPIO 使用,确保引脚复用为 GPIO 功能 */ +/* 在设备树中或通过 pinctrl 驱动程序 */ +``` + +### 电源管理状态切换 + +**症状**: 从休眠恢复后外设不工作 + +**可能原因**: +- 忘记恢复默认引脚状态 +- 引脚状态切换顺序不正确 + +**解决方案**: +```c +/* 在恢复时始终恢复默认状态 */ +static void my_device_resume(const struct rt_device *dev, rt_uint8_t mode) +{ + rt_pin_ctrl_confs_apply_by_name((struct rt_device *)dev, "default"); + + /* 然后重新初始化硬件 */ +} +``` + +## 相关模块 + +- **GPIO 框架**: 运行时 GPIO 操作 +- **OFW (设备树)**: 设备树解析 +- **电源管理**: 引脚状态用于低功耗模式 +- **SCMI**: 固件引脚控制 + +## 参考 + +- `components/drivers/include/drivers/pin.h` +- `components/drivers/core/pinctrl.c` +- Linux Pinctrl 子系统文档 diff --git a/documentation/6.components/device-driver/device_driver_model/regulator/README.md b/documentation/6.components/device-driver/device_driver_model/regulator/README.md new file mode 100644 index 00000000000..a6453a362d2 --- /dev/null +++ b/documentation/6.components/device-driver/device_driver_model/regulator/README.md @@ -0,0 +1,1253 @@ +# Regulator Framework + +## Introduction + +The Regulator framework in RT-Thread provides a standardized interface for managing voltage and current regulators in embedded systems. Regulators are essential components that provide stable power supplies to various hardware peripherals, processors, and other system components. + +### General Overview + +Voltage regulators are electronic circuits that maintain a constant voltage level regardless of changes in load current or input voltage. They are crucial for: + +- **Power Management**: Efficient power distribution and management +- **Voltage Scaling**: Dynamic voltage and frequency scaling (DVFS) for power optimization +- **Device Protection**: Protecting devices from overvoltage and overcurrent +- **System Stability**: Ensuring stable operation of hardware components + +Common regulator types include: +- **Linear Regulators**: Simple, low-noise but less efficient +- **Switching Regulators (SMPS)**: More efficient but may generate noise +- **LDO (Low Dropout Regulators)**: Linear regulators with low voltage drop +- **Buck/Boost Converters**: Step-down/step-up switching regulators + +### RT-Thread Implementation + +The RT-Thread regulator framework, located in `components/drivers/regulator/`, provides: + +1. **Consumer API**: Simple interface for device drivers to manage power supplies +2. **Provider API**: Framework for implementing regulator drivers +3. **Regulator Tree**: Hierarchical management of regulator dependencies +4. **Device Tree Integration**: Automatic configuration from device tree +5. **Reference Counting**: Safe enable/disable with multiple consumers +6. **Notifier Chains**: Event notification for voltage changes +7. **Multiple Driver Support**: Fixed, GPIO-controlled, SCMI-based regulators + +**Architecture**: +``` +┌─────────────────────────────────────────────────────────┐ +│ Consumer Drivers │ +│ (UART, SPI, I2C, MMC, CPU, etc.) │ +└────────────────────┬────────────────────────────────────┘ + │ Consumer API + │ (get, enable, set_voltage, etc.) +┌────────────────────┴────────────────────────────────────┐ +│ Regulator Framework Core │ +│ - Reference Counting │ +│ - Regulator Tree Management │ +│ - Notifier Chains │ +│ - Voltage/Current Validation │ +└────────────────────┬────────────────────────────────────┘ + │ Provider API + │ (ops callbacks) +┌────────────────────┴────────────────────────────────────┐ +│ Regulator Drivers │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Fixed │ │ GPIO │ │ SCMI │ │ +│ │ Regulator │ │ Regulator │ │ Regulator │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +└────────────────────┬────────────────────────────────────┘ + │ +┌────────────────────┴────────────────────────────────────┐ +│ Hardware Regulators │ +│ (PMIC, discrete regulators, power supplies) │ +└──────────────────────────────────────────────────────────┘ +``` + +## Kconfig Configuration + +### Main Configuration + +The regulator framework depends on the Device Driver Model (DM): + +```kconfig +menuconfig RT_USING_REGULATOR + bool "Using Voltage and Current Regulator" + select RT_USING_ADT + select RT_USING_ADT_REF + depends on RT_USING_DM + default n +``` + +**Location in menuconfig**: +``` +RT-Thread Components → Device Drivers → Using Voltage and Current Regulator +``` + +**Dependencies**: +- `RT_USING_DM`: Must be enabled first +- `RT_USING_ADT`: Abstract Data Types (automatic) +- `RT_USING_ADT_REF`: Reference counting support (automatic) + +### Regulator Driver Options + +#### Fixed Regulator +```kconfig +config RT_REGULATOR_FIXED + bool "Fixed regulator support" + depends on RT_USING_REGULATOR + depends on RT_USING_PIN + depends on RT_USING_PINCTRL + default y +``` + +Supports regulators with fixed output voltage, optionally controlled by a GPIO enable pin. + +**Dependencies**: +- `RT_USING_PIN`: GPIO support +- `RT_USING_PINCTRL`: Pin control support + +#### GPIO Regulator +```kconfig +config RT_REGULATOR_GPIO + bool "GPIO regulator support" + depends on RT_USING_REGULATOR + depends on RT_USING_PIN + default y +``` + +Supports regulators with multiple voltage levels selected by GPIO pins. + +**Dependencies**: +- `RT_USING_PIN`: GPIO support + +#### SCMI Regulator +```kconfig +config RT_REGULATOR_SCMI + bool "SCMI regulator support" + depends on RT_USING_REGULATOR + depends on RT_USING_OFW + depends on RT_FIRMWARE_ARM_SCMI + default n +``` + +Supports regulators controlled through ARM System Control and Management Interface (SCMI). + +**Dependencies**: +- `RT_USING_OFW`: Device tree support +- `RT_FIRMWARE_ARM_SCMI`: ARM SCMI firmware interface + +### Vendor-Specific Options + +```kconfig +if RT_USING_REGULATOR + osource "$(SOC_DM_REGULATOR_DIR)/Kconfig" +endif +``` + +This allows SoC-specific regulator drivers to add their own Kconfig options via the `SOC_DM_REGULATOR_DIR` variable. + +## Device Tree Bindings + +### Common Properties + +All regulator nodes support these standard properties: + +```dts +regulator-name = "supply_name"; /* Human-readable name */ +regulator-min-microvolt = ; /* Minimum voltage in µV */ +regulator-max-microvolt = ; /* Maximum voltage in µV */ +regulator-min-microamp = ; /* Minimum current in µA */ +regulator-max-microamp = ; /* Maximum current in µA */ +regulator-ramp-delay = ; /* Voltage change rate in µV/µs */ +regulator-enable-ramp-delay = ; /* Enable delay in µs */ +regulator-settling-time-us = ; /* Settling time in µs */ +regulator-settling-time-up-us = ; /* Settling time for voltage increase */ +regulator-settling-time-down-us = ; /* Settling time for voltage decrease */ +enable-active-high; /* Enable pin active high (default) */ +regulator-boot-on; /* Enable at boot */ +regulator-always-on; /* Never disable */ +regulator-soft-start; /* Enable soft-start */ +regulator-pull-down; /* Enable pull-down when off */ +regulator-over-current-protection; /* Enable OCP */ +``` + +### Fixed Regulator Example + +Fixed regulators have a constant output voltage: + +```dts +regulators { + /* Simple fixed 3.3V regulator */ + vcc_3v3: regulator-vcc-3v3 { + compatible = "regulator-fixed"; + regulator-name = "vcc-3v3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + regulator-boot-on; + }; + + /* Fixed regulator with GPIO enable control */ + vcc_sd: regulator-vcc-sd { + compatible = "regulator-fixed"; + regulator-name = "vcc-sd"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + gpio = <&gpio0 10 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&sd_power_pins>; + enable-active-high; + startup-delay-us = <100000>; /* 100ms */ + off-on-delay-us = <10000>; /* 10ms */ + }; + + /* Regulator supplied by another regulator */ + vcc_1v8: regulator-vcc-1v8 { + compatible = "regulator-fixed"; + regulator-name = "vcc-1v8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + vin-supply = <&vcc_3v3>; /* Parent supply */ + regulator-always-on; + }; +}; +``` + +### GPIO-Controlled Regulator Example + +GPIO regulators support multiple voltage levels: + +```dts +vcc_ddr: regulator-vcc-ddr { + compatible = "regulator-gpio"; + regulator-name = "vcc-ddr"; + regulator-min-microvolt = <1200000>; + regulator-max-microvolt = <1350000>; + + /* GPIO pins to select voltage */ + gpios = <&gpio1 5 GPIO_ACTIVE_HIGH>, + <&gpio1 6 GPIO_ACTIVE_HIGH>; + + /* Voltage selection table (based on GPIO states) */ + states = <1200000 0x0 /* 1.2V: both low */ + 1250000 0x1 /* 1.25V: pin6=high, pin5=low */ + 1300000 0x2 /* 1.3V: pin6=low, pin5=high */ + 1350000 0x3>; /* 1.35V: both high */ + + enable-gpio = <&gpio1 4 GPIO_ACTIVE_HIGH>; + startup-delay-us = <50000>; +}; +``` + +### Consumer Usage in Device Tree + +Devices reference regulators using supply properties: + +```dts +/* UART with multiple power supplies */ +uart0: serial@10000000 { + compatible = "vendor,uart"; + reg = <0x10000000 0x1000>; + interrupts = <32>; + clocks = <&clk_uart0>; + + /* Power supplies */ + vdd-supply = <&vcc_3v3>; /* Core supply */ + vddio-supply = <&vcc_1v8>; /* I/O supply */ + + status = "okay"; +}; + +/* MMC/SD controller with regulator control */ +mmc0: mmc@20000000 { + compatible = "vendor,mmc"; + reg = <0x20000000 0x1000>; + + vmmc-supply = <&vcc_sd>; /* Card power supply */ + vqmmc-supply = <&vcc_1v8>; /* I/O level shifter supply */ + + status = "okay"; +}; +``` + +## Application Layer API + +### Overview + +The consumer API provides functions for device drivers to manage their power supplies. All operations use opaque `struct rt_regulator` pointers obtained through the get API. + +### Getting and Releasing Regulators + +#### rt_regulator_get + +```c +struct rt_regulator *rt_regulator_get(struct rt_device *dev, const char *id); +``` + +Get a regulator for a device. + +**Parameters**: +- `dev`: Pointer to the device structure +- `id`: Supply name (matches `-supply` in device tree, e.g., "vdd", "vmmc") + +**Returns**: +- Pointer to regulator on success +- NULL on failure + +**Example**: +```c +struct rt_device *dev = &pdev->parent; +struct rt_regulator *vdd_reg; + +/* Get the "vdd" supply */ +vdd_reg = rt_regulator_get(dev, "vdd"); +if (!vdd_reg) { + LOG_E("Failed to get vdd regulator"); + return -RT_ERROR; +} +``` + +#### rt_regulator_put + +```c +void rt_regulator_put(struct rt_regulator *reg); +``` + +Release a regulator reference. + +**Parameters**: +- `reg`: Regulator pointer obtained from `rt_regulator_get()` + +**Example**: +```c +rt_regulator_put(vdd_reg); +``` + +### Enable and Disable + +#### rt_regulator_enable + +```c +rt_err_t rt_regulator_enable(struct rt_regulator *reg); +``` + +Enable a regulator. Uses reference counting, so multiple enables require matching disables. + +**Parameters**: +- `reg`: Regulator pointer + +**Returns**: +- `RT_EOK` on success +- Error code on failure + +**Notes**: +- Automatically enables parent regulators +- Waits for enable delay if configured +- Calls notifier chains + +**Example**: +```c +rt_err_t ret; + +ret = rt_regulator_enable(vdd_reg); +if (ret != RT_EOK) { + LOG_E("Failed to enable regulator: %d", ret); + return ret; +} +``` + +#### rt_regulator_disable + +```c +rt_err_t rt_regulator_disable(struct rt_regulator *reg); +``` + +Disable a regulator. Only actually disables when reference count reaches zero. + +**Parameters**: +- `reg`: Regulator pointer + +**Returns**: +- `RT_EOK` on success +- Error code on failure + +**Notes**: +- Respects `regulator-always-on` property +- Waits for off-on delay if re-enabling soon +- Calls notifier chains + +**Example**: +```c +rt_regulator_disable(vdd_reg); +``` + +#### rt_regulator_is_enabled + +```c +rt_bool_t rt_regulator_is_enabled(struct rt_regulator *reg); +``` + +Check if a regulator is currently enabled. + +**Parameters**: +- `reg`: Regulator pointer + +**Returns**: +- `RT_TRUE` if enabled +- `RT_FALSE` if disabled + +**Example**: +```c +if (rt_regulator_is_enabled(vdd_reg)) { + LOG_I("Regulator is enabled"); +} +``` + +### Voltage Control + +#### rt_regulator_set_voltage + +```c +rt_err_t rt_regulator_set_voltage(struct rt_regulator *reg, int min_uvolt, int max_uvolt); +``` + +Set regulator output voltage within a range. + +**Parameters**: +- `reg`: Regulator pointer +- `min_uvolt`: Minimum acceptable voltage in microvolts (µV) +- `max_uvolt`: Maximum acceptable voltage in microvolts (µV) + +**Returns**: +- `RT_EOK` on success +- `-RT_ENOSYS` if voltage control not supported +- Error code on failure + +**Notes**: +- Voltage must be within regulator's configured range +- Framework selects optimal voltage within specified range +- Waits for settling time after voltage change +- Calls notifier chains with voltage change events + +**Example**: +```c +/* Set voltage to 1.8V ±5% */ +ret = rt_regulator_set_voltage(vdd_reg, 1710000, 1890000); +if (ret != RT_EOK) { + LOG_E("Failed to set voltage: %d", ret); +} + +/* Set precise voltage */ +ret = rt_regulator_set_voltage(vdd_reg, 3300000, 3300000); +``` + +#### rt_regulator_set_voltage_triplet + +```c +rt_inline rt_err_t rt_regulator_set_voltage_triplet(struct rt_regulator *reg, + int min_uvolt, int target_uvolt, int max_uvolt); +``` + +Set voltage with a preferred target value. + +**Parameters**: +- `reg`: Regulator pointer +- `min_uvolt`: Minimum voltage in µV +- `target_uvolt`: Preferred voltage in µV +- `max_uvolt`: Maximum voltage in µV + +**Returns**: +- `RT_EOK` on success +- Error code on failure + +**Notes**: +- First attempts to set target voltage +- Falls back to min-max range if target fails + +**Example**: +```c +/* Prefer 1.8V, but accept 1.71V-1.89V */ +ret = rt_regulator_set_voltage_triplet(vdd_reg, 1710000, 1800000, 1890000); +``` + +#### rt_regulator_get_voltage + +```c +int rt_regulator_get_voltage(struct rt_regulator *reg); +``` + +Get current regulator output voltage. + +**Parameters**: +- `reg`: Regulator pointer + +**Returns**: +- Current voltage in microvolts (µV) +- Negative error code on failure + +**Example**: +```c +int voltage = rt_regulator_get_voltage(vdd_reg); +if (voltage > 0) { + LOG_I("Current voltage: %d.%03dV", voltage / 1000000, (voltage / 1000) % 1000); +} +``` + +#### rt_regulator_is_supported_voltage + +```c +rt_bool_t rt_regulator_is_supported_voltage(struct rt_regulator *reg, + int min_uvolt, int max_uvolt); +``` + +Check if a voltage range is supported. + +**Parameters**: +- `reg`: Regulator pointer +- `min_uvolt`: Minimum voltage in µV +- `max_uvolt`: Maximum voltage in µV + +**Returns**: +- `RT_TRUE` if supported +- `RT_FALSE` if not supported + +**Example**: +```c +if (rt_regulator_is_supported_voltage(vdd_reg, 1800000, 1800000)) { + rt_regulator_set_voltage(vdd_reg, 1800000, 1800000); +} +``` + +### Operating Modes + +#### rt_regulator_set_mode + +```c +rt_err_t rt_regulator_set_mode(struct rt_regulator *reg, rt_uint32_t mode); +``` + +Set regulator operating mode. + +**Parameters**: +- `reg`: Regulator pointer +- `mode`: Operating mode flags: + - `RT_REGULATOR_MODE_FAST`: High-speed mode (higher power consumption) + - `RT_REGULATOR_MODE_NORMAL`: Normal operation mode + - `RT_REGULATOR_MODE_IDLE`: Idle mode (reduced performance) + - `RT_REGULATOR_MODE_STANDBY`: Standby mode (minimal power) + +**Returns**: +- `RT_EOK` on success +- `-RT_ENOSYS` if mode control not supported +- Error code on failure + +**Example**: +```c +/* Set to low-power mode during idle */ +rt_regulator_set_mode(vdd_reg, RT_REGULATOR_MODE_IDLE); + +/* Restore normal mode */ +rt_regulator_set_mode(vdd_reg, RT_REGULATOR_MODE_NORMAL); +``` + +#### rt_regulator_get_mode + +```c +rt_int32_t rt_regulator_get_mode(struct rt_regulator *reg); +``` + +Get current regulator operating mode. + +**Parameters**: +- `reg`: Regulator pointer + +**Returns**: +- Current mode flags +- `RT_REGULATOR_MODE_INVALID` on error + +### Notification API + +#### rt_regulator_notifier_register + +```c +rt_err_t rt_regulator_notifier_register(struct rt_regulator *reg, + struct rt_regulator_notifier *notifier); +``` + +Register a notifier for regulator events. + +**Parameters**: +- `reg`: Regulator pointer +- `notifier`: Notifier structure with callback + +**Returns**: +- `RT_EOK` on success +- Error code on failure + +**Notifier Structure**: +```c +struct rt_regulator_notifier { + rt_list_t list; + struct rt_regulator *regulator; + rt_regulator_notifier_callback callback; + void *priv; +}; + +typedef rt_err_t (*rt_regulator_notifier_callback)( + struct rt_regulator_notifier *notifier, + rt_ubase_t msg, + void *data); +``` + +**Event Messages**: +- `RT_REGULATOR_MSG_ENABLE`: Regulator enabled +- `RT_REGULATOR_MSG_DISABLE`: Regulator disabled +- `RT_REGULATOR_MSG_VOLTAGE_CHANGE`: Voltage changed successfully +- `RT_REGULATOR_MSG_VOLTAGE_CHANGE_ERR`: Voltage change failed + +**Example**: +```c +static rt_err_t voltage_change_callback(struct rt_regulator_notifier *notifier, + rt_ubase_t msg, void *data) +{ + if (msg == RT_REGULATOR_MSG_VOLTAGE_CHANGE) { + union rt_regulator_notifier_args *args = data; + LOG_I("Voltage changed: %d -> %d µV", + args->old_uvolt, args->min_uvolt); + } + return RT_EOK; +} + +struct rt_regulator_notifier my_notifier = { + .callback = voltage_change_callback, + .priv = NULL, +}; + +rt_regulator_notifier_register(vdd_reg, &my_notifier); +``` + +#### rt_regulator_notifier_unregister + +```c +rt_err_t rt_regulator_notifier_unregister(struct rt_regulator *reg, + struct rt_regulator_notifier *notifier); +``` + +Unregister a regulator notifier. + +**Parameters**: +- `reg`: Regulator pointer +- `notifier`: Notifier to unregister + +**Returns**: +- `RT_EOK` on success +- Error code on failure + +## Complete Application Example + +### Example: MMC/SD Driver with Regulator Control + +```c +#include +#include +#include +#include + +struct mmc_host { + void *base; + int irq; + struct rt_clk *clk; + struct rt_regulator *vmmc; /* Card power supply */ + struct rt_regulator *vqmmc; /* I/O voltage supply */ +}; + +static rt_err_t mmc_set_ios_voltage(struct mmc_host *host, int voltage) +{ + rt_err_t ret; + + /* Set I/O voltage level */ + if (voltage == 1800000) { + ret = rt_regulator_set_voltage(host->vqmmc, 1800000, 1800000); + } else { + ret = rt_regulator_set_voltage(host->vqmmc, 3300000, 3300000); + } + + if (ret != RT_EOK) { + LOG_E("Failed to set I/O voltage: %d", ret); + return ret; + } + + /* Wait for voltage to stabilize */ + rt_thread_mdelay(10); + + return RT_EOK; +} + +static rt_err_t mmc_power_on(struct mmc_host *host) +{ + rt_err_t ret; + + /* Enable card power supply */ + ret = rt_regulator_enable(host->vmmc); + if (ret != RT_EOK) { + LOG_E("Failed to enable vmmc: %d", ret); + return ret; + } + + /* Set I/O voltage to 3.3V initially */ + ret = rt_regulator_enable(host->vqmmc); + if (ret != RT_EOK) { + LOG_E("Failed to enable vqmmc: %d", ret); + goto err_disable_vmmc; + } + + ret = mmc_set_ios_voltage(host, 3300000); + if (ret != RT_EOK) { + goto err_disable_vqmmc; + } + + /* Enable clock */ + ret = rt_clk_prepare_enable(host->clk); + if (ret != RT_EOK) { + goto err_disable_vqmmc; + } + + return RT_EOK; + +err_disable_vqmmc: + rt_regulator_disable(host->vqmmc); +err_disable_vmmc: + rt_regulator_disable(host->vmmc); + return ret; +} + +static void mmc_power_off(struct mmc_host *host) +{ + /* Disable clock */ + rt_clk_disable_unprepare(host->clk); + + /* Disable regulators in reverse order */ + rt_regulator_disable(host->vqmmc); + rt_regulator_disable(host->vmmc); +} + +static rt_err_t mmc_probe(struct rt_platform_device *pdev) +{ + rt_err_t ret; + struct rt_device *dev = &pdev->parent; + struct mmc_host *host; + + /* Allocate host structure */ + host = rt_calloc(1, sizeof(*host)); + if (!host) + return -RT_ENOMEM; + + /* Map MMIO region */ + host->base = rt_dm_dev_iomap(dev, 0); + if (!host->base) { + ret = -RT_ERROR; + goto err_free_host; + } + + /* Get IRQ */ + host->irq = rt_dm_dev_get_irq(dev, 0); + if (host->irq < 0) { + ret = host->irq; + goto err_iounmap; + } + + /* Get clock */ + host->clk = rt_clk_get_by_name(dev, "mmc"); + if (!host->clk) { + ret = -RT_ERROR; + goto err_iounmap; + } + + /* Get regulators */ + host->vmmc = rt_regulator_get(dev, "vmmc"); + if (!host->vmmc) { + LOG_W("No vmmc regulator"); + /* Not fatal, some boards don't have switchable power */ + } + + host->vqmmc = rt_regulator_get(dev, "vqmmc"); + if (!host->vqmmc) { + LOG_W("No vqmmc regulator"); + /* Not fatal, fixed I/O voltage is acceptable */ + } + + /* Power on MMC */ + if (host->vmmc || host->vqmmc) { + ret = mmc_power_on(host); + if (ret != RT_EOK) { + goto err_put_regulators; + } + } + + /* Initialize MMC hardware */ + /* ... */ + + pdev->priv = host; + LOG_I("MMC host initialized"); + + return RT_EOK; + +err_put_regulators: + if (host->vqmmc) + rt_regulator_put(host->vqmmc); + if (host->vmmc) + rt_regulator_put(host->vmmc); + rt_clk_put(host->clk); +err_iounmap: + rt_iounmap(host->base); +err_free_host: + rt_free(host); + return ret; +} + +static rt_err_t mmc_remove(struct rt_platform_device *pdev) +{ + struct mmc_host *host = pdev->priv; + + /* Power off */ + if (host->vmmc || host->vqmmc) { + mmc_power_off(host); + } + + /* Release resources */ + if (host->vqmmc) + rt_regulator_put(host->vqmmc); + if (host->vmmc) + rt_regulator_put(host->vmmc); + rt_clk_put(host->clk); + rt_iounmap(host->base); + rt_free(host); + + return RT_EOK; +} + +static const struct rt_ofw_node_id mmc_ofw_ids[] = { + { .compatible = "vendor,mmc" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver mmc_driver = { + .name = "mmc", + .ids = mmc_ofw_ids, + .probe = mmc_probe, + .remove = mmc_remove, +}; + +RT_PLATFORM_DRIVER_EXPORT(mmc_driver); +``` + +## Driver Implementation Guide + +### Overview + +Implementing a regulator driver involves: +1. Defining regulator parameters +2. Implementing regulator operations +3. Registering with the framework +4. (Optional) Parsing device tree properties + +### Key Structures + +#### rt_regulator_node + +```c +struct rt_regulator_node { + rt_list_t list; + rt_list_t children_nodes; + + struct rt_device *dev; /* Parent device */ + struct rt_regulator_node *parent; /* Parent regulator */ + + const char *supply_name; /* Supply name */ + const struct rt_regulator_ops *ops; /* Operations */ + + struct rt_ref ref; /* Reference count */ + rt_atomic_t enabled_count; /* Enable count */ + + const struct rt_regulator_param *param; /* Parameters */ + rt_list_t notifier_nodes; /* Notifier list */ + + void *priv; /* Private data */ +}; +``` + +#### rt_regulator_param + +```c +struct rt_regulator_param { + const char *name; /* Regulator name */ + + int min_uvolt; /* Minimum voltage in µV */ + int max_uvolt; /* Maximum voltage in µV */ + int min_uamp; /* Minimum current in µA */ + int max_uamp; /* Maximum current in µA */ + int ramp_delay; /* Voltage ramp rate in µV/µs */ + int enable_delay; /* Enable delay in µs */ + int off_on_delay; /* Off-to-on delay in µs */ + int settling_time; /* General settling time */ + int settling_time_up; /* Voltage increase settling time */ + int settling_time_down; /* Voltage decrease settling time */ + + rt_uint32_t enable_active_high:1; /* Enable pin polarity */ + rt_uint32_t boot_on:1; /* Enabled at boot */ + rt_uint32_t always_on:1; /* Never disable */ + rt_uint32_t soft_start:1; /* Soft-start enabled */ + rt_uint32_t pull_down:1; /* Pull-down when off */ + rt_uint32_t over_current_protection:1; /* OCP enabled */ + rt_uint32_t ramp_disable:1; /* Ramp disabled */ +}; +``` + +#### rt_regulator_ops + +```c +struct rt_regulator_ops { + rt_err_t (*enable)(struct rt_regulator_node *reg); + rt_err_t (*disable)(struct rt_regulator_node *reg); + rt_bool_t (*is_enabled)(struct rt_regulator_node *reg); + rt_err_t (*set_voltage)(struct rt_regulator_node *reg, int min_uvolt, int max_uvolt); + int (*get_voltage)(struct rt_regulator_node *reg); + rt_err_t (*set_mode)(struct rt_regulator_node *reg, rt_uint32_t mode); + rt_int32_t (*get_mode)(struct rt_regulator_node *reg); + rt_err_t (*set_ramp_delay)(struct rt_regulator_node *reg, int ramp); + rt_uint32_t (*enable_time)(struct rt_regulator_node *reg); +}; +``` + +All callbacks are optional. Implement only what the hardware supports. + +### Example: Simple Fixed Regulator Driver + +```c +#include +#include +#include +#include + +struct my_regulator { + struct rt_regulator_node reg_node; + struct rt_regulator_param param; + rt_base_t enable_pin; +}; + +static rt_err_t my_regulator_enable(struct rt_regulator_node *reg_node) +{ + struct my_regulator *reg = rt_container_of(reg_node, struct my_regulator, reg_node); + + if (reg->enable_pin >= 0) { + rt_pin_mode(reg->enable_pin, PIN_MODE_OUTPUT); + rt_pin_write(reg->enable_pin, + reg->param.enable_active_high ? PIN_HIGH : PIN_LOW); + } + + return RT_EOK; +} + +static rt_err_t my_regulator_disable(struct rt_regulator_node *reg_node) +{ + struct my_regulator *reg = rt_container_of(reg_node, struct my_regulator, reg_node); + + if (reg->enable_pin >= 0) { + rt_pin_mode(reg->enable_pin, PIN_MODE_OUTPUT); + rt_pin_write(reg->enable_pin, + reg->param.enable_active_high ? PIN_LOW : PIN_HIGH); + } + + return RT_EOK; +} + +static rt_bool_t my_regulator_is_enabled(struct rt_regulator_node *reg_node) +{ + struct my_regulator *reg = rt_container_of(reg_node, struct my_regulator, reg_node); + + if (reg->enable_pin >= 0) { + rt_uint8_t value; + rt_pin_mode(reg->enable_pin, PIN_MODE_INPUT); + value = rt_pin_read(reg->enable_pin); + return (value == PIN_HIGH) == reg->param.enable_active_high; + } + + return RT_TRUE; /* Always on if no enable pin */ +} + +static int my_regulator_get_voltage(struct rt_regulator_node *reg_node) +{ + struct my_regulator *reg = rt_container_of(reg_node, struct my_regulator, reg_node); + + /* Fixed voltage: return average of min and max */ + return (reg->param.min_uvolt + reg->param.max_uvolt) / 2; +} + +static const struct rt_regulator_ops my_regulator_ops = { + .enable = my_regulator_enable, + .disable = my_regulator_disable, + .is_enabled = my_regulator_is_enabled, + .get_voltage = my_regulator_get_voltage, +}; + +static rt_err_t my_regulator_probe(struct rt_platform_device *pdev) +{ + rt_err_t ret; + struct rt_device *dev = &pdev->parent; + struct my_regulator *reg; + rt_uint32_t voltage; + + /* Allocate regulator structure */ + reg = rt_calloc(1, sizeof(*reg)); + if (!reg) + return -RT_ENOMEM; + + /* Parse device tree properties */ + if (rt_dm_dev_prop_read_u32(dev, "regulator-min-microvolt", &voltage) == 0) { + reg->param.min_uvolt = voltage; + } + + if (rt_dm_dev_prop_read_u32(dev, "regulator-max-microvolt", &voltage) == 0) { + reg->param.max_uvolt = voltage; + } + + reg->param.name = rt_dm_dev_get_name(dev); + reg->param.enable_active_high = + rt_dm_dev_prop_read_bool(dev, "enable-active-high"); + reg->param.always_on = + rt_dm_dev_prop_read_bool(dev, "regulator-always-on"); + reg->param.boot_on = + rt_dm_dev_prop_read_bool(dev, "regulator-boot-on"); + + /* Get enable GPIO pin */ + reg->enable_pin = rt_pin_get_named_pin(dev, "enable", 0, RT_NULL, RT_NULL); + if (reg->enable_pin < 0) { + reg->enable_pin = -1; /* No GPIO control */ + } + + /* Apply pin control configuration */ + rt_pin_ctrl_confs_apply(dev, 0); + + /* Initialize regulator node */ + reg->reg_node.dev = dev; + reg->reg_node.supply_name = reg->param.name; + reg->reg_node.ops = &my_regulator_ops; + reg->reg_node.param = ®->param; + + /* Register with framework */ + ret = rt_regulator_register(®->reg_node); + if (ret != RT_EOK) { + LOG_E("Failed to register regulator: %d", ret); + rt_free(reg); + return ret; + } + + pdev->priv = reg; + LOG_I("Regulator '%s' registered: %d-%d µV", + reg->param.name, reg->param.min_uvolt, reg->param.max_uvolt); + + return RT_EOK; +} + +static rt_err_t my_regulator_remove(struct rt_platform_device *pdev) +{ + struct my_regulator *reg = pdev->priv; + + rt_regulator_unregister(®->reg_node); + rt_free(reg); + + return RT_EOK; +} + +static const struct rt_ofw_node_id my_regulator_ofw_ids[] = { + { .compatible = "myvendor,my-regulator" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver my_regulator_driver = { + .name = "my-regulator", + .ids = my_regulator_ofw_ids, + .probe = my_regulator_probe, + .remove = my_regulator_remove, +}; + +RT_PLATFORM_DRIVER_EXPORT(my_regulator_driver); +``` + +### Helper Function: Device Tree Parsing + +The framework provides a helper to parse standard regulator properties: + +```c +#ifdef RT_USING_OFW +rt_err_t regulator_ofw_parse(struct rt_ofw_node *np, + struct rt_regulator_param *param); +#endif +``` + +**Usage in Driver**: +```c +#include "regulator_dm.h" /* Internal header */ + +static rt_err_t my_probe(struct rt_platform_device *pdev) +{ + struct my_regulator *reg; + struct rt_device *dev = &pdev->parent; + + reg = rt_calloc(1, sizeof(*reg)); + if (!reg) + return -RT_ENOMEM; + + /* Parse standard properties */ + regulator_ofw_parse(dev->ofw_node, ®->param); + + /* Parse custom properties */ + /* ... */ + + return RT_EOK; +} +``` + +## Architecture Diagram + +The regulator framework architecture with component relationships: + +![Regulator Architecture](regulator-architecture.svg) + +## Best Practices + +### For Consumer Drivers + +1. **Always check return values**: Regulator operations can fail +2. **Balance enable/disable**: Match every enable with a disable +3. **Order matters**: Enable regulators before clocks, disable in reverse order +4. **Use voltage ranges**: Specify acceptable voltage ranges, not exact values +5. **Handle probe deferral**: Regulators may not be available during early boot + +```c +/* Good: Flexible voltage range */ +ret = rt_regulator_set_voltage(reg, 1710000, 1890000); + +/* Avoid: Overly strict requirement */ +ret = rt_regulator_set_voltage(reg, 1800000, 1800000); +``` + +### For Provider Drivers + +1. **Implement only supported operations**: Leave unsupported ops NULL +2. **Use reference counting**: Don't track enables yourself, the framework does it +3. **Report accurate parameters**: Provide correct voltage ranges and delays +4. **Handle parent supplies**: Set `vin-supply` in device tree for chained regulators +5. **Use atomic operations carefully**: `enabled_count` is managed by the framework + +### Common Patterns + +#### Power Sequencing + +```c +/* Correct power-on sequence */ +rt_regulator_enable(core_supply); +rt_regulator_enable(io_supply); +rt_clk_prepare_enable(clock); +/* Initialize hardware */ + +/* Correct power-off sequence (reverse order) */ +/* Shutdown hardware */ +rt_clk_disable_unprepare(clock); +rt_regulator_disable(io_supply); +rt_regulator_disable(core_supply); +``` + +#### Voltage Scaling + +```c +/* Dynamic voltage scaling for performance modes */ +switch (perf_mode) { +case PERF_HIGH: + rt_regulator_set_voltage(cpu_supply, 1200000, 1200000); + rt_clk_set_rate(cpu_clk, 1000000000); /* 1GHz */ + break; +case PERF_NORMAL: + rt_clk_set_rate(cpu_clk, 800000000); /* 800MHz */ + rt_regulator_set_voltage(cpu_supply, 1000000, 1000000); + break; +case PERF_LOW: + rt_clk_set_rate(cpu_clk, 400000000); /* 400MHz */ + rt_regulator_set_voltage(cpu_supply, 900000, 900000); + break; +} +``` + +## Troubleshooting + +### Common Issues + +1. **Regulator not found** + - Check device tree: Ensure `-supply` property exists + - Check compatible string: Verify regulator driver is loaded + - Check Kconfig: Enable regulator support and driver + +2. **Enable/disable balance errors** + - Count enable/disable calls: They must match + - Check error paths: Ensure cleanup code disables on failure + - Use debugging: Add LOG_D calls to track reference count + +3. **Voltage out of range** + - Check device tree: Verify `regulator-min/max-microvolt` properties + - Check hardware: Ensure physical regulator supports the voltage + - Check parent supply: Parent must support child's voltage range + +4. **Boot failures** + - Check boot-on property: Critical regulators need `regulator-boot-on` + - Check always-on property: Some regulators must never be disabled + - Check sequencing: Power supplies must enable before consumers + +### Debug Logging + +Enable regulator debug logging: + +```c +#define DBG_TAG "rtdm.regulator" +#define DBG_LVL DBG_LOG +#include +``` + +Or at runtime: +``` +msh> ulog_tag_lvl_set rtdm.regulator 7 +``` + +## Performance Considerations + +### Memory Usage + +- Each regulator node: ~100-150 bytes +- Each consumer reference: ~16 bytes +- Device tree overhead: Depends on complexity + +### Timing Considerations + +- Enable delays: Specified by hardware (µs to ms) +- Ramp delays: Voltage-dependent (µs per volt) +- Notifier overhead: Linear with number of registered notifiers +- Lock contention: Minimal with per-regulator locking + +### Optimization Tips + +1. **Group voltage changes**: Change multiple voltages before delays +2. **Use async operations**: Don't block on regulator delays if possible +3. **Cache voltage values**: Avoid unnecessary get_voltage calls +4. **Minimize mode changes**: Mode switching can be expensive + +## Related Modules + +- **clk**: Clock management, often paired with regulator control +- **pinctrl**: Pin configuration, may require voltage switching +- **pmdomain**: Power domain management, higher-level power control +- **thermal**: Thermal management, may trigger voltage/frequency scaling + +## References + +- RT-Thread Source: `components/drivers/regulator/` +- Header File: `components/drivers/include/drivers/regulator.h` +- Device Tree Bindings: [Linux Regulator Bindings](https://www.kernel.org/doc/Documentation/devicetree/bindings/regulator/) +- [RT-Thread DM Documentation](../README.md) diff --git a/documentation/6.components/device-driver/device_driver_model/regulator/README_zh.md b/documentation/6.components/device-driver/device_driver_model/regulator/README_zh.md new file mode 100644 index 00000000000..4487f6da9c9 --- /dev/null +++ b/documentation/6.components/device-driver/device_driver_model/regulator/README_zh.md @@ -0,0 +1,777 @@ +# Regulator 框架 + +## 简介 + +RT-Thread 的 Regulator(调节器)框架为嵌入式系统中的电压和电流调节器管理提供了标准化接口。调节器是为各种硬件外设、处理器和其他系统组件提供稳定电源供应的关键组件。 + +### 概述 + +电压调节器是一种电子电路,无论负载电流或输入电压如何变化,都能保持恒定的电压水平。它们对于以下方面至关重要: + +- **电源管理**:高效的电源分配和管理 +- **电压调节**:动态电压和频率调节(DVFS)以优化功耗 +- **设备保护**:保护设备免受过压和过流影响 +- **系统稳定性**:确保硬件组件稳定运行 + +常见的调节器类型包括: +- **线性调节器**:简单、低噪声但效率较低 +- **开关调节器(SMPS)**:更高效但可能产生噪声 +- **LDO(低压差调节器)**:压差低的线性调节器 +- **降压/升压转换器**:降压/升压开关调节器 + +### RT-Thread 实现 + +RT-Thread 的调节器框架位于 `components/drivers/regulator/`,提供: + +1. **消费者 API**:设备驱动程序管理电源的简单接口 +2. **提供者 API**:实现调节器驱动程序的框架 +3. **调节器树**:调节器依赖关系的层次化管理 +4. **设备树集成**:从设备树自动配置 +5. **引用计数**:多消费者安全启用/禁用 +6. **通知链**:电压变化的事件通知 +7. **多驱动支持**:固定、GPIO 控制、SCMI 基础的调节器 + +**架构**: +``` +┌─────────────────────────────────────────────────────────┐ +│ 消费者驱动程序 │ +│ (UART, SPI, I2C, MMC, CPU 等) │ +└────────────────────┬────────────────────────────────────┘ + │ 消费者 API + │ (get, enable, set_voltage 等) +┌────────────────────┴────────────────────────────────────┐ +│ Regulator 框架核心 │ +│ - 引用计数 │ +│ - 调节器树管理 │ +│ - 通知链 │ +│ - 电压/电流验证 │ +└────────────────────┬────────────────────────────────────┘ + │ 提供者 API + │ (操作回调) +┌────────────────────┴────────────────────────────────────┐ +│ 调节器驱动程序 │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ 固定 │ │ GPIO │ │ SCMI │ │ +│ │ 调节器 │ │ 调节器 │ │ 调节器 │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +└────────────────────┬────────────────────────────────────┘ + │ +┌────────────────────┴────────────────────────────────────┐ +│ 硬件调节器 │ +│ (PMIC、分立调节器、电源) │ +└──────────────────────────────────────────────────────────┘ +``` + +## Kconfig 配置 + +### 主配置 + +调节器框架依赖于设备驱动模型(DM): + +```kconfig +menuconfig RT_USING_REGULATOR + bool "Using Voltage and Current Regulator" + select RT_USING_ADT + select RT_USING_ADT_REF + depends on RT_USING_DM + default n +``` + +**在 menuconfig 中的位置**: +``` +RT-Thread Components → Device Drivers → Using Voltage and Current Regulator +``` + +**依赖项**: +- `RT_USING_DM`:必须首先启用 +- `RT_USING_ADT`:抽象数据类型(自动) +- `RT_USING_ADT_REF`:引用计数支持(自动) + +### 调节器驱动选项 + +#### 固定调节器 +```kconfig +config RT_REGULATOR_FIXED + bool "Fixed regulator support" + depends on RT_USING_REGULATOR + depends on RT_USING_PIN + depends on RT_USING_PINCTRL + default y +``` + +支持固定输出电压的调节器,可选通过 GPIO 使能引脚控制。 + +#### GPIO 调节器 +```kconfig +config RT_REGULATOR_GPIO + bool "GPIO regulator support" + depends on RT_USING_REGULATOR + depends on RT_USING_PIN + default y +``` + +支持通过 GPIO 引脚选择多个电压级别的调节器。 + +#### SCMI 调节器 +```kconfig +config RT_REGULATOR_SCMI + bool "SCMI regulator support" + depends on RT_USING_REGULATOR + depends on RT_USING_OFW + depends on RT_FIRMWARE_ARM_SCMI + default n +``` + +支持通过 ARM 系统控制和管理接口(SCMI)控制的调节器。 + +## 设备树绑定 + +### 通用属性 + +所有调节器节点支持这些标准属性: + +```dts +regulator-name = "supply_name"; /* 可读名称 */ +regulator-min-microvolt = ; /* 最小电压(µV)*/ +regulator-max-microvolt = ; /* 最大电压(µV)*/ +regulator-min-microamp = ; /* 最小电流(µA)*/ +regulator-max-microamp = ; /* 最大电流(µA)*/ +regulator-ramp-delay = ; /* 电压变化率(µV/µs)*/ +regulator-enable-ramp-delay = ; /* 使能延迟(µs)*/ +regulator-settling-time-us = ; /* 稳定时间(µs)*/ +regulator-settling-time-up-us = ; /* 电压升高稳定时间 */ +regulator-settling-time-down-us = ; /* 电压降低稳定时间 */ +enable-active-high; /* 使能引脚高电平有效(默认)*/ +regulator-boot-on; /* 启动时使能 */ +regulator-always-on; /* 永不禁用 */ +regulator-soft-start; /* 使能软启动 */ +regulator-pull-down; /* 关闭时使能下拉 */ +regulator-over-current-protection; /* 使能过流保护 */ +``` + +### 固定调节器示例 + +固定调节器具有恒定的输出电压: + +```dts +regulators { + /* 简单的固定 3.3V 调节器 */ + vcc_3v3: regulator-vcc-3v3 { + compatible = "regulator-fixed"; + regulator-name = "vcc-3v3"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-always-on; + regulator-boot-on; + }; + + /* 带 GPIO 使能控制的固定调节器 */ + vcc_sd: regulator-vcc-sd { + compatible = "regulator-fixed"; + regulator-name = "vcc-sd"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + gpio = <&gpio0 10 GPIO_ACTIVE_HIGH>; + pinctrl-names = "default"; + pinctrl-0 = <&sd_power_pins>; + enable-active-high; + startup-delay-us = <100000>; /* 100ms */ + off-on-delay-us = <10000>; /* 10ms */ + }; + + /* 由另一个调节器供电的调节器 */ + vcc_1v8: regulator-vcc-1v8 { + compatible = "regulator-fixed"; + regulator-name = "vcc-1v8"; + regulator-min-microvolt = <1800000>; + regulator-max-microvolt = <1800000>; + vin-supply = <&vcc_3v3>; /* 父供电 */ + regulator-always-on; + }; +}; +``` + +### 设备树中的消费者使用 + +设备使用 supply 属性引用调节器: + +```dts +/* 带多个电源的 UART */ +uart0: serial@10000000 { + compatible = "vendor,uart"; + reg = <0x10000000 0x1000>; + interrupts = <32>; + clocks = <&clk_uart0>; + + /* 电源 */ + vdd-supply = <&vcc_3v3>; /* 核心供电 */ + vddio-supply = <&vcc_1v8>; /* I/O 供电 */ + + status = "okay"; +}; + +/* 带调节器控制的 MMC/SD 控制器 */ +mmc0: mmc@20000000 { + compatible = "vendor,mmc"; + reg = <0x20000000 0x1000>; + + vmmc-supply = <&vcc_sd>; /* 卡电源 */ + vqmmc-supply = <&vcc_1v8>; /* I/O 电平转换器电源 */ + + status = "okay"; +}; +``` + +## 应用层 API + +### 概述 + +消费者 API 为设备驱动程序提供管理其电源供应的函数。所有操作都使用通过 get API 获得的不透明 `struct rt_regulator` 指针。 + +### 获取和释放调节器 + +#### rt_regulator_get + +```c +struct rt_regulator *rt_regulator_get(struct rt_device *dev, const char *id); +``` + +为设备获取调节器。 + +**参数**: +- `dev`:设备结构指针 +- `id`:供电名称(匹配设备树中的 `-supply`,例如 "vdd"、"vmmc") + +**返回值**: +- 成功时返回调节器指针 +- 失败时返回 NULL + +**示例**: +```c +struct rt_device *dev = &pdev->parent; +struct rt_regulator *vdd_reg; + +/* 获取 "vdd" 供电 */ +vdd_reg = rt_regulator_get(dev, "vdd"); +if (!vdd_reg) { + LOG_E("Failed to get vdd regulator"); + return -RT_ERROR; +} +``` + +#### rt_regulator_put + +```c +void rt_regulator_put(struct rt_regulator *reg); +``` + +释放调节器引用。 + +**参数**: +- `reg`:从 `rt_regulator_get()` 获得的调节器指针 + +### 启用和禁用 + +#### rt_regulator_enable + +```c +rt_err_t rt_regulator_enable(struct rt_regulator *reg); +``` + +启用调节器。使用引用计数,因此多次启用需要匹配的禁用。 + +**参数**: +- `reg`:调节器指针 + +**返回值**: +- 成功时返回 `RT_EOK` +- 失败时返回错误代码 + +**注意**: +- 自动启用父调节器 +- 如果配置了启用延迟,则等待 +- 调用通知链 + +**示例**: +```c +rt_err_t ret; + +ret = rt_regulator_enable(vdd_reg); +if (ret != RT_EOK) { + LOG_E("Failed to enable regulator: %d", ret); + return ret; +} +``` + +#### rt_regulator_disable + +```c +rt_err_t rt_regulator_disable(struct rt_regulator *reg); +``` + +禁用调节器。仅当引用计数达到零时才实际禁用。 + +**参数**: +- `reg`:调节器指针 + +**返回值**: +- 成功时返回 `RT_EOK` +- 失败时返回错误代码 + +#### rt_regulator_is_enabled + +```c +rt_bool_t rt_regulator_is_enabled(struct rt_regulator *reg); +``` + +检查调节器当前是否启用。 + +**参数**: +- `reg`:调节器指针 + +**返回值**: +- 启用时返回 `RT_TRUE` +- 禁用时返回 `RT_FALSE` + +### 电压控制 + +#### rt_regulator_set_voltage + +```c +rt_err_t rt_regulator_set_voltage(struct rt_regulator *reg, int min_uvolt, int max_uvolt); +``` + +在范围内设置调节器输出电压。 + +**参数**: +- `reg`:调节器指针 +- `min_uvolt`:最小可接受电压(微伏,µV) +- `max_uvolt`:最大可接受电压(微伏,µV) + +**返回值**: +- 成功时返回 `RT_EOK` +- 不支持电压控制时返回 `-RT_ENOSYS` +- 失败时返回错误代码 + +**注意**: +- 电压必须在调节器配置的范围内 +- 框架在指定范围内选择最佳电压 +- 电压变化后等待稳定时间 +- 调用通知链并发送电压变化事件 + +**示例**: +```c +/* 将电压设置为 1.8V ±5% */ +ret = rt_regulator_set_voltage(vdd_reg, 1710000, 1890000); +if (ret != RT_EOK) { + LOG_E("Failed to set voltage: %d", ret); +} + +/* 设置精确电压 */ +ret = rt_regulator_set_voltage(vdd_reg, 3300000, 3300000); +``` + +#### rt_regulator_get_voltage + +```c +int rt_regulator_get_voltage(struct rt_regulator *reg); +``` + +获取当前调节器输出电压。 + +**参数**: +- `reg`:调节器指针 + +**返回值**: +- 当前电压(微伏,µV) +- 失败时返回负错误代码 + +**示例**: +```c +int voltage = rt_regulator_get_voltage(vdd_reg); +if (voltage > 0) { + LOG_I("当前电压: %d.%03dV", voltage / 1000000, (voltage / 1000) % 1000); +} +``` + +### 工作模式 + +#### rt_regulator_set_mode + +```c +rt_err_t rt_regulator_set_mode(struct rt_regulator *reg, rt_uint32_t mode); +``` + +设置调节器工作模式。 + +**参数**: +- `reg`:调节器指针 +- `mode`:工作模式标志: + - `RT_REGULATOR_MODE_FAST`:高速模式(功耗较高) + - `RT_REGULATOR_MODE_NORMAL`:正常工作模式 + - `RT_REGULATOR_MODE_IDLE`:空闲模式(性能降低) + - `RT_REGULATOR_MODE_STANDBY`:待机模式(功耗最低) + +**返回值**: +- 成功时返回 `RT_EOK` +- 不支持模式控制时返回 `-RT_ENOSYS` +- 失败时返回错误代码 + +**示例**: +```c +/* 空闲期间设置为低功耗模式 */ +rt_regulator_set_mode(vdd_reg, RT_REGULATOR_MODE_IDLE); + +/* 恢复正常模式 */ +rt_regulator_set_mode(vdd_reg, RT_REGULATOR_MODE_NORMAL); +``` + +## 完整应用示例 + +### 示例:带调节器控制的 MMC/SD 驱动程序 + +```c +#include +#include +#include +#include + +struct mmc_host { + void *base; + int irq; + struct rt_clk *clk; + struct rt_regulator *vmmc; /* 卡电源 */ + struct rt_regulator *vqmmc; /* I/O 电压电源 */ +}; + +static rt_err_t mmc_power_on(struct mmc_host *host) +{ + rt_err_t ret; + + /* 启用卡电源 */ + ret = rt_regulator_enable(host->vmmc); + if (ret != RT_EOK) { + LOG_E("启用 vmmc 失败: %d", ret); + return ret; + } + + /* 将 I/O 电压设置为初始 3.3V */ + ret = rt_regulator_enable(host->vqmmc); + if (ret != RT_EOK) { + LOG_E("启用 vqmmc 失败: %d", ret); + goto err_disable_vmmc; + } + + ret = rt_regulator_set_voltage(host->vqmmc, 3300000, 3300000); + if (ret != RT_EOK) { + goto err_disable_vqmmc; + } + + /* 启用时钟 */ + ret = rt_clk_prepare_enable(host->clk); + if (ret != RT_EOK) { + goto err_disable_vqmmc; + } + + return RT_EOK; + +err_disable_vqmmc: + rt_regulator_disable(host->vqmmc); +err_disable_vmmc: + rt_regulator_disable(host->vmmc); + return ret; +} + +static void mmc_power_off(struct mmc_host *host) +{ + /* 禁用时钟 */ + rt_clk_disable_unprepare(host->clk); + + /* 按相反顺序禁用调节器 */ + rt_regulator_disable(host->vqmmc); + rt_regulator_disable(host->vmmc); +} + +static rt_err_t mmc_probe(struct rt_platform_device *pdev) +{ + rt_err_t ret; + struct rt_device *dev = &pdev->parent; + struct mmc_host *host; + + /* 分配主机结构 */ + host = rt_calloc(1, sizeof(*host)); + if (!host) + return -RT_ENOMEM; + + /* 映射 MMIO 区域 */ + host->base = rt_dm_dev_iomap(dev, 0); + if (!host->base) { + ret = -RT_ERROR; + goto err_free_host; + } + + /* 获取 IRQ */ + host->irq = rt_dm_dev_get_irq(dev, 0); + + /* 获取时钟 */ + host->clk = rt_clk_get_by_name(dev, "mmc"); + if (!host->clk) { + ret = -RT_ERROR; + goto err_iounmap; + } + + /* 获取调节器 */ + host->vmmc = rt_regulator_get(dev, "vmmc"); + host->vqmmc = rt_regulator_get(dev, "vqmmc"); + + /* 启动 MMC */ + if (host->vmmc || host->vqmmc) { + ret = mmc_power_on(host); + if (ret != RT_EOK) { + goto err_put_regulators; + } + } + + pdev->priv = host; + LOG_I("MMC 主机已初始化"); + + return RT_EOK; + +err_put_regulators: + if (host->vqmmc) + rt_regulator_put(host->vqmmc); + if (host->vmmc) + rt_regulator_put(host->vmmc); + rt_clk_put(host->clk); +err_iounmap: + rt_iounmap(host->base); +err_free_host: + rt_free(host); + return ret; +} +``` + +## 驱动实现指南 + +### 核心结构 + +#### rt_regulator_param + +```c +struct rt_regulator_param { + const char *name; /* 调节器名称 */ + + int min_uvolt; /* 最小电压(µV)*/ + int max_uvolt; /* 最大电压(µV)*/ + int min_uamp; /* 最小电流(µA)*/ + int max_uamp; /* 最大电流(µA)*/ + int ramp_delay; /* 电压爬坡率(µV/µs)*/ + int enable_delay; /* 使能延迟(µs)*/ + int off_on_delay; /* 关闭到打开延迟(µs)*/ + + rt_uint32_t enable_active_high:1; /* 使能引脚极性 */ + rt_uint32_t boot_on:1; /* 启动时启用 */ + rt_uint32_t always_on:1; /* 永不禁用 */ + rt_uint32_t soft_start:1; /* 软启动启用 */ +}; +``` + +#### rt_regulator_ops + +```c +struct rt_regulator_ops { + rt_err_t (*enable)(struct rt_regulator_node *reg); + rt_err_t (*disable)(struct rt_regulator_node *reg); + rt_bool_t (*is_enabled)(struct rt_regulator_node *reg); + rt_err_t (*set_voltage)(struct rt_regulator_node *reg, int min_uvolt, int max_uvolt); + int (*get_voltage)(struct rt_regulator_node *reg); + rt_err_t (*set_mode)(struct rt_regulator_node *reg, rt_uint32_t mode); + rt_int32_t (*get_mode)(struct rt_regulator_node *reg); +}; +``` + +所有回调都是可选的。只实现硬件支持的功能。 + +### 简单固定调节器驱动程序示例 + +```c +#include +#include +#include +#include + +struct my_regulator { + struct rt_regulator_node reg_node; + struct rt_regulator_param param; + rt_base_t enable_pin; +}; + +static rt_err_t my_regulator_enable(struct rt_regulator_node *reg_node) +{ + struct my_regulator *reg = rt_container_of(reg_node, struct my_regulator, reg_node); + + if (reg->enable_pin >= 0) { + rt_pin_mode(reg->enable_pin, PIN_MODE_OUTPUT); + rt_pin_write(reg->enable_pin, + reg->param.enable_active_high ? PIN_HIGH : PIN_LOW); + } + + return RT_EOK; +} + +static rt_err_t my_regulator_disable(struct rt_regulator_node *reg_node) +{ + struct my_regulator *reg = rt_container_of(reg_node, struct my_regulator, reg_node); + + if (reg->enable_pin >= 0) { + rt_pin_mode(reg->enable_pin, PIN_MODE_OUTPUT); + rt_pin_write(reg->enable_pin, + reg->param.enable_active_high ? PIN_LOW : PIN_HIGH); + } + + return RT_EOK; +} + +static const struct rt_regulator_ops my_regulator_ops = { + .enable = my_regulator_enable, + .disable = my_regulator_disable, +}; + +static rt_err_t my_regulator_probe(struct rt_platform_device *pdev) +{ + rt_err_t ret; + struct rt_device *dev = &pdev->parent; + struct my_regulator *reg; + rt_uint32_t voltage; + + /* 分配调节器结构 */ + reg = rt_calloc(1, sizeof(*reg)); + if (!reg) + return -RT_ENOMEM; + + /* 解析设备树属性 */ + rt_dm_dev_prop_read_u32(dev, "regulator-min-microvolt", &voltage); + reg->param.min_uvolt = voltage; + + rt_dm_dev_prop_read_u32(dev, "regulator-max-microvolt", &voltage); + reg->param.max_uvolt = voltage; + + reg->param.name = rt_dm_dev_get_name(dev); + reg->param.enable_active_high = + rt_dm_dev_prop_read_bool(dev, "enable-active-high"); + + /* 获取使能 GPIO 引脚 */ + reg->enable_pin = rt_pin_get_named_pin(dev, "enable", 0, RT_NULL, RT_NULL); + + /* 初始化调节器节点 */ + reg->reg_node.dev = dev; + reg->reg_node.supply_name = reg->param.name; + reg->reg_node.ops = &my_regulator_ops; + reg->reg_node.param = ®->param; + + /* 向框架注册 */ + ret = rt_regulator_register(®->reg_node); + if (ret != RT_EOK) { + rt_free(reg); + return ret; + } + + pdev->priv = reg; + return RT_EOK; +} + +static const struct rt_ofw_node_id my_regulator_ofw_ids[] = { + { .compatible = "myvendor,my-regulator" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver my_regulator_driver = { + .name = "my-regulator", + .ids = my_regulator_ofw_ids, + .probe = my_regulator_probe, +}; + +RT_PLATFORM_DRIVER_EXPORT(my_regulator_driver); +``` + +## 最佳实践 + +### 对于消费者驱动程序 + +1. **始终检查返回值**:调节器操作可能失败 +2. **平衡启用/禁用**:每次启用都要匹配一次禁用 +3. **顺序很重要**:在时钟之前启用调节器,按相反顺序禁用 +4. **使用电压范围**:指定可接受的电压范围,而不是精确值 +5. **处理探测延迟**:调节器在早期启动时可能不可用 + +### 对于提供者驱动程序 + +1. **仅实现支持的操作**:不支持的操作留 NULL +2. **使用引用计数**:不要自己跟踪启用,框架会处理 +3. **报告准确的参数**:提供正确的电压范围和延迟 +4. **处理父供电**:在设备树中为链式调节器设置 `vin-supply` + +### 常见模式 + +#### 电源时序 + +```c +/* 正确的上电顺序 */ +rt_regulator_enable(core_supply); +rt_regulator_enable(io_supply); +rt_clk_prepare_enable(clock); +/* 初始化硬件 */ + +/* 正确的断电顺序(相反顺序)*/ +/* 关闭硬件 */ +rt_clk_disable_unprepare(clock); +rt_regulator_disable(io_supply); +rt_regulator_disable(core_supply); +``` + +## 故障排除 + +### 常见问题 + +1. **找不到调节器** + - 检查设备树:确保 `-supply` 属性存在 + - 检查 compatible 字符串:验证调节器驱动已加载 + - 检查 Kconfig:启用调节器支持和驱动程序 + +2. **启用/禁用不平衡** + - 计数启用/禁用调用:它们必须匹配 + - 检查错误路径:确保清理代码在失败时禁用 + - 使用调试:添加 LOG_D 调用以跟踪引用计数 + +3. **电压超出范围** + - 检查设备树:验证 `regulator-min/max-microvolt` 属性 + - 检查硬件:确保物理调节器支持该电压 + - 检查父供电:父级必须支持子级的电压范围 + +## 性能考虑 + +### 内存使用 + +- 每个调节器节点:约 100-150 字节 +- 每个消费者引用:约 16 字节 +- 设备树开销:取决于复杂性 + +### 时序考虑 + +- 使能延迟:由硬件指定(µs 到 ms) +- 爬坡延迟:电压相关(µs/伏) +- 通知器开销:与注册的通知器数量成线性关系 + +## 相关模块 + +- **clk**:时钟管理,通常与调节器控制配对 +- **pinctrl**:引脚配置,可能需要电压切换 +- **pmdomain**:电源域管理,更高级别的电源控制 +- **thermal**:热管理,可能触发电压/频率调节 + +## 参考资料 + +- RT-Thread 源代码:`components/drivers/regulator/` +- 头文件:`components/drivers/include/drivers/regulator.h` +- 设备树绑定:[Linux Regulator 绑定](https://www.kernel.org/doc/Documentation/devicetree/bindings/regulator/) +- [RT-Thread DM 文档](../README_zh.md) diff --git a/documentation/6.components/device-driver/device_driver_model/reset/README.md b/documentation/6.components/device-driver/device_driver_model/reset/README.md new file mode 100644 index 00000000000..d7995ad49af --- /dev/null +++ b/documentation/6.components/device-driver/device_driver_model/reset/README.md @@ -0,0 +1,878 @@ +# Reset Controller Framework + +## Introduction + +The Reset Controller Framework in RT-Thread provides a standardized interface for managing hardware reset signals in embedded systems. Reset controllers are essential for properly initializing, recovering, and managing the state of hardware peripherals and subsystems. + +### General Overview + +Reset signals are fundamental to hardware management for: + +- **Device Initialization**: Bringing devices to a known state during boot +- **Error Recovery**: Resetting devices when errors occur +- **Power Management**: Resetting devices when entering/exiting low-power modes +- **Hot-plug Support**: Reinitializing devices for hotplug scenarios +- **System Stability**: Ensuring clean state transitions + +Common reset controller types include: +- **Register-based**: Single register controlling multiple resets +- **GPIO-based**: Reset lines controlled by GPIO pins +- **Power Management IC (PMIC)**: Resets integrated in power management chips +- **System Control**: Resets managed through system control interfaces (SCMI) + +### RT-Thread Implementation + +The RT-Thread reset controller framework, located in `components/drivers/reset/`, provides: + +1. **Consumer API**: Simple interface for device drivers to control reset lines +2. **Provider API**: Framework for implementing reset controller drivers +3. **Device Tree Integration**: Automatic configuration from FDT +4. **Reset Operations**: Assert, deassert, and pulse reset signals +5. **Array Support**: Managing multiple reset lines for a device +6. **Status Query**: Checking reset line state + +**Architecture**: +``` +┌─────────────────────────────────────────────────────────┐ +│ Consumer Drivers │ +│ (UART, SPI, MMC, USB, Peripheral drivers) │ +└────────────────────┬────────────────────────────────────┘ + │ Consumer API + │ (get, reset, assert, deassert) +┌────────────────────┴────────────────────────────────────┐ +│ Reset Controller Framework │ +│ - Reset Control Management │ +│ - Array Support │ +│ - Status Tracking │ +└────────────────────┬────────────────────────────────────┘ + │ Provider API + │ (ops callbacks) +┌────────────────────┴────────────────────────────────────┐ +│ Reset Controller Drivers │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Simple │ │ SCMI │ │ Custom │ │ +│ │ Reset │ │ Reset │ │ Reset │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +└────────────────────┬────────────────────────────────────┘ + │ +┌────────────────────┴────────────────────────────────────┐ +│ Hardware Reset Controllers │ +│ (Reset registers, PMICs, System controllers) │ +└──────────────────────────────────────────────────────────┘ +``` + +## Kconfig Configuration + +### Main Configuration + +```kconfig +menuconfig RT_USING_RESET + bool "Using Reset Controller support" + depends on RT_USING_DM + depends on RT_USING_OFW + default n +``` + +**Location in menuconfig**: +``` +RT-Thread Components → Device Drivers → Using Reset Controller support +``` + +**Dependencies**: +- `RT_USING_DM`: Must be enabled first +- `RT_USING_OFW`: Device tree support required + +**Default**: Disabled (opt-in feature) + +### Reset Driver Options + +#### SCMI Reset Driver +```kconfig +config RT_RESET_SCMI + bool "Reset driver controlled via ARM SCMI interface" + depends on RT_USING_RESET + depends on RT_FIRMWARE_ARM_SCMI + default n +``` + +Supports reset controllers controlled through ARM System Control and Management Interface (SCMI). + +**Dependencies**: +- `RT_FIRMWARE_ARM_SCMI`: ARM SCMI firmware interface + +#### Simple Reset Driver +```kconfig +config RT_RESET_SIMPLE + bool "Simple Reset Controller Driver" + depends on RT_USING_RESET + default n +``` + +Supports basic register-based reset controllers with simple bit operations. + +### Vendor-Specific Options + +```kconfig +if RT_USING_RESET + osource "$(SOC_DM_RESET_DIR)/Kconfig" +endif +``` + +Allows SoC-specific reset drivers to add their own Kconfig options. + +## Device Tree Bindings + +### Reset Controller Properties + +Reset controllers export resets using: + +```dts +#reset-cells = ; /* Number of cells in reset specifier */ +``` + +The number of cells typically indicates how many values are needed to identify a specific reset line (usually 1). + +### Reset Consumer Properties + +Devices reference reset lines using: + +```dts +resets = <&rstc idx>; /* Reset controller phandle and index */ +reset-names = "name"; /* Reset line names */ +``` + +### Simple Reset Controller Example + +```dts +soc { + /* Reset controller node */ + rstc: reset-controller@1c20000 { + compatible = "vendor,reset-controller", "reset-simple"; + reg = <0x1c20000 0x100>; + #reset-cells = <1>; + }; +}; +``` + +### Consumer Usage Examples + +```dts +/* Single reset consumer */ +uart0: serial@1c28000 { + compatible = "vendor,uart"; + reg = <0x1c28000 0x400>; + interrupts = <0 0 4>; + + clocks = <&ccu 64>; + resets = <&rstc 32>; /* Reset index 32 */ + reset-names = "uart"; + + status = "okay"; +}; + +/* Multiple resets consumer */ +usb0: usb@1c19000 { + compatible = "vendor,usb-otg"; + reg = <0x1c19000 0x1000>; + interrupts = <0 32 4>; + + clocks = <&ccu 24>, <&ccu 25>; + clock-names = "ahb", "otg"; + + resets = <&rstc 11>, <&rstc 12>; + reset-names = "ahb", "phy"; + + status = "okay"; +}; + +/* Reset with shared line */ +mmc0: mmc@1c0f000 { + compatible = "vendor,mmc"; + reg = <0x1c0f000 0x1000>; + + resets = <&rstc 8>; + /* No reset-names for single reset */ + + status = "okay"; +}; +``` + +## Application Layer API + +### Overview + +The consumer API provides simple functions for device drivers to control reset lines. Reset operations are typically performed during device initialization and error recovery. + +### Getting and Releasing Reset Controls + +#### rt_reset_control_get_by_name + +```c +struct rt_reset_control *rt_reset_control_get_by_name(struct rt_device *dev, + const char *name); +``` + +Get a reset control by name. + +**Parameters**: +- `dev`: Device structure pointer +- `name`: Reset line name (matches `reset-names` in device tree) + +**Returns**: +- Pointer to reset control on success +- NULL on failure + +**Example**: +```c +struct rt_device *dev = &pdev->parent; +struct rt_reset_control *rst; + +/* Get the "phy" reset line */ +rst = rt_reset_control_get_by_name(dev, "phy"); +if (!rst) { + LOG_W("No phy reset line"); + /* Not fatal - some boards may not have it */ +} +``` + +#### rt_reset_control_get_by_index + +```c +struct rt_reset_control *rt_reset_control_get_by_index(struct rt_device *dev, + int index); +``` + +Get a reset control by index. + +**Parameters**: +- `dev`: Device structure pointer +- `index`: Reset index (0-based) in `resets` property + +**Returns**: +- Pointer to reset control on success +- NULL on failure + +**Example**: +```c +/* Get the first reset */ +struct rt_reset_control *rst = rt_reset_control_get_by_index(dev, 0); +``` + +#### rt_reset_control_get_array + +```c +struct rt_reset_control *rt_reset_control_get_array(struct rt_device *dev); +``` + +Get all reset controls for a device as an array. + +**Parameters**: +- `dev`: Device structure pointer + +**Returns**: +- Pointer to reset control array on success +- NULL on failure + +**Example**: +```c +struct rt_reset_control *rst_array = rt_reset_control_get_array(dev); +if (rst_array) { + /* All resets can be controlled as a group */ +} +``` + +#### rt_reset_control_get_count + +```c +rt_ssize_t rt_reset_control_get_count(struct rt_device *dev); +``` + +Get the number of reset lines for a device. + +**Parameters**: +- `dev`: Device structure pointer + +**Returns**: +- Number of reset lines +- Negative error code on failure + +#### rt_reset_control_put + +```c +void rt_reset_control_put(struct rt_reset_control *rstc); +``` + +Release a reset control. + +**Parameters**: +- `rstc`: Reset control pointer + +### Reset Operations + +#### rt_reset_control_reset + +```c +rt_err_t rt_reset_control_reset(struct rt_reset_control *rstc); +``` + +Perform a reset pulse: assert then deassert. + +**Parameters**: +- `rstc`: Reset control pointer + +**Returns**: +- `RT_EOK` on success +- Error code on failure + +**Notes**: +- This is the most common operation +- Automatically asserts and then deasserts with appropriate delay +- Safe to call even if reset control is NULL + +**Example**: +```c +/* Reset a peripheral during initialization */ +ret = rt_reset_control_reset(rst); +if (ret != RT_EOK) { + LOG_E("Failed to reset peripheral: %d", ret); + return ret; +} + +/* Wait for hardware to stabilize */ +rt_thread_mdelay(10); +``` + +#### rt_reset_control_assert + +```c +rt_err_t rt_reset_control_assert(struct rt_reset_control *rstc); +``` + +Assert (activate) the reset signal. + +**Parameters**: +- `rstc`: Reset control pointer + +**Returns**: +- `RT_EOK` on success +- Error code on failure + +**Notes**: +- Puts the device into reset state +- Must be followed by deassert to restore operation +- Safe to call even if reset control is NULL + +**Example**: +```c +/* Put device into reset before power off */ +rt_reset_control_assert(rst); +``` + +#### rt_reset_control_deassert + +```c +rt_err_t rt_reset_control_deassert(struct rt_reset_control *rstc); +``` + +Deassert (deactivate) the reset signal. + +**Parameters**: +- `rstc`: Reset control pointer + +**Returns**: +- `RT_EOK` on success +- Error code on failure + +**Notes**: +- Takes the device out of reset state +- Should be called after assert +- Safe to call even if reset control is NULL + +**Example**: +```c +/* Take device out of reset */ +rt_reset_control_deassert(rst); + +/* Wait for hardware to be ready */ +rt_thread_mdelay(1); +``` + +#### rt_reset_control_status + +```c +int rt_reset_control_status(struct rt_reset_control *rstc); +``` + +Get the current status of a reset line. + +**Parameters**: +- `rstc`: Reset control pointer + +**Returns**: +- 1 if asserted (in reset) +- 0 if deasserted (not in reset) +- Negative error code on failure + +**Example**: +```c +int status = rt_reset_control_status(rst); +if (status > 0) { + LOG_W("Device is currently in reset"); +} +``` + +## Complete Application Example + +### Example: USB Driver with Reset Control + +```c +#include +#include +#include +#include +#include + +struct usb_device { + void *base; + int irq; + struct rt_clk *ahb_clk; + struct rt_clk *otg_clk; + struct rt_reset_control *ahb_rst; + struct rt_reset_control *phy_rst; +}; + +static rt_err_t usb_hw_init(struct usb_device *usb) +{ + rt_err_t ret; + + /* Enable clocks first */ + ret = rt_clk_prepare_enable(usb->ahb_clk); + if (ret != RT_EOK) { + LOG_E("Failed to enable AHB clock: %d", ret); + return ret; + } + + ret = rt_clk_prepare_enable(usb->otg_clk); + if (ret != RT_EOK) { + LOG_E("Failed to enable OTG clock: %d", ret); + goto err_disable_ahb; + } + + /* Deassert resets after clocks are enabled */ + ret = rt_reset_control_deassert(usb->ahb_rst); + if (ret != RT_EOK) { + LOG_E("Failed to deassert AHB reset: %d", ret); + goto err_disable_otg; + } + + ret = rt_reset_control_deassert(usb->phy_rst); + if (ret != RT_EOK) { + LOG_E("Failed to deassert PHY reset: %d", ret); + goto err_assert_ahb; + } + + /* Wait for hardware to stabilize */ + rt_thread_mdelay(10); + + return RT_EOK; + +err_assert_ahb: + rt_reset_control_assert(usb->ahb_rst); +err_disable_otg: + rt_clk_disable_unprepare(usb->otg_clk); +err_disable_ahb: + rt_clk_disable_unprepare(usb->ahb_clk); + return ret; +} + +static void usb_hw_deinit(struct usb_device *usb) +{ + /* Assert resets before disabling clocks */ + rt_reset_control_assert(usb->phy_rst); + rt_reset_control_assert(usb->ahb_rst); + + /* Disable clocks */ + rt_clk_disable_unprepare(usb->otg_clk); + rt_clk_disable_unprepare(usb->ahb_clk); +} + +static rt_err_t usb_probe(struct rt_platform_device *pdev) +{ + rt_err_t ret; + struct rt_device *dev = &pdev->parent; + struct usb_device *usb; + + /* Allocate device structure */ + usb = rt_calloc(1, sizeof(*usb)); + if (!usb) + return -RT_ENOMEM; + + /* Map MMIO region */ + usb->base = rt_dm_dev_iomap(dev, 0); + if (!usb->base) { + ret = -RT_ERROR; + goto err_free; + } + + /* Get IRQ */ + usb->irq = rt_dm_dev_get_irq(dev, 0); + if (usb->irq < 0) { + ret = usb->irq; + goto err_unmap; + } + + /* Get clocks */ + usb->ahb_clk = rt_clk_get_by_name(dev, "ahb"); + if (!usb->ahb_clk) { + LOG_E("Failed to get AHB clock"); + ret = -RT_ERROR; + goto err_unmap; + } + + usb->otg_clk = rt_clk_get_by_name(dev, "otg"); + if (!usb->otg_clk) { + LOG_E("Failed to get OTG clock"); + ret = -RT_ERROR; + goto err_put_ahb_clk; + } + + /* Get resets */ + usb->ahb_rst = rt_reset_control_get_by_name(dev, "ahb"); + if (!usb->ahb_rst) { + LOG_W("No AHB reset line"); + /* Not fatal - some platforms may not have it */ + } + + usb->phy_rst = rt_reset_control_get_by_name(dev, "phy"); + if (!usb->phy_rst) { + LOG_W("No PHY reset line"); + /* Not fatal */ + } + + /* Initialize hardware */ + ret = usb_hw_init(usb); + if (ret != RT_EOK) { + goto err_put_resets; + } + + /* Register USB controller */ + /* ... */ + + pdev->priv = usb; + LOG_I("USB device initialized"); + + return RT_EOK; + +err_put_resets: + if (usb->phy_rst) + rt_reset_control_put(usb->phy_rst); + if (usb->ahb_rst) + rt_reset_control_put(usb->ahb_rst); + rt_clk_put(usb->otg_clk); +err_put_ahb_clk: + rt_clk_put(usb->ahb_clk); +err_unmap: + rt_iounmap(usb->base); +err_free: + rt_free(usb); + return ret; +} + +static rt_err_t usb_remove(struct rt_platform_device *pdev) +{ + struct usb_device *usb = pdev->priv; + + /* Deinitialize hardware */ + usb_hw_deinit(usb); + + /* Release resources */ + if (usb->phy_rst) + rt_reset_control_put(usb->phy_rst); + if (usb->ahb_rst) + rt_reset_control_put(usb->ahb_rst); + rt_clk_put(usb->otg_clk); + rt_clk_put(usb->ahb_clk); + rt_iounmap(usb->base); + rt_free(usb); + + return RT_EOK; +} + +static const struct rt_ofw_node_id usb_ofw_ids[] = { + { .compatible = "vendor,usb-otg" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver usb_driver = { + .name = "usb-otg", + .ids = usb_ofw_ids, + .probe = usb_probe, + .remove = usb_remove, +}; + +RT_PLATFORM_DRIVER_EXPORT(usb_driver); +``` + +## Driver Implementation Guide + +### Key Structures + +#### rt_reset_controller + +```c +struct rt_reset_controller { + struct rt_object parent; + rt_list_t rstc_nodes; /* List of reset controls */ + + const char *name; /* Controller name */ + const struct rt_reset_control_ops *ops; /* Operations */ + + struct rt_ofw_node *ofw_node; /* Device tree node */ + void *priv; /* Private data */ + + struct rt_spinlock spinlock; /* Lock for thread safety */ +}; +``` + +#### rt_reset_control_ops + +```c +struct rt_reset_control_ops { + /* Parse device tree arguments */ + rt_err_t (*ofw_parse)(struct rt_reset_control *rstc, + struct rt_ofw_cell_args *args); + + /* Reset operations */ + rt_err_t (*reset)(struct rt_reset_control *rstc); + rt_err_t (*assert)(struct rt_reset_control *rstc); + rt_err_t (*deassert)(struct rt_reset_control *rstc); + int (*status)(struct rt_reset_control *rstc); +}; +``` + +### Example: Simple Reset Controller Driver + +```c +#include +#include +#include + +#define RESET_ASSERT 0x0 +#define RESET_DEASSERT 0x4 +#define RESET_STATUS 0x8 + +struct simple_reset { + struct rt_reset_controller rstcer; + void *base; + rt_uint32_t nr_resets; +}; + +static rt_err_t simple_reset_ofw_parse(struct rt_reset_control *rstc, + struct rt_ofw_cell_args *args) +{ + /* args->args[0] contains reset index */ + if (args->args_count != 1) { + return -RT_EINVAL; + } + + rstc->id = args->args[0]; + return RT_EOK; +} + +static rt_err_t simple_reset_assert(struct rt_reset_control *rstc) +{ + struct simple_reset *sr = rstc->rstcer->priv; + rt_uint32_t mask = RT_BIT(rstc->id); + + /* Set bit to assert reset */ + writel(readl(sr->base + RESET_ASSERT) | mask, + sr->base + RESET_ASSERT); + + return RT_EOK; +} + +static rt_err_t simple_reset_deassert(struct rt_reset_control *rstc) +{ + struct simple_reset *sr = rstc->rstcer->priv; + rt_uint32_t mask = RT_BIT(rstc->id); + + /* Set bit to deassert reset */ + writel(readl(sr->base + RESET_DEASSERT) | mask, + sr->base + RESET_DEASSERT); + + return RT_EOK; +} + +static int simple_reset_status(struct rt_reset_control *rstc) +{ + struct simple_reset *sr = rstc->rstcer->priv; + rt_uint32_t status = readl(sr->base + RESET_STATUS); + + /* Return 1 if in reset, 0 if not */ + return !!(status & RT_BIT(rstc->id)); +} + +static const struct rt_reset_control_ops simple_reset_ops = { + .ofw_parse = simple_reset_ofw_parse, + .assert = simple_reset_assert, + .deassert = simple_reset_deassert, + .status = simple_reset_status, +}; + +static rt_err_t simple_reset_probe(struct rt_platform_device *pdev) +{ + rt_err_t ret; + rt_uint32_t nr_resets; + struct rt_device *dev = &pdev->parent; + struct simple_reset *sr; + + /* Allocate driver structure */ + sr = rt_calloc(1, sizeof(*sr)); + if (!sr) + return -RT_ENOMEM; + + /* Map reset controller registers */ + sr->base = rt_dm_dev_iomap(dev, 0); + if (!sr->base) { + ret = -RT_ERROR; + goto err_free; + } + + /* Get number of reset lines */ + if (rt_dm_dev_prop_read_u32(dev, "reset-count", &nr_resets)) { + nr_resets = 32; /* Default */ + } + sr->nr_resets = nr_resets; + + /* Initialize reset controller */ + sr->rstcer.ops = &simple_reset_ops; + sr->rstcer.ofw_node = dev->ofw_node; + sr->rstcer.priv = sr; + + /* Register with framework */ + ret = rt_reset_controller_register(&sr->rstcer); + if (ret != RT_EOK) { + LOG_E("Failed to register reset controller: %d", ret); + goto err_unmap; + } + + pdev->priv = sr; + LOG_I("Simple reset controller registered: %u lines", nr_resets); + + return RT_EOK; + +err_unmap: + rt_iounmap(sr->base); +err_free: + rt_free(sr); + return ret; +} + +static const struct rt_ofw_node_id simple_reset_ofw_ids[] = { + { .compatible = "simple-reset" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver simple_reset_driver = { + .name = "simple-reset", + .ids = simple_reset_ofw_ids, + .probe = simple_reset_probe, +}; + +static int simple_reset_drv_register(void) +{ + rt_platform_driver_register(&simple_reset_driver); + return 0; +} +INIT_SUBSYS_EXPORT(simple_reset_drv_register); +``` + +## Best Practices + +### For Consumer Drivers + +1. **Check return values**: Reset operations can fail +2. **Order matters**: Enable clocks before deasserting resets +3. **Handle missing resets**: Not all platforms have reset lines (check for NULL) +4. **Wait after reset**: Allow hardware time to stabilize +5. **Reset during errors**: Use reset for error recovery +6. **Assert before power off**: Put devices in reset before disabling power + +### Common Patterns + +#### Initialization Sequence + +```c +/* Correct initialization order */ +rt_clk_prepare_enable(clk); /* 1. Enable clocks */ +rt_reset_control_deassert(rst); /* 2. Take out of reset */ +rt_thread_mdelay(10); /* 3. Wait for stability */ +/* Initialize hardware registers */ +``` + +#### Cleanup Sequence + +```c +/* Correct cleanup order (reverse of init) */ +/* Disable hardware */ +rt_reset_control_assert(rst); /* 1. Put into reset */ +rt_clk_disable_unprepare(clk); /* 2. Disable clocks */ +``` + +#### Error Recovery + +```c +/* Reset device on error */ +if (device_error_detected()) { + rt_reset_control_reset(rst); /* Pulse reset */ + rt_thread_mdelay(10); + reinitialize_device(); +} +``` + +## Troubleshooting + +### Common Issues + +1. **Reset not found** + - Check device tree: Ensure `resets` and `reset-names` properties exist + - Check compatible string: Verify reset driver is loaded + - Check Kconfig: Enable reset controller support + +2. **Device doesn't work after reset** + - Check clock order: Clocks must be enabled before deassert + - Check delay: Add delay after reset for hardware stabilization + - Check reset polarity: Verify assert/deassert are correct + +3. **System hangs during reset** + - Check clock status: Device clock must be running + - Check dependencies: Some resets depend on others + - Check power: Device power must be on + +## Performance Considerations + +### Memory Usage + +- Each reset controller: ~100 bytes +- Each reset control: ~40 bytes +- Minimal overhead + +### Timing + +- Reset operations: Usually fast (register writes) +- May need delays: For hardware stabilization +- No complex calculations + +## Related Modules + +- **clk**: Clock management, coordinate with resets +- **regulator**: Power supply, coordinate with resets +- **pinctrl**: Pin configuration, may need after reset +- **pmdomain**: Power domain, higher-level control + +## References + +- RT-Thread Source: `components/drivers/reset/` +- Header File: `components/drivers/include/drivers/reset.h` +- Device Tree Bindings: [Linux Reset Bindings](https://www.kernel.org/doc/Documentation/devicetree/bindings/reset/) +- [RT-Thread DM Documentation](../README.md) diff --git a/documentation/6.components/device-driver/device_driver_model/reset/README_zh.md b/documentation/6.components/device-driver/device_driver_model/reset/README_zh.md new file mode 100644 index 00000000000..b630e52fd7f --- /dev/null +++ b/documentation/6.components/device-driver/device_driver_model/reset/README_zh.md @@ -0,0 +1,727 @@ +# 复位控制器框架 + +## 简介 + +RT-Thread 的复位控制器框架为嵌入式系统中的硬件复位信号管理提供了标准化接口。复位控制器对于正确初始化、恢复和管理硬件外设及子系统的状态至关重要。 + +### 概述 + +复位信号对硬件管理至关重要: + +- **设备初始化**:在启动期间将设备置于已知状态 +- **错误恢复**:发生错误时复位设备 +- **电源管理**:进入/退出低功耗模式时复位设备 +- **热插拔支持**:为热插拔场景重新初始化设备 +- **系统稳定性**:确保干净的状态转换 + +常见的复位控制器类型包括: +- **基于寄存器**:单个寄存器控制多个复位 +- **基于 GPIO**:通过 GPIO 引脚控制的复位线 +- **电源管理 IC (PMIC)**:集成在电源管理芯片中的复位 +- **系统控制**:通过系统控制接口(SCMI)管理的复位 + +### RT-Thread 实现 + +RT-Thread 复位控制器框架位于 `components/drivers/reset/`,提供: + +1. **消费者 API**:设备驱动程序控制复位线的简单接口 +2. **提供者 API**:实现复位控制器驱动程序的框架 +3. **设备树集成**:从 FDT 自动配置 +4. **复位操作**:断言、解除断言和脉冲复位信号 +5. **数组支持**:管理设备的多条复位线 +6. **状态查询**:检查复位线状态 + +## Kconfig 配置 + +### 主配置 + +```kconfig +menuconfig RT_USING_RESET + bool "Using Reset Controller support" + depends on RT_USING_DM + depends on RT_USING_OFW + default n +``` + +**在 menuconfig 中的位置**: +``` +RT-Thread Components → Device Drivers → Using Reset Controller support +``` + +**依赖项**: +- `RT_USING_DM`:必须首先启用 +- `RT_USING_OFW`:需要设备树支持 + +**默认值**:禁用(选择加入功能) + +### 复位驱动选项 + +#### SCMI 复位驱动 +```kconfig +config RT_RESET_SCMI + bool "Reset driver controlled via ARM SCMI interface" + depends on RT_USING_RESET + depends on RT_FIRMWARE_ARM_SCMI + default n +``` + +支持通过 ARM 系统控制和管理接口(SCMI)控制的复位控制器。 + +#### 简单复位驱动 +```kconfig +config RT_RESET_SIMPLE + bool "Simple Reset Controller Driver" + depends on RT_USING_RESET + default n +``` + +支持基本的基于寄存器的复位控制器,具有简单的位操作。 + +## 设备树绑定 + +### 复位控制器属性 + +复位控制器使用以下方式导出复位: + +```dts +#reset-cells = ; /* 复位说明符中的单元数 */ +``` + +单元数通常表示标识特定复位线所需的值数量(通常为 1)。 + +### 复位消费者属性 + +设备使用以下方式引用复位线: + +```dts +resets = <&rstc idx>; /* 复位控制器 phandle 和索引 */ +reset-names = "name"; /* 复位线名称 */ +``` + +### 简单复位控制器示例 + +```dts +soc { + /* 复位控制器节点 */ + rstc: reset-controller@1c20000 { + compatible = "vendor,reset-controller", "reset-simple"; + reg = <0x1c20000 0x100>; + #reset-cells = <1>; + }; +}; +``` + +### 消费者使用示例 + +```dts +/* 单复位消费者 */ +uart0: serial@1c28000 { + compatible = "vendor,uart"; + reg = <0x1c28000 0x400>; + interrupts = <0 0 4>; + + clocks = <&ccu 64>; + resets = <&rstc 32>; /* 复位索引 32 */ + reset-names = "uart"; + + status = "okay"; +}; + +/* 多复位消费者 */ +usb0: usb@1c19000 { + compatible = "vendor,usb-otg"; + reg = <0x1c19000 0x1000>; + interrupts = <0 32 4>; + + clocks = <&ccu 24>, <&ccu 25>; + clock-names = "ahb", "otg"; + + resets = <&rstc 11>, <&rstc 12>; + reset-names = "ahb", "phy"; + + status = "okay"; +}; +``` + +## 应用层 API + +### 概述 + +消费者 API 为设备驱动程序提供简单的函数来控制复位线。复位操作通常在设备初始化和错误恢复期间执行。 + +### 获取和释放复位控制 + +#### rt_reset_control_get_by_name + +```c +struct rt_reset_control *rt_reset_control_get_by_name(struct rt_device *dev, + const char *name); +``` + +通过名称获取复位控制。 + +**参数**: +- `dev`:设备结构指针 +- `name`:复位线名称(匹配设备树中的 `reset-names`) + +**返回值**: +- 成功时返回复位控制指针 +- 失败时返回 NULL + +**示例**: +```c +struct rt_reset_control *rst; + +/* 获取 "phy" 复位线 */ +rst = rt_reset_control_get_by_name(dev, "phy"); +if (!rst) { + LOG_W("没有 phy 复位线"); + /* 不致命 - 某些板可能没有 */ +} +``` + +#### rt_reset_control_get_by_index + +```c +struct rt_reset_control *rt_reset_control_get_by_index(struct rt_device *dev, + int index); +``` + +通过索引获取复位控制。 + +**参数**: +- `dev`:设备结构指针 +- `index`:`resets` 属性中的复位索引(从 0 开始) + +**返回值**: +- 成功时返回复位控制指针 +- 失败时返回 NULL + +#### rt_reset_control_get_array + +```c +struct rt_reset_control *rt_reset_control_get_array(struct rt_device *dev); +``` + +获取设备的所有复位控制作为数组。 + +**参数**: +- `dev`:设备结构指针 + +**返回值**: +- 成功时返回复位控制数组指针 +- 失败时返回 NULL + +#### rt_reset_control_put + +```c +void rt_reset_control_put(struct rt_reset_control *rstc); +``` + +释放复位控制。 + +**参数**: +- `rstc`:复位控制指针 + +### 复位操作 + +#### rt_reset_control_reset + +```c +rt_err_t rt_reset_control_reset(struct rt_reset_control *rstc); +``` + +执行复位脉冲:断言然后解除断言。 + +**参数**: +- `rstc`:复位控制指针 + +**返回值**: +- 成功时返回 `RT_EOK` +- 失败时返回错误代码 + +**注意**: +- 这是最常见的操作 +- 自动断言然后以适当的延迟解除断言 +- 即使复位控制为 NULL 也安全调用 + +**示例**: +```c +/* 在初始化期间复位外设 */ +ret = rt_reset_control_reset(rst); +if (ret != RT_EOK) { + LOG_E("复位外设失败: %d", ret); + return ret; +} + +/* 等待硬件稳定 */ +rt_thread_mdelay(10); +``` + +#### rt_reset_control_assert + +```c +rt_err_t rt_reset_control_assert(struct rt_reset_control *rstc); +``` + +断言(激活)复位信号。 + +**参数**: +- `rstc`:复位控制指针 + +**返回值**: +- 成功时返回 `RT_EOK` +- 失败时返回错误代码 + +**注意**: +- 将设备置于复位状态 +- 必须跟随解除断言以恢复操作 +- 即使复位控制为 NULL 也安全调用 + +**示例**: +```c +/* 在断电前将设备置于复位状态 */ +rt_reset_control_assert(rst); +``` + +#### rt_reset_control_deassert + +```c +rt_err_t rt_reset_control_deassert(struct rt_reset_control *rstc); +``` + +解除断言(停用)复位信号。 + +**参数**: +- `rstc`:复位控制指针 + +**返回值**: +- 成功时返回 `RT_EOK` +- 失败时返回错误代码 + +**注意**: +- 将设备从复位状态中取出 +- 应在断言之后调用 +- 即使复位控制为 NULL 也安全调用 + +**示例**: +```c +/* 将设备从复位中取出 */ +rt_reset_control_deassert(rst); + +/* 等待硬件准备好 */ +rt_thread_mdelay(1); +``` + +#### rt_reset_control_status + +```c +int rt_reset_control_status(struct rt_reset_control *rstc); +``` + +获取复位线的当前状态。 + +**参数**: +- `rstc`:复位控制指针 + +**返回值**: +- 如果已断言(处于复位状态)返回 1 +- 如果已解除断言(不处于复位状态)返回 0 +- 失败时返回负错误代码 + +## 完整应用示例 + +### 示例:带复位控制的 USB 驱动 + +```c +#include +#include +#include +#include + +struct usb_device { + void *base; + int irq; + struct rt_clk *ahb_clk; + struct rt_clk *otg_clk; + struct rt_reset_control *ahb_rst; + struct rt_reset_control *phy_rst; +}; + +static rt_err_t usb_hw_init(struct usb_device *usb) +{ + rt_err_t ret; + + /* 首先启用时钟 */ + ret = rt_clk_prepare_enable(usb->ahb_clk); + if (ret != RT_EOK) { + LOG_E("启用 AHB 时钟失败: %d", ret); + return ret; + } + + ret = rt_clk_prepare_enable(usb->otg_clk); + if (ret != RT_EOK) { + LOG_E("启用 OTG 时钟失败: %d", ret); + goto err_disable_ahb; + } + + /* 启用时钟后解除断言复位 */ + ret = rt_reset_control_deassert(usb->ahb_rst); + if (ret != RT_EOK) { + LOG_E("解除断言 AHB 复位失败: %d", ret); + goto err_disable_otg; + } + + ret = rt_reset_control_deassert(usb->phy_rst); + if (ret != RT_EOK) { + LOG_E("解除断言 PHY 复位失败: %d", ret); + goto err_assert_ahb; + } + + /* 等待硬件稳定 */ + rt_thread_mdelay(10); + + return RT_EOK; + +err_assert_ahb: + rt_reset_control_assert(usb->ahb_rst); +err_disable_otg: + rt_clk_disable_unprepare(usb->otg_clk); +err_disable_ahb: + rt_clk_disable_unprepare(usb->ahb_clk); + return ret; +} + +static void usb_hw_deinit(struct usb_device *usb) +{ + /* 在禁用时钟之前断言复位 */ + rt_reset_control_assert(usb->phy_rst); + rt_reset_control_assert(usb->ahb_rst); + + /* 禁用时钟 */ + rt_clk_disable_unprepare(usb->otg_clk); + rt_clk_disable_unprepare(usb->ahb_clk); +} + +static rt_err_t usb_probe(struct rt_platform_device *pdev) +{ + rt_err_t ret; + struct rt_device *dev = &pdev->parent; + struct usb_device *usb; + + /* 分配设备结构 */ + usb = rt_calloc(1, sizeof(*usb)); + if (!usb) + return -RT_ENOMEM; + + /* 映射 MMIO 区域 */ + usb->base = rt_dm_dev_iomap(dev, 0); + if (!usb->base) { + ret = -RT_ERROR; + goto err_free; + } + + /* 获取 IRQ */ + usb->irq = rt_dm_dev_get_irq(dev, 0); + + /* 获取时钟 */ + usb->ahb_clk = rt_clk_get_by_name(dev, "ahb"); + if (!usb->ahb_clk) { + LOG_E("获取 AHB 时钟失败"); + ret = -RT_ERROR; + goto err_unmap; + } + + usb->otg_clk = rt_clk_get_by_name(dev, "otg"); + if (!usb->otg_clk) { + LOG_E("获取 OTG 时钟失败"); + ret = -RT_ERROR; + goto err_put_ahb_clk; + } + + /* 获取复位 */ + usb->ahb_rst = rt_reset_control_get_by_name(dev, "ahb"); + if (!usb->ahb_rst) { + LOG_W("没有 AHB 复位线"); + /* 不致命 - 某些平台可能没有 */ + } + + usb->phy_rst = rt_reset_control_get_by_name(dev, "phy"); + if (!usb->phy_rst) { + LOG_W("没有 PHY 复位线"); + /* 不致命 */ + } + + /* 初始化硬件 */ + ret = usb_hw_init(usb); + if (ret != RT_EOK) { + goto err_put_resets; + } + + pdev->priv = usb; + LOG_I("USB 设备已初始化"); + + return RT_EOK; + +err_put_resets: + if (usb->phy_rst) + rt_reset_control_put(usb->phy_rst); + if (usb->ahb_rst) + rt_reset_control_put(usb->ahb_rst); + rt_clk_put(usb->otg_clk); +err_put_ahb_clk: + rt_clk_put(usb->ahb_clk); +err_unmap: + rt_iounmap(usb->base); +err_free: + rt_free(usb); + return ret; +} +``` + +## 驱动实现指南 + +### 核心结构 + +#### rt_reset_control_ops + +```c +struct rt_reset_control_ops { + /* 解析设备树参数 */ + rt_err_t (*ofw_parse)(struct rt_reset_control *rstc, + struct rt_ofw_cell_args *args); + + /* 复位操作 */ + rt_err_t (*reset)(struct rt_reset_control *rstc); + rt_err_t (*assert)(struct rt_reset_control *rstc); + rt_err_t (*deassert)(struct rt_reset_control *rstc); + int (*status)(struct rt_reset_control *rstc); +}; +``` + +### 示例:简单复位控制器驱动 + +```c +#include +#include +#include + +#define RESET_ASSERT 0x0 +#define RESET_DEASSERT 0x4 +#define RESET_STATUS 0x8 + +struct simple_reset { + struct rt_reset_controller rstcer; + void *base; + rt_uint32_t nr_resets; +}; + +static rt_err_t simple_reset_ofw_parse(struct rt_reset_control *rstc, + struct rt_ofw_cell_args *args) +{ + /* args->args[0] 包含复位索引 */ + if (args->args_count != 1) { + return -RT_EINVAL; + } + + rstc->id = args->args[0]; + return RT_EOK; +} + +static rt_err_t simple_reset_assert(struct rt_reset_control *rstc) +{ + struct simple_reset *sr = rstc->rstcer->priv; + rt_uint32_t mask = RT_BIT(rstc->id); + + /* 设置位以断言复位 */ + writel(readl(sr->base + RESET_ASSERT) | mask, + sr->base + RESET_ASSERT); + + return RT_EOK; +} + +static rt_err_t simple_reset_deassert(struct rt_reset_control *rstc) +{ + struct simple_reset *sr = rstc->rstcer->priv; + rt_uint32_t mask = RT_BIT(rstc->id); + + /* 设置位以解除断言复位 */ + writel(readl(sr->base + RESET_DEASSERT) | mask, + sr->base + RESET_DEASSERT); + + return RT_EOK; +} + +static int simple_reset_status(struct rt_reset_control *rstc) +{ + struct simple_reset *sr = rstc->rstcer->priv; + rt_uint32_t status = readl(sr->base + RESET_STATUS); + + /* 如果处于复位状态返回 1,否则返回 0 */ + return !!(status & RT_BIT(rstc->id)); +} + +static const struct rt_reset_control_ops simple_reset_ops = { + .ofw_parse = simple_reset_ofw_parse, + .assert = simple_reset_assert, + .deassert = simple_reset_deassert, + .status = simple_reset_status, +}; + +static rt_err_t simple_reset_probe(struct rt_platform_device *pdev) +{ + rt_err_t ret; + rt_uint32_t nr_resets; + struct rt_device *dev = &pdev->parent; + struct simple_reset *sr; + + /* 分配驱动结构 */ + sr = rt_calloc(1, sizeof(*sr)); + if (!sr) + return -RT_ENOMEM; + + /* 映射复位控制器寄存器 */ + sr->base = rt_dm_dev_iomap(dev, 0); + if (!sr->base) { + ret = -RT_ERROR; + goto err_free; + } + + /* 获取复位线数量 */ + if (rt_dm_dev_prop_read_u32(dev, "reset-count", &nr_resets)) { + nr_resets = 32; /* 默认值 */ + } + sr->nr_resets = nr_resets; + + /* 初始化复位控制器 */ + sr->rstcer.ops = &simple_reset_ops; + sr->rstcer.ofw_node = dev->ofw_node; + sr->rstcer.priv = sr; + + /* 向框架注册 */ + ret = rt_reset_controller_register(&sr->rstcer); + if (ret != RT_EOK) { + LOG_E("注册复位控制器失败: %d", ret); + goto err_unmap; + } + + pdev->priv = sr; + LOG_I("简单复位控制器已注册: %u 线", nr_resets); + + return RT_EOK; + +err_unmap: + rt_iounmap(sr->base); +err_free: + rt_free(sr); + return ret; +} + +static const struct rt_ofw_node_id simple_reset_ofw_ids[] = { + { .compatible = "simple-reset" }, + { /* sentinel */ } +}; + +static struct rt_platform_driver simple_reset_driver = { + .name = "simple-reset", + .ids = simple_reset_ofw_ids, + .probe = simple_reset_probe, +}; + +static int simple_reset_drv_register(void) +{ + rt_platform_driver_register(&simple_reset_driver); + return 0; +} +INIT_SUBSYS_EXPORT(simple_reset_drv_register); +``` + +## 最佳实践 + +### 对于消费者驱动程序 + +1. **检查返回值**:复位操作可能失败 +2. **顺序很重要**:在解除断言复位之前启用时钟 +3. **处理缺失的复位**:并非所有平台都有复位线(检查 NULL) +4. **复位后等待**:让硬件有时间稳定 +5. **错误时复位**:使用复位进行错误恢复 +6. **断电前断言**:在禁用电源之前将设备置于复位状态 + +### 常见模式 + +#### 初始化序列 + +```c +/* 正确的初始化顺序 */ +rt_clk_prepare_enable(clk); /* 1. 启用时钟 */ +rt_reset_control_deassert(rst); /* 2. 从复位中取出 */ +rt_thread_mdelay(10); /* 3. 等待稳定 */ +/* 初始化硬件寄存器 */ +``` + +#### 清理序列 + +```c +/* 正确的清理顺序(初始化的相反顺序)*/ +/* 禁用硬件 */ +rt_reset_control_assert(rst); /* 1. 置于复位状态 */ +rt_clk_disable_unprepare(clk); /* 2. 禁用时钟 */ +``` + +#### 错误恢复 + +```c +/* 发生错误时复位设备 */ +if (device_error_detected()) { + rt_reset_control_reset(rst); /* 脉冲复位 */ + rt_thread_mdelay(10); + reinitialize_device(); +} +``` + +## 故障排除 + +### 常见问题 + +1. **找不到复位** + - 检查设备树:确保 `resets` 和 `reset-names` 属性存在 + - 检查 compatible 字符串:验证复位驱动已加载 + - 检查 Kconfig:启用复位控制器支持 + +2. **复位后设备不工作** + - 检查时钟顺序:解除断言前必须启用时钟 + - 检查延迟:复位后添加延迟以便硬件稳定 + - 检查复位极性:验证断言/解除断言是否正确 + +3. **复位期间系统挂起** + - 检查时钟状态:设备时钟必须运行 + - 检查依赖关系:某些复位依赖于其他复位 + - 检查电源:设备电源必须打开 + +## 性能考虑 + +### 内存使用 + +- 每个复位控制器:约 100 字节 +- 每个复位控制:约 40 字节 +- 开销最小 + +### 时序 + +- 复位操作:通常很快(寄存器写入) +- 可能需要延迟:用于硬件稳定 +- 无复杂计算 + +## 相关模块 + +- **clk**:时钟管理,与复位协调 +- **regulator**:电源,与复位协调 +- **pinctrl**:引脚配置,复位后可能需要 +- **pmdomain**:电源域,更高级别的控制 + +## 参考资料 + +- RT-Thread 源代码:`components/drivers/reset/` +- 头文件:`components/drivers/include/drivers/reset.h` +- 设备树绑定:[Linux Reset Bindings](https://www.kernel.org/doc/Documentation/devicetree/bindings/reset/) +- [RT-Thread DM 文档](../README_zh.md)