diff --git a/documentation/6.components/device-driver/INDEX.md b/documentation/6.components/device-driver/INDEX.md index b42844dbc94..0994df23f72 100644 --- a/documentation/6.components/device-driver/INDEX.md +++ b/documentation/6.components/device-driver/INDEX.md @@ -2,15 +2,110 @@ - @subpage page_device_framework - @subpage page_device_pin +- @subpage page_device_pin_dm +- @subpage page_device_pinctrl - @subpage page_device_uart +- @subpage page_device_uart_dm +- @subpage page_device_uart_earlycon - @subpage page_device_adc - @subpage page_device_i2c +- @subpage page_device_i2c_dm - @subpage page_device_spi +- @subpage page_device_spi_dm - @subpage page_device_pwm - @subpage page_device_rtc +- @subpage page_device_rtc_dm - @subpage page_device_clock_time +- @subpage page_device_hwtimer - @subpage page_device_watchdog - @subpage page_device_wlan - @subpage page_device_sensor - @subpage page_device_audio +- @subpage page_device_ofw +- @subpage page_device_ofw_base +- @subpage page_device_ofw_boot +- @subpage page_device_ofw_io +- @subpage page_device_ofw_irq +- @subpage page_device_ofw_raw +- @subpage page_device_ofw_platform - @subpage page_device_dtc +- @subpage page_device_ata +- @subpage page_device_blk +- @subpage page_device_disk +- @subpage page_device_partitions +- @subpage page_device_hwcache +- @subpage page_device_can +- @subpage page_device_can_dm +- @subpage page_device_clk +- @subpage page_device_clk_fixed +- @subpage page_device_bus +- @subpage page_device_dm +- @subpage page_device_dm_power +- @subpage page_device_dma +- @subpage page_device_dma_pool +- @subpage page_device_graphic_dm +- @subpage page_device_graphic_backlight +- @subpage page_device_graphic_framebuffer +- @subpage page_device_graphic_logo +- @subpage page_device_hwspinlock +- @subpage page_device_iio +- @subpage page_device_iio_dm +- @subpage page_device_input +- @subpage page_device_input_dm +- @subpage page_device_input_uapi +- @subpage page_device_input_touch +- @subpage page_device_input_power +- @subpage page_device_led +- @subpage page_device_led_dm +- @subpage page_device_mailbox +- @subpage page_device_mailbox_dm +- @subpage page_device_numa +- @subpage page_device_nvme +- @subpage page_device_nvme_dm +- @subpage page_device_nvmem +- @subpage page_device_nvmem_dm +- @subpage page_device_pci +- @subpage page_device_pci_probe +- @subpage page_device_pci_access +- @subpage page_device_pci_host +- @subpage page_device_pci_irq +- @subpage page_device_pci_pme +- @subpage page_device_pci_msi +- @subpage page_device_pci_ofw +- @subpage page_device_pci_bridge +- @subpage page_device_pci_endpoint +- @subpage page_device_phye +- @subpage page_device_phye_core +- @subpage page_device_phye_ofw +- @subpage page_device_phye_provider +- @subpage page_device_phye_consumer +- @subpage page_device_phye_generic_usb +- @subpage page_device_pic +- @subpage page_device_pic_irq_domain +- @subpage page_device_pic_core +- @subpage page_device_pic_cascade +- @subpage page_device_pic_ofw +- @subpage page_device_pic_msi +- @subpage page_device_pic_examples +- @subpage page_device_platform +- @subpage page_device_power +- @subpage page_device_power_supply +- @subpage page_device_power_charger +- @subpage page_device_power_board_reset +- @subpage page_device_power_domain +- @subpage page_device_regulator +- @subpage page_device_reset +- @subpage page_device_rpmsg +- @subpage page_device_rpmsg_char +- @subpage page_device_rpmsg_ns +- @subpage page_device_scmi +- @subpage page_device_scmi_agent +- @subpage page_device_scsi +- @subpage page_device_sdio +- @subpage page_device_sdio_dm +- @subpage page_device_sdio_regulator +- @subpage page_device_sdhci +- @subpage page_device_syscon +- @subpage page_device_thermal +- @subpage page_device_thermal_cool +- @subpage page_device_ufs diff --git a/documentation/6.components/device-driver/ata/ata.md b/documentation/6.components/device-driver/ata/ata.md new file mode 100644 index 00000000000..d87e2eb303b --- /dev/null +++ b/documentation/6.components/device-driver/ata/ata.md @@ -0,0 +1,118 @@ +@page page_device_ata ATA / AHCI + +# AHCI host abstraction (`ahci.h`) + +This page documents **`struct rt_ahci_host`**, **`struct rt_ahci_ops`**, and per-port **`struct rt_ahci_port`** in **`drivers/ahci.h`**, as driven by **`rt_ahci_host_register` / `rt_ahci_host_unregister`** in `components/drivers/ata/ahci.c`. + +## `struct rt_ahci_host` (caller fills before `rt_ahci_host_register`) + +```368:383:components/drivers/include/drivers/ahci.h +struct rt_ahci_host +{ + struct rt_scsi_host parent; + + int irq; + void *regs; + + rt_size_t ports_nr; + rt_uint32_t ports_map; + struct rt_ahci_port ports[32]; + + rt_uint32_t cap; + rt_uint32_t max_blocks; + + const struct rt_ahci_ops *ops; +}; +``` + +| Member | Owner | Meaning | +| --- | --- | --- | +| `parent` | **Caller** | Embedded **`rt_scsi_host`**; **`parent.dev` must be non-NULL** (used for `rt_dma_alloc_coherent`, etc.). | +| `irq` | **Caller** | Global host IRQ; the core installs the ISR with `struct rt_ahci_host *` as parameter. | +| `regs` | **Caller** | Virtual base of the HBA (AHCI 1.0) register block. | +| `ports_nr` | **Core** | Filled from **`CAP.NP`**; do not rely on the initial value. | +| `ports_map` | **Core (DT may override)** | Read from **`PI`**; if the node has **`ports-implemented`**, that property wins. | +| `ports[]` | **Core + ops** | See **Per-port state** below. | +| `cap` | **Core** | Read from **`RT_AHCI_HBA_CAP`** (masked); used with SG paths for **64-bit DMA** (**`RT_AHCI_CAP_64`**, etc.). | +| `max_blocks` | **Caller (may be 0)** | Max blocks per SCSI split; **0** is replaced with **`0x80`** at register time. | +| `ops` | **Caller** | **Required**; every callback is optional (**`NULL`** = core default), but failures in **`host_init` / `port_init` / `port_dma_init`** skip the port or log DMA errors. | + +## `struct rt_ahci_port` (fields visible to `ops`) + +```346:366:components/drivers/include/drivers/ahci.h +struct rt_ahci_port +{ + void *regs; + + void *dma; + rt_ubase_t dma_handle; + + struct rt_ahci_cmd_hdr *cmd_slot; + struct rt_ahci_sg *cmd_tbl_sg; + void *cmd_tbl; + rt_ubase_t cmd_tbl_dma; + void *rx_fis; + + rt_uint32_t int_enabled; + rt_size_t block_size; + + rt_uint16_t *ataid; + + rt_bool_t link; + struct rt_completion done; +}; +``` + +| Member | Owner | Meaning | +| --- | --- | --- | +| `regs` | **Core** | `host->regs + 0x100 + port_index * 0x80`; valid before **`port_init`**. | +| `dma`, `dma_handle`, `cmd_slot`, `rx_fis`, `cmd_tbl`, `cmd_tbl_dma`, `cmd_tbl_sg` | **Core** | After **`link == RT_TRUE`**, **`RT_AHCI_DMA_SIZE`** coherent memory is allocated and laid out; **`CLB`/`CLBU`**, **`FB`/`FBU`** programmed. | +| `int_enabled` | **Core** | **`RT_AHCI_PORT_INTE_*`** mask written to **`PORT_INTE`** after DMA engines start. | +| `block_size` | **Core** | **512** or **2048** from Identify / Inquiry and **SIG** / **ATAPI**. | +| `ataid` | **Core** | Identify buffer; used with **`rt_ahci_ata_id_*`** helpers. | +| `link` | **Core** | **`RT_TRUE`** when link is up; ISR calls **`port_isr`** and **`rt_completion_done`** only if **`link`**. | +| `done` | **Core** | **`rt_completion_init`**; command path **`rt_completion_wait(&port->done, …)`** after **`PORT_CI`**; **`port_isr`** must stay consistent with **`done`**. | + +In **`port_init` / `port_dma_init`** you may touch **`port->regs`** and other HW state not yet filled by the core. **Do not assume `dma` exists inside `port_init`** (DMA is allocated only after a successful link). + +## `struct rt_ahci_ops` (order inside `rt_ahci_host_register`) + +```385:392:components/drivers/include/drivers/ahci.h +struct rt_ahci_ops +{ + rt_err_t (*host_init)(struct rt_ahci_host *host); + rt_err_t (*port_init)(struct rt_ahci_host *host, struct rt_ahci_port *port); + rt_err_t (*port_link_up)(struct rt_ahci_host *host, struct rt_ahci_port *port); + rt_err_t (*port_dma_init)(struct rt_ahci_host *host, struct rt_ahci_port *port); + rt_err_t (*port_isr)(struct rt_ahci_host *host, struct rt_ahci_port *port, rt_uint32_t isr); +}; +``` + +| Callback | When | Role | +| --- | --- | --- | +| **`host_init`** | After HBA soft reset, **`GHC.AHCI_EN`**, **CAP** read/mask, **`PI` write**, before port enumeration. | Optional. HBA-level, clocks, PHY, vendor regs; non-**`RT_EOK`** fails **`rt_ahci_host_register`**. | +| **`port_init`** | After **`PORT_CMD`** has **LIST_ON/FIS_ON/START/FIS_RX** cleared and the port has quiesced, before **SPIN_UP**. | Optional. Port reset, **`SCTL`**, non-standard **SIG**; **`port->regs`** valid. Failure **`continue`**s the port. | +| **`port_link_up`** | After **`port_init`** and the core has set **SPIN_UP**. | Optional. If **`NULL`**, core polls **`SSTS.DET`** for **`PHYRDY`**. Must return **`RT_EOK`** when the link is usable. | +| **`port_dma_init`** | After **`CLB`/`FB`** and DMA buffers are programmed, before the core writes **`PORT_CMD`** (ACTIVE, FIS_RX, POWER_ON, SPIN_UP, START). | Optional. Extra SoC DMA/port setup; errors are logged but the core still asserts **`PORT_CMD`**. | +| **`port_isr`** | Global ISR: bit set in **`HBA_INTS`**, **`port->link`**, **`PORT_INTS`** read into **`isr`**. | Optional. SoC-specific IRQ handling; core always **`rt_completion_done(&port->done)`** and clears **`PORT_INTS`**. | + +### Common integration pitfalls + +- **`port_init` vs DMA**: **`port->dma` is not allocated yet** inside **`port_init`**—only touch **`port->regs`** and PHY-level setup there. +- **`port_dma_init` errors are logged only**: the core still enables **`PORT_CMD`**—if your SoC cannot run without extra setup, fail earlier in **`port_link_up`** instead. +- **`port_isr`**: if you override, ensure you still cooperate with **`rt_completion_done`** semantics expected by **`ahci.c`** command path, or commands hang. +- **64-bit DMA**: honor **`RT_AHCI_CAP_64`** when building PRDT—SG list addresses must match HBA capability. + +## Registration API + +| API | Role | +| --- | --- | +| **`rt_ahci_host_register(host)`** | Validates **`host`**, **`host->parent.dev`**, **`host->ops`**; HBA reset, ports, link, DMA, **`rt_scsi_host_register`**, IRQ. | +| **`rt_ahci_host_unregister(host)`** | Masks IRQ, frees per-port DMA, unregisters the SCSI host. | + +## See also + +- `components/drivers/include/drivers/ahci.h` (**`RT_AHCI_*`**, **`struct rt_ahci_cmd_hdr`**, **`struct rt_ahci_sg`**) +- `components/drivers/ata/ahci.c` + +**Same documentation pattern** (fill structs + `ops` + register order): `documentation/6.components/device-driver/ufs/ufs.md`, `nvme/nvme.md`, `scsi/scsi.md` (section 1), `block/disk.md`, `dma/dma.md` (section 1). diff --git a/documentation/6.components/device-driver/block/blk.md b/documentation/6.components/device-driver/block/blk.md new file mode 100644 index 00000000000..0cf12417adb --- /dev/null +++ b/documentation/6.components/device-driver/block/blk.md @@ -0,0 +1,272 @@ +@page page_device_blk Block layer (`blk`) + +# Block storage subsystem + +The **blk** layer turns a single physical storage backend into one or more **RT-Thread block devices** that applications and **DFS** can open, read/write by **sector**, and control with standard IOCTLs. Hardware drivers implement **`struct rt_blk_disk_ops`** on an embedded **`struct rt_blk_disk`**; the core registers the disk, scans partition tables, and creates child volumes (**`struct rt_blk_device`**) such as **`sda1`** or **`mmcsd0p1`**. + +Header: `components/drivers/include/drivers/blk.h`. IOCTLs and geometry: `components/drivers/include/drivers/classes/block.h`. Core: `components/drivers/block/blk.c`, `blk_dev.c`, `blk_partition.c`. + +## Architecture + +``` + [NVMe / SDIO / SCSI SD / … driver] + │ rt_blk_disk_ops (read/write/getgeome/…) + ▼ + struct rt_blk_disk ← rt_hw_blk_disk_register() + │ rt_blk_disk_probe_partition() (inside register) + ▼ + struct rt_blk_device × N ← disk_add_blk_dev() / blk_dev.c + │ rt_device_read/write (sector-relative) + ▼ + DFS / applications / POSIX devio +``` + +| Layer | Struct | Document | +| --- | --- | --- | +| **Physical disk** | `struct rt_blk_disk` | @ref page_device_disk | +| **Partition table** | (on-disk metadata) | @ref page_device_partitions | +| **Volume (partition)** | `struct rt_blk_device` | **This page** — application-facing nodes | + +Both disk and volume nodes use **`RT_Device_Class_Block`**. Applications normally open **volumes** (`sda1`), not the raw disk device, unless probing created a single full-disk volume with **`partno == 0`**. + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_BLK`** | Master switch for `components/drivers/block/` | +| **`RT_BLK_PARTITION_EFI`** | GPT / protective MBR parser (`partitions/efi.c`) | +| **`RT_BLK_PARTITION_DFS`** | Legacy DFS superblock layout on sector 0 (`partitions/dfs.c`, needs **`RT_USING_DFS`**) | + +**`rt_hw_blk_disk_register`** always calls **`rt_blk_disk_probe_partition`**; partition scan errors are **ignored** (register still succeeds). + +--- + +# Access block devices (applications) + +Same usage model as other char/block devices: **find → open → read/write → control → close**. Sector index is **0-based within the opened device** (for a partition device, sector 0 is the start of that partition). + +## Find device + +```c +rt_device_t rt_device_find(const char *name); +``` + +| Parameter | Description | +| --- | --- | +| `name` | Volume name, e.g. **`sda0`**, **`sda1`**, **`mmcsd0`**, **`nvme0n1`** (from DM naming — see @ref page_device_disk) | +| **Return** | Device handle, or **`RT_NULL`** if not found | + +```c +#define BLK_DEV_NAME "sda1" +rt_device_t blk = rt_device_find(BLK_DEV_NAME); +``` + +## Open / close + +```c +rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflag); +rt_err_t rt_device_close(rt_device_t dev); +``` + +| `oflag` | Notes | +| --- | --- | +| **`RT_DEVICE_OFLAG_RDONLY`** | Always valid | +| **`RT_DEVICE_OFLAG_WRONLY`** | Fails on disk/volume if backend is read-only (**`ops->write == NULL`** sets **`read_only`**) | + +Opening a **partition** device (`rt_blk_device`) internally **opens the parent disk** (`blk_dev_open` → `rt_device_open(&blk->disk->parent)`). + +## Read / write (sectors) + +```c +rt_ssize_t rt_device_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size); +rt_ssize_t rt_device_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size); +``` + +For block class devices, **`pos`** is the **starting sector number**, **`size`** is **sector count** (not bytes). + +| Parameter | Partition volume | Whole disk | +| --- | --- | --- | +| `pos` | 0 … `sector_count - 1` (relative to partition) | 0 … disk capacity − 1 | +| `size` | Sectors to transfer | Same | +| **Return** | Sectors actually transferred, or negative error | Same | + +```c +#define SECTOR_SIZE 512 +rt_uint8_t buf[SECTOR_SIZE]; + +if (rt_device_read(blk, 0, buf, 1) == 1) +{ + /* one sector at LBA 0 of this partition */ +} +``` + +## Control (IOCTL) + +```c +rt_err_t rt_device_control(rt_device_t dev, int cmd, void *args); +``` + +### Standard commands (`classes/block.h`) + +| Command | `args` type | Partition volume | Raw disk | +| --- | --- | --- | --- | +| **`RT_DEVICE_CTRL_BLK_GETGEOME`** | `struct rt_device_blk_geometry *` | **`sector_count`** = partition size; **`bytes_per_sector`** from disk | Full disk geometry | +| **`RT_DEVICE_CTRL_BLK_SYNC`** | ignored | Flushes parent disk | **`ops->sync`** under lock | +| **`RT_DEVICE_CTRL_BLK_ERASE`** | ignored | Only if **`disk->partitions <= 1`**; else **`-RT_EIO`** | May remove all partitions then **`ops->erase`** | +| **`RT_DEVICE_CTRL_BLK_AUTOREFRESH`** | `rt_bool_t *` / pointer treated as enable flag | Delegates to disk if single partition | **`ops->autorefresh`** | +| **`RT_DEVICE_CTRL_BLK_PARTITION`** | `struct dfs_partition *` | Copies **`blk->partition`** | **`-RT_EINVAL`** on disk node | + +### DFS helpers (`blk_dfs.h`) + +| Command | Role | +| --- | --- | +| **`RT_DEVICE_CTRL_BLK_SSIZEGET`** | Fills `*(rt_off_t *)args` with **bytes per sector** | +| **`RT_DEVICE_CTRL_ALL_BLK_SSIZEGET`** | Total byte size = sectors × bytes_per_sector (used by DFS v2 POSIX layer) | + +Vendor-specific IOCTLs pass through **`disk->ops->control(disk, blk, cmd, args)`** on the volume path; on the disk path **`blk` is NULL**. + +### Geometry structure + +```c +struct rt_device_blk_geometry { + rt_uint64_t sector_count; + rt_uint32_t bytes_per_sector; + rt_uint32_t block_size; /* erase block size for flash-style media */ +}; +``` + +--- + +# Partition volumes (`rt_blk_device`) + +Defined in **`blk.h`** when DFS headers are available. + +| Field | Meaning | +| --- | --- | +| `disk` | Parent **`rt_blk_disk`** | +| `sector_start` / `sector_count` | Slice on the physical disk (LBA range) | +| `partno` | Index used in device name (`%sp%d` / `%s%d`) | +| `partition` | **`struct dfs_partition`** for DFS mount; **`partition.lock`** → **`disk->usr_lock`** | +| `list` | Node on **`disk->part_nodes`** | + +### `blk_dev.c` forwarding + +| Operation | Behavior | +| --- | --- | +| **read/write** | Translates to **`disk->parent`** at **`sector_start + pos`** | +| **GETGEOME** | Disk geometry with **`sector_count`** replaced by partition length | +| **SYNC** | **`rt_device_control(&disk->parent, SYNC)`** | +| **ERASE / AUTOREFRESH** | Allowed only when **`disk->partitions <= 1`** | + +### Device naming (DM) + +**`disk_add_blk_dev`** (`blk_dev.c`): + +- If disk name’s last character is **`'a'`–`'z'`** (e.g. **`sda`**): volume name **`sdap1`** (`%sp%d`). +- Otherwise (e.g. **`mmcsd0`**, **`nvme0n1`**): **`mmcsd0p1`**, **`nvme0n1p2`** (`%s%d`). + +Each volume gets its own **`rt_dm_ida_alloc(disk->ida)`** slot under the same **`master_id`** as the disk. + +--- + +# DFS integration + +When **`RT_USING_DFS`** and **`RT_USING_POSIX_DEVIO`** (and DFS v2 as implemented in-tree), **`blk_dfs.c`** registers file operations that: + +1. On **open**: **`GETGEOME`** + **`ALL_BLK_SSIZEGET`** for file size. +2. On **read/write**: byte-oriented I/O with unaligned head/tail sector handling via a bounce sector buffer. +3. **ioctl**: forwards to **`rt_device_control`**. + +Mount the **volume** device name (not the disk) with the appropriate filesystem driver after partition probe. + +--- + +# Drivers already using blk + +These drivers embed **`struct rt_blk_disk`**, set **`ops`**, **`ida`**, and call **`rt_hw_blk_disk_register`** when media is present: + +| Driver | File | Typical name | Notable `rt_blk_disk` fields | +| --- | --- | --- | --- | +| **NVMe** | `nvme/nvme.c` | `nvme0n1`, … | **`parallel_io = RT_TRUE`**, per-namespace disk, **`max_partitions = RT_BLK_PARTITION_MAX`** | +| **SDIO/MMC** | `sdio/dev_block.c` | host name / `mmcsd0p*` | **`removable`**, **`max_partitions = RT_MMCSD_MAX_PARTITION`**, sets geometry before register | +| **SCSI disk** | `scsi/scsi_sd.c` | `sda`, `sdb`, … | **`parallel_io`** from host, READ CAPACITY 10/16, **`sd_ida`** | +| **SCSI CD-ROM** | `scsi/scsi_cdrom.c` | optical media | Read-only style backend | + +**AHCI/SATA** path: **`ata/ahci.c`** exposes SCSI devices; **SD card** goes through **`scsi_sd`** or **MMC `dev_block`**, then blk. + +### Minimal driver pattern + +```c +static const struct rt_blk_disk_ops my_blk_ops = { + .read = my_read, + .write = my_write, + .getgeome = my_getgeome, + .sync = my_sync, +}; + +static struct rt_dm_ida my_ida = RT_DM_IDA_INIT(MY_DISK); + +void my_disk_register(struct my_hw *hw) +{ + struct rt_blk_disk *disk = &hw->blk; + + disk->ops = &my_blk_ops; + disk->ida = &my_ida; + disk->parallel_io = RT_FALSE; /* or RT_TRUE if ops are reentrant */ + rt_dm_dev_set_name(&disk->parent.parent, "sda"); /* example */ + + rt_hw_blk_disk_register(disk); /* also runs partition probe */ +} +``` + +Details: @ref page_device_disk. + +--- + +# Locks and concurrency + +| Mechanism | Scope | +| --- | --- | +| **`disk->usr_lock`** (semaphore) | Serializes disk **`read`/`write`/`sync`** when **`parallel_io == 0`** | +| **`disk->lock`** (spinlock) | Partition list, unregister, erase path | +| **`parallel_io == 1`** | Disk **`read`/`write`** skip **`usr_lock`** — backend must be safe for concurrent I/O | + +Partition volume I/O still goes through the disk node and follows the disk’s **`parallel_io`** / lock rules. + +--- + +# Typical bring-up sequence + +1. Allocate **`struct rt_blk_disk`** (often embedded in driver private data). +2. Implement **`rt_blk_disk_ops`**; set **`ida`**, name, flags (**`read_only`**, **`removable`**, **`parallel_io`**, **`max_partitions`**). +3. **`rt_hw_blk_disk_register(disk)`** — partition probe runs automatically. +4. Application or init code **`rt_device_find("sda1")`**, mount DFS, or run filesystem code. +5. **`rt_hw_blk_disk_unregister(disk)`** after unmount — syncs, removes all volumes, checks **`ref_count`**. + +To **re-scan** partitions after media change, unregister, update geometry, register again (or call **`rt_blk_disk_probe_partition`** only if **`disk->partitions == 0`**). + +--- + +# Driver / BSP quick notes + +| Topic | Guidance | +| --- | --- | +| **Name before register** | First character of **`disk->parent.parent.name`** must be non-NUL | +| **`ida`** | Mandatory with **`RT_USING_DM`**; disk and each volume consume IDA slots | +| **Probe errors** | Ignored at register — check **`disk->partitions`** or open volumes explicitly | +| **`max_partitions`** | Set **`RT_BLK_PARTITION_NONE`** to skip table parsing | +| **Read-only** | Omit **`write`** in **`ops`** — core forces **`read_only`** | +| **Unmount first** | **`unregister`** returns **`-RT_EBUSY`** if **`ref_count > 0`** | +| **Erase** | Disk erase IOCTL removes **all** partition devices first | + +## See also + +- @ref page_device_disk — `rt_blk_disk` / `rt_blk_disk_ops` / register API +- @ref page_device_partitions — GPT/EFI and DFS parsers +- @ref page_device_ata — AHCI → SCSI → blk path +- @ref page_device_nvme +- @ref page_device_sdio +- @ref page_device_scsi +- `components/drivers/block/blk.c` +- `components/drivers/block/blk_dev.c` +- `components/drivers/block/blk_partition.c` diff --git a/documentation/6.components/device-driver/block/disk.md b/documentation/6.components/device-driver/block/disk.md new file mode 100644 index 00000000000..35a40c15f2c --- /dev/null +++ b/documentation/6.components/device-driver/block/disk.md @@ -0,0 +1,195 @@ +@page page_device_disk Physical disk (`rt_blk_disk`) + +# Disk device (driver writer guide) + +**`struct rt_blk_disk`** is the **physical backing store**: one contiguous LBA space implemented by your driver’s **`rt_blk_disk_ops`**. The blk core wraps it as an **`rt_device`**, then creates **volume** children — see @ref page_device_blk and @ref page_device_partitions. + +Implementation: **`components/drivers/block/blk.c`**. Header: **`components/drivers/include/drivers/blk.h`**. + +Same documentation pattern as **`documentation/6.components/device-driver/ata/ata.md`**: fill structures before **`rt_hw_blk_disk_register`**, implement **`ops`**, then let the core attach partitions and DFS-facing devices. + +--- + +## `struct rt_blk_disk` (before `rt_hw_blk_disk_register`) + +```34:57:components/drivers/include/drivers/blk.h +struct rt_blk_disk +{ + struct rt_device parent; + const struct rt_blk_disk_ops *ops; +#ifdef RT_USING_DM + struct rt_dm_ida *ida; +#endif + rt_uint32_t read_only:1; + rt_uint32_t parallel_io:1; + rt_uint32_t removable:1; + rt_uint32_t __magic; + rt_uint32_t partitions; + rt_int32_t max_partitions; + rt_list_t part_nodes; + struct rt_spinlock lock; + struct rt_semaphore usr_lock; +}; +``` + +| Member | Owner | Rule / meaning | +| --- | --- | --- | +| **`parent`** | **Caller** | Embedded **`rt_device`**. **Name must be set** before register (first char of **`parent.parent.name` ≠ `\0`**). | +| **`ops`** | **Caller** | **Required**; see **`struct rt_blk_disk_ops`** below. | +| **`ida`** | **Caller** (`RT_USING_DM`) | **Non-NULL**; shared with all volume children for **`rt_dm_ida_alloc`**. | +| **`read_only`** | **Caller / core** | If **`ops->write == NULL`**, core sets **`read_only = RT_TRUE`**. | +| **`parallel_io`** | **Caller** | **`RT_TRUE`**: disk read/write skip **`usr_lock`** (NVMe uses this). | +| **`removable`** | **Caller** | Hint for removable media (e.g. SDIO sets from host). | +| **`max_partitions`** | **Caller** | Cap for parsers; **`RT_BLK_PARTITION_NONE`** disables probing. **`RT_BLK_PARTITION_MAX`** = unlimited. | +| **`partitions`** | **Core** | Count of registered **`rt_blk_device`** nodes. | +| **`part_nodes`** | **Core** | List of **`struct rt_blk_device`**. | +| **`lock`** | **Core** | Spinlock for list / unregister / erase. | +| **`usr_lock`** | **Core** | Semaphore (prio) serializing I/O when not **`parallel_io`**. | +| **`__magic`** | **Core** | Set to **`RT_BLK_DISK_MAGIC`** at register. | + +--- + +## `struct rt_blk_disk_ops` + +```62:73:components/drivers/include/drivers/blk.h +struct rt_blk_disk_ops +{ + rt_ssize_t (*read)(struct rt_blk_disk *disk, rt_off_t sector, void *buffer, + rt_size_t sector_count); + rt_ssize_t (*write)(struct rt_blk_disk *disk, rt_off_t sector, const void *buffer, + rt_size_t sector_count); + rt_err_t (*getgeome)(struct rt_blk_disk *disk, struct rt_device_blk_geometry *geometry); + rt_err_t (*sync)(struct rt_blk_disk *disk); + rt_err_t (*erase)(struct rt_blk_disk *disk); + rt_err_t (*autorefresh)(struct rt_blk_disk *disk, rt_bool_t is_auto); + rt_err_t (*control)(struct rt_blk_disk *disk, struct rt_blk_device *blk, int cmd, void *args); +}; +``` + +| Callback | Required? | Contract | +| --- | --- | --- | +| **`read`** | **Yes** (practical) | **`sector`**, **`sector_count`** in **512-byte sectors** unless **`getgeome`** says otherwise. Return sectors read or **`< 0`** error. | +| **`write`** | Optional | **`NULL`** → disk read-only. Same addressing as **`read`**. | +| **`getgeome`** | **Yes** (practical) | Fill **`sector_count`**, **`bytes_per_sector`**, **`block_size`**. Used by partition code and DFS. | +| **`sync`** | Optional | Flush caches; called on unregister and **`RT_DEVICE_CTRL_BLK_SYNC`**. | +| **`erase`** | Optional | Whole-device erase; disk IOCTL may remove partitions first. | +| **`autorefresh`** | Optional | **`RT_DEVICE_CTRL_BLK_AUTOREFRESH`** (e.g. SD auto refresh). | +| **`control`** | Optional | Vendor IOCTLs; **`blk`** is **`NULL`** on raw disk, non-NULL on volume forwarding. | + +### Sector addressing expectations + +- Arguments are **LBA sector indices** on the **whole disk**, not bytes. +- **`getgeome()->bytes_per_sector`** must match what **`read`/`write`** use. +- **`getgeome()->sector_count`** is full device capacity in sectors. +- If **`parallel_io`**, **`read`/`write`** may overlap — use driver-internal locking or queue depth as needed. + +--- + +## `rt_hw_blk_disk_register` (core sequence) + +```228:337:components/drivers/block/blk.c +rt_err_t rt_hw_blk_disk_register(struct rt_blk_disk *disk) +``` + +| Step | Action | +| --- | --- | +| 1 | Validate **`disk`**, **`ops`**, name, **`ida`** (DM). | +| 2 | **`rt_dm_ida_alloc(disk->ida)`** for the **disk** device id. | +| 3 | Init **`usr_lock`**, **`part_nodes`**, **`lock`**. | +| 4 | Hook **`blk_ops`** or **`blk_parallel_ops`** on **`parent`**. | +| 5 | If **`!ops->write`** → **`read_only = 1`**. | +| 6 | **`device_set_blk_fops`**, **`rt_device_register`** with RD/WR flags. | +| 7 | **`rt_blk_disk_probe_partition(disk)`** — errors ignored. | + +**Return**: register error only; partition failure does not fail register. + +### Disk `rt_device` ops (internal) + +| Op | `parallel_io == 0` | `parallel_io == 1` | +| --- | --- | --- | +| read/write | Under **`usr_lock`** | Direct **`ops->read/write`** | +| control | **`blk_control`** → **`ops`** | Same | + +**`RT_DEVICE_CTRL_BLK_PARTITION`** on the **disk** node returns **`-RT_EINVAL`** (use volume devices). + +--- + +## `rt_hw_blk_disk_unregister` + +| Step | Action | +| --- | --- | +| 1 | **`ref_count > 0`** → **`-RT_EBUSY`** | +| 2 | **`ops->sync`** if present | +| 3 | **`blk_remove_all`** — unregister every **`rt_blk_device`** | +| 4 | **`rt_dm_ida_free`** for disk id, **`rt_device_unregister`** | + +Unmount DFS / close handles before unregister. + +--- + +## Helper APIs + +| API | Role | +| --- | --- | +| **`rt_blk_disk_probe_partition(disk)`** | Re-run parsers if **`partitions == 0`**; see @ref page_device_partitions | +| **`rt_blk_disk_get_capacity(disk)`** | **`getgeome`** → **`sector_count`** | +| **`rt_blk_disk_get_logical_block_size(disk)`** | **`getgeome`** → **`bytes_per_sector`** | + +--- + +## In-tree integration examples + +### NVMe (`nvme/nvme.c`) + +Per namespace: + +- **`rt_dm_dev_set_name(&ndev->parent.parent, "%sn%u", nvme->name, nsid)`** +- **`parallel_io = RT_TRUE`**, **`ops = &nvme_blk_ops`**, **`ida = &nvme_ida`** +- **`max_partitions = RT_BLK_PARTITION_MAX`** +- **`rt_hw_blk_disk_register(&ndev->parent)`** after identify + +### SDIO / MMC (`sdio/dev_block.c`) + +- Geometry from card CSD: **`sector_count`**, **`bytes_per_sector`**, **`block_size`** +- **`mmcsd_set_blksize`** before register +- **`removable`**, **`max_partitions = RT_MMCSD_MAX_PARTITION`** +- Name from **`host->name`** → volumes **`hostp1`** style + +### SCSI SD (`scsi/scsi_sd.c`) + +- READ CAPACITY (10/16) → geometry +- **`rt_dm_dev_set_name(..., "sd%c%c", letter_name(sd_id))`** — names like **`sda`** +- **`parallel_io`** from SCSI host +- **`ida = &scsi_sd_ida`** + +### SCSI CD-ROM (`scsi/scsi_cdrom.c`) + +Same blk registration pattern; typically read-only media (**`write`** omitted or read-only flag). + +--- + +## DM naming checklist + +1. **`RT_DM_IDA_INIT`** per disk family (e.g. **`scsi_sd_ida`**, **`nvme_ida`**, **`sdio_ida`**). +2. Set **`disk->ida`** before register. +3. Disk name via **`rt_dm_dev_set_name(&disk->parent.parent, ...)`**. +4. Volumes are named automatically at probe — do not register **`rt_blk_device`** yourself unless you bypass **`blk_put_partition`**. + +--- + +## Driver cautions + +| Topic | Guidance | +| --- | --- | +| **DMA / buffers** | Bounce in **`ops`** if callers pass non-DMA-safe pointers | +| **Capacity change** | Unregister, update geometry, register again | +| **Hotplug** | Remove card → **`rt_hw_blk_disk_unregister`**; insert → register fresh | +| **GPT + DFS** | Enable both Kconfig parsers; EFI runs first in **`partition_list[]`** | +| **Custom IOCTL** | Implement **`control(disk, blk, cmd, args)`**; forward unknown cmds from **`blk_control`** default case | + +## See also + +- @ref page_device_blk — application API and volume layer +- @ref page_device_partitions +- `components/drivers/block/blk.c` +- `components/drivers/include/drivers/blk.h` diff --git a/documentation/6.components/device-driver/block/partitions.md b/documentation/6.components/device-driver/block/partitions.md new file mode 100755 index 00000000000..b26de94aef2 --- /dev/null +++ b/documentation/6.components/device-driver/block/partitions.md @@ -0,0 +1,160 @@ +@page page_device_partitions Disk partition probing + +# Partition tables on `rt_blk_disk` + +After **`rt_hw_blk_disk_register`**, the core calls **`rt_blk_disk_probe_partition`** to read the on-disk partition layout and register one **`rt_blk_device`** per slice via **`blk_put_partition`**. Implementation: **`components/drivers/block/blk_partition.c`**; parsers in **`components/drivers/block/partitions/`**. + +Volume naming and application access: @ref page_device_blk. Disk registration: @ref page_device_disk. + +--- + +## Kconfig + +| Option | File | Role | +| --- | --- | --- | +| **`RT_BLK_PARTITION_EFI`** | `partitions/efi.c` | Protective MBR + **GPT** (UEFI-style) | +| **`RT_BLK_PARTITION_DFS`** | `partitions/dfs.c` | **DFS** legacy partition table in sector 0 | + +Both can be enabled; **`partition_list[]`** order is **EFI first**, then **DFS**. The first parser returning **0** wins. + +--- + +## `rt_blk_disk_probe_partition` + +```103:154:components/drivers/block/blk_partition.c +rt_err_t rt_blk_disk_probe_partition(struct rt_blk_disk *disk) +``` + +| Step | Behavior | +| --- | --- | +| 1 | **`disk == NULL`** → **`-RT_EINVAL`** | +| 2 | **`disk->partitions != 0`** → return **success** (already probed) | +| 3 | **`max_partitions == RT_BLK_PARTITION_NONE`** → **`-RT_EEMPTY`** | +| 4 | Loop **`partition_list[i](disk)`** | +| 5 | Parser **`-RT_ENOMEM`** → abort, return **`-RT_ENOMEM`** | +| 6 | Parser **0** → success, stop | +| 7 | No parser matched → **full-disk volume**: **`blk_put_partition(disk, NULL, 0, total_sectors, 0)`** | + +So a blank or unknown layout still yields **one** mountable device covering the entire disk (**`partno == 0`**). + +--- + +## `blk_put_partition` + +```27:100:components/drivers/block/blk_partition.c +rt_err_t blk_put_partition(struct rt_blk_disk *disk, const char *type, + rt_size_t start, rt_size_t count, int partno) +``` + +| Phase | Action | +| --- | --- | +| Allocate | **`struct rt_blk_device`** | +| Init | **`blk_dev_initialize(blk)`** | +| Range | **`sector_start = start`**, **`sector_count = count`**, **`partno`** | +| DFS meta | **`partition.offset`**, **`partition.size`**, **`partition.lock = &disk->usr_lock`** | +| Register | **`disk_add_blk_dev(disk, blk)`** → **`rt_device_register`**, link **`part_nodes`** | +| Count | **`disk->partitions++`** | + +### Console log + +For non-**`"dfs"`** types, printk partition index, byte offset, and human-readable size (KB/MB/GB). + +### Failure + +**`-RT_ENOMEM`** frees the partial **`rt_blk_device`** and logs via **`LOG_E`**. + +--- + +## EFI / GPT (`partitions/efi.c`) + +### Boot-time `force_gpt` + +**`INIT_CORE_EXPORT(force_gpt_setup)`** reads bootarg **`gpt`** via **`rt_ofw_bootargs_select("gpt", 0)`** when **`RT_USING_OFW`**. When set, GPT probing is forced even if protective MBR checks are ambiguous. + +### Validation flow (summary) + +1. Read LBA 0 — check **protective MBR** (EFI GPT type, starting LBA 1). +2. Read **primary GPT header** at LBA 1 (or from **`force_gpt`** path). +3. Verify signature **"EFI PART"**, header CRC (**`efi_crc32`**), **`my_lba`**, **`first_usable_lba` / `last_usable_lba`** vs disk capacity (**`last_lba()`** = **`rt_blk_disk_get_capacity - 1`**). +4. Load partition entry array; validate entry CRC. +5. For each valid entry: **`blk_put_partition(disk, type, start, count, partno)`** with type string / GUID mapping where implemented. + +### Sector size + +All LBA math uses **`rt_blk_disk_get_logical_block_size(disk)`** — must stay consistent with **`getgeome`**. + +### Enable + +Menu: **`RT_BLK_PARTITION_EFI`** (default **y** when blk partitions menu is on). + +--- + +## DFS partition (`partitions/dfs.c`) + +Legacy path for RT-Thread **DFS** on-disk superblock layout: + +1. Allocate one sector buffer. +2. **`disk->ops->read(disk, 0, sector, 1)`**. +3. Loop **`dfs_filesystem_get_partition(&part, sector, i)`** for **`i < max_partitions`**. +4. **`blk_put_partition(disk, "dfs", part.offset, part.size, i)`**. + +Requires **`RT_USING_DFS`** and **`RT_BLK_PARTITION_DFS`**. + +--- + +## Volume naming after probe + +Handled in **`disk_add_blk_dev`** (`blk_dev.c`): + +| Disk name ends with | Example disk | Volume examples | +| --- | --- | --- | +| Letter **`a`–`z`** | **`sda`** | **`sdap1`**, **`sdap2`** | +| Digit / other | **`mmcsd0`**, **`nvme0n1`** | **`mmcsd0p1`**, **`nvme0n1p2`** | + +--- + +## When to call probe + +| Scenario | Action | +| --- | --- | +| Normal driver | Rely on **`rt_hw_blk_disk_register`** (automatic) | +| Media just formatted | Unregister disk, register again, or call **`rt_blk_disk_probe_partition`** if **`partitions == 0`** | +| Disable tables | Set **`max_partitions = RT_BLK_PARTITION_NONE`** | +| RAM disk / single volume | Still get one full-disk **`partno 0`** unless you manage volumes manually | + +### Call sites in tree + +| Location | Notes | +| --- | --- | +| **`blk.c`** | End of **`rt_hw_blk_disk_register`** | +| Driver after hotplug | Rare; prefer re-register | + +--- + +## Interaction with disk IOCTLs + +| IOCTL | Multi-partition disk | +| --- | --- | +| **ERASE / AUTOREFRESH** on **volume** | **`-RT_EIO`** if **`disk->partitions > 1`** | +| **ERASE** on **disk** | Removes **all** volumes first, then **`ops->erase`** | +| **GETGEOME** on **volume** | **`sector_count`** = partition only | + +--- + +## Driver / BSP quick notes + +| Topic | Guidance | +| --- | --- | +| **Order** | EFI before DFS — do not rely on DFS if GPT is present | +| **`max_partitions`** | SDIO passes **`RT_MMCSD_MAX_PARTITION`**; NVMe uses **`RT_BLK_PARTITION_MAX`** | +| **Empty card** | Probe still creates full-disk volume — mount may need valid FS | +| **Re-probe** | Not supported with existing volumes; unregister disk first | +| **Custom layouts** | Add a parser to **`partition_list[]`** or call **`blk_put_partition`** from driver code | + +## See also + +- @ref page_device_blk +- @ref page_device_disk +- `components/drivers/block/blk_partition.c` +- `components/drivers/block/partitions/efi.c` +- `components/drivers/block/partitions/dfs.c` diff --git a/documentation/6.components/device-driver/can/can.md b/documentation/6.components/device-driver/can/can.md new file mode 100644 index 00000000000..a7ff371cd5f --- /dev/null +++ b/documentation/6.components/device-driver/can/can.md @@ -0,0 +1,311 @@ +@page page_device_can CAN device + +# CAN bus device framework + +RT-Thread exposes CAN controllers as **`RT_Device_Class_CAN`** devices. The framework in **`components/drivers/can/dev_can.c`** provides interrupt-driven RX/TX FIFOs, optional **hardware filters**, **CAN-FD** message layout, **blocking** and **non-blocking** send paths, and periodic **bus status** polling. Board and SoC drivers implement **`struct rt_can_ops`** and register with **`rt_hw_can_register()`**. + +**`can_dm.c`** is **not** the bus core — it only supplies ISO 11898 DLC/length helpers (`can_dlc2len`, `can_len2dlc`). See @ref page_device_can_dm. + +Header: `components/drivers/include/drivers/dev_can.h`. Core: `components/drivers/can/dev_can.c`. + +## Architecture + +``` + Application / FinSH + │ rt_device_find / open / read / write / control + ▼ + struct rt_can_device (dev_can.c) + │ software RX FIFO, TX mailboxes, nb_tx ringbuffer + │ rt_hw_can_isr() ← BSP ISR + ▼ + struct rt_can_ops + │ configure / control / sendmsg / recvmsg / sendmsg_nonblocking + ▼ + SoC CAN / CAN-FD controller (BSP or bsp/*/dm/can/) +``` + +| Component | File | Role | +| --- | --- | --- | +| **Framework** | `dev_can.c` | Device ops, FIFOs, ISR demux, `rt_hw_can_register` | +| **FD DLC helpers** | `can_dm.c`, `can_dm.h` | DLC ↔ byte length (FD payloads) | +| **DM SoC example** | `bsp/rockchip/dm/can/canfd-rockchip.c` | OFW platform driver + `rt_hw_can_register` | +| **Legacy BSP** | e.g. `bsp/stm32/.../drv_can.c`, `drv_fdcan.c` | HAL-based `rt_can_ops` | + +With **`RT_USING_DM`**, SoC-specific CAN drivers may live under **`SOC_DM_CAN_DIR`** (see `can/Kconfig`). + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_CAN`** | Build `components/drivers/can/` | +| **`RT_CAN_USING_HDR`** | Per-filter RX lists and filter callbacks (`rt_can_hdr`) | +| **`RT_CAN_USING_CANFD`** | 64-byte payload, `fd_frame` / `brs`, bit-timing fields in `can_configure` | +| **`RT_CANMSG_BOX_SZ`** | Software RX buffer depth (messages) | +| **`RT_CANSND_BOX_NUM`** | Concurrent blocking TX slots (match HW mailboxes) | +| **`RT_CANSND_MSG_TIMEOUT`** | Blocking send completion timeout (ticks) | +| **`RT_CAN_NB_TX_FIFO_SIZE`** | Non-blocking TX ring buffer (bytes); size ≥ N × `sizeof(struct rt_can_msg)` | +| **`RT_CAN_MALLOC_NB_TX_BUFFER`** | Allocate NB ring at `open` instead of static pool in `rt_can_device` | + +Optional (not in core Kconfig): **`RT_CAN_USING_BUS_HOOK`** — periodic `bus_hook` from status timer. + +--- + +# Access CAN devices (applications) + +## Find and open + +```c +#define CAN_DEV_NAME "can1" + +rt_device_t candev = rt_device_find(CAN_DEV_NAME); +rt_device_open(candev, RT_DEVICE_FLAG_INT_TX | RT_DEVICE_FLAG_INT_RX); +``` + +| Open flag | Effect | +| --- | --- | +| **`RT_DEVICE_FLAG_INT_RX`** | Allocates software RX FIFO; driver enables RX IRQ via `control(SET_INT, INT_RX)` | +| **`RT_DEVICE_FLAG_INT_TX`** | Allocates TX mailbox pool + semaphore; enables TX IRQ | +| **`RT_DEVICE_FLAG_RDWR`** | Set at register time (`rt_hw_can_register`) | + +**`read`** only works with **`INT_RX`** and `ref_count > 0`. **`write`** requires the device to be opened; size must be a multiple of **`sizeof(struct rt_can_msg)`**. + +Register **`rt_device_set_rx_indicate()`** to wake a thread when frames arrive (see example in `dev_can.h` Doxygen block). + +## Receive + +```c +struct rt_can_msg rx_msg; +rx_msg.hdr_index = -1; /* -1: any frame; >=0: specific filter bank (RT_CAN_USING_HDR) */ + +rt_ssize_t n = rt_device_read(candev, 0, &rx_msg, sizeof(rx_msg)); +``` + +Batch read: pass an array of `rt_can_msg` and divide returned bytes by `sizeof(struct rt_can_msg)`. + +## Send — blocking vs non-blocking + +| Mode | How | Behavior | +| --- | --- | --- | +| **Blocking** | Default (`nonblocking == 0`), thread context, `INT_TX` open | Waits for HW mailbox + `RT_CAN_EVENT_TX_DONE` / `FAIL` | +| **Non-blocking** | `msg.nonblocking = 1`, or call from ISR | Uses `sendmsg_nonblocking`; overflow → `nb_tx_rb` ring | +| **Private mailbox** | `RT_CAN_CMD_SET_PRIV` + `msg.priv` = mailbox index | `_can_int_tx_priv` — fixed TX slot per message | + +```c +struct rt_can_msg tx = {0}; +tx.id = 0x123; +tx.ide = RT_CAN_STDID; +tx.rtr = RT_CAN_DTR; +tx.len = 8; +tx.data[0] = 0x11; +rt_device_write(candev, 0, &tx, sizeof(tx)); /* blocking */ + +tx.nonblocking = 1; +rt_device_write(candev, 0, &tx, sizeof(tx)); /* accept or enqueue */ +``` + +**`RT_CANSND_MSG_TIMEOUT`** applies to blocking waits. Non-blocking path needs **`ops->sendmsg_nonblocking`** implemented in the low-level driver. + +## Control commands (`rt_device_control`) + +| Command | Value | Argument | Role | +| --- | --- | --- | --- | +| **`RT_DEVICE_CTRL_CONFIG`** | — | `struct can_configure *` | Re-`configure` (baud, mode, box sizes) | +| **`RT_CAN_CMD_SET_BAUD`** | `0x14` | baud (driver-defined) | Set arbitration bitrate | +| **`RT_CAN_CMD_SET_BAUD_FD`** | `0x1B` | baud | CAN-FD data phase bitrate | +| **`RT_CAN_CMD_SET_BITTIMING`** | `0x1C` | `struct rt_can_bit_timing_config *` | Custom prescaler/segments | +| **`RT_CAN_CMD_SET_MODE`** | `0x15` | mode | `RT_CAN_MODE_NORMAL` / `LISTEN` / `LOOPBACK` / `LOOPBACKANLISTEN` | +| **`RT_CAN_CMD_SET_PRIV`** | `0x16` | `RT_CAN_MODE_PRIV` / `NOPRIV` | Private TX mailbox mode | +| **`RT_CAN_CMD_SET_FILTER`** | `0x13` | `struct rt_can_filter_config *` | HW filters (needs **`RT_CAN_USING_HDR`**) | +| **`RT_CAN_CMD_SET_CANFD`** | `0x1A` | enable flag | Enable FD in driver | +| **`RT_CAN_CMD_START`** | `0x1D` | non-zero / zero | Start or stop controller | +| **`RT_CAN_CMD_GET_STATUS`** | `0x17` | `struct rt_can_status *` | Error counters, pkg stats | +| **`RT_CAN_CMD_SET_STATUS_IND`** | `0x18` | `rt_can_status_ind_type_t` | User callback on timer tick | +| **`RT_CAN_CMD_SET_BUS_HOOK`** | `0x19` | `rt_can_bus_hook` | Periodic hook (if enabled) | +| **`RT_DEVICE_CTRL_SET_INT` / `CLR_INT`** | — | `RT_DEVICE_FLAG_INT_RX/TX`, `RT_DEVICE_CAN_INT_ERR` | IRQ mask (used internally on open/close) | + +Typical bring-up sequence: + +1. `rt_device_open(..., INT_TX | INT_RX)` +2. `RT_CAN_CMD_SET_BAUD` / `SET_BITTIMING` / `SET_CANFD` as needed +3. `RT_CAN_CMD_SET_FILTER` (optional) +4. `RT_CAN_CMD_SET_MODE` +5. `RT_CAN_CMD_START` with non-zero argument + +On **`close`**, the framework calls **`RT_CAN_CMD_START`** with **`RT_FALSE`** to stop the controller. + +### Baud presets (`enum CANBAUD`) + +`CAN1MBaud`, `CAN800kBaud`, `CAN500kBaud`, `CAN250kBaud`, `CAN125kBaud`, `CAN100kBaud`, `CAN50kBaud`, `CAN20kBaud`, `CAN10kBaud` — used in **`can_configure.baud_rate`**. + +### Default configuration macro + +```c +struct can_configure cfg = CANDEFAULTCONFIG; +cfg.baud_rate = CAN500kBaud; +cfg.msgboxsz = RT_CANMSG_BOX_SZ; +cfg.sndboxnumber = RT_CANSND_BOX_NUM; +``` + +--- + +## Message layout (`struct rt_can_msg`) + +| Field | Meaning | +| --- | --- | +| **`id`** (29 bits) | CAN ID | +| **`ide`** | `RT_CAN_STDID` (0) or `RT_CAN_EXTID` (1) | +| **`rtr`** | `RT_CAN_DTR` (data) or `RT_CAN_RTR` (remote) | +| **`len`** | DLC or payload length (driver may expect bytes or DLC; FD drivers often use `can_len2dlc`) | +| **`priv`** | TX mailbox index in private mode | +| **`hdr_index`** | RX: matched filter bank; set **-1** before read for any frame | +| **`nonblocking`** | 1 → non-blocking send path | +| **`fd_frame` / `brs`** | CAN-FD (`RT_CAN_USING_CANFD`) | +| **`data[]`** | 8 bytes (classic) or 64 bytes (FD) | + +--- + +## Hardware filters (`RT_CAN_USING_HDR`) + +```c +struct rt_can_filter_item items[] = { + RT_CAN_FILTER_STD_INIT(0x100, RT_NULL, RT_NULL), + {0x555, 0, 0, 0, 0x7FF, 7}, /* fixed bank #7 */ +}; +struct rt_can_filter_config cfg = { + .count = sizeof(items)/sizeof(items[0]), + .actived = 1, + .items = items, +}; +rt_device_control(candev, RT_CAN_CMD_SET_FILTER, &cfg); +``` + +| Filter field | Role | +| --- | --- | +| **`mode`** | `RT_CAN_MODE_MASK` (0) or `RT_CAN_MODE_LIST` (1) | +| **`mask`** | Mask mode: 1 = bit must match | +| **`hdr_bank`** | -1 = driver assigns; ≥0 = fixed bank | +| **`rxfifo`** | `CAN_RX_FIFO0` or `CAN_RX_FIFO1` | +| **`ind` / `args`** | Optional per-filter RX callback | + +Framework mirrors filters into **`can->hdr[]`** and routes RX frames to per-bank lists when **`hdr_index`** matches. + +Helper macros: **`RT_CAN_FILTER_ITEM_INIT`**, **`RT_CAN_FILTER_EXT_INIT`**, **`RT_CAN_STD_RMT_FILTER_INIT`**, etc. (`dev_can.h`). + +--- + +## CAN-FD + +Enable **`RT_CAN_USING_CANFD`** in Kconfig and in driver: + +- **`can_configure.enable_canfd`**, **`baud_rate_fd`** +- **`use_bit_timing`** + **`can_timing`** / **`canfd_timing`** (`struct rt_can_bit_timing`) +- **`RT_CAN_CMD_SET_CANFD`**, **`RT_CAN_CMD_SET_BAUD_FD`**, **`RT_CAN_CMD_SET_BITTIMING`** + +Set **`rt_can_msg.fd_frame`** and **`brs`** on TX; on RX use **`can_dlc2len()`** if HW returns DLC nibble (@ref page_device_can_dm). + +--- + +## Status and errors + +**`struct rt_can_status`** — REC/TEC, **`errcode`** (`RT_CAN_BUS_*`), packet/drop counters, error type histogram. + +Periodic timer (**`can_configure.ticks`**, default from driver) calls **`RT_CAN_CMD_GET_STATUS`** and optional **`status_indicate`** callback. + +**`enum RT_CAN_STATUS_MODE`**: `NORMAL`, `ERRWARNING`, `ERRPASSIVE`, `BUSOFF` (derived from **`errcode`** in FinSH **`canstat`**). + +FinSH (when **`RT_USING_FINSH`**): **`canstat can1`** — exported from `dev_can.c`. + +--- + +## Driver author guide + +### `struct rt_can_ops` + +| Op | Required | Role | +| --- | --- | --- | +| **`configure`** | Yes | Apply `struct can_configure` (baud, mode, FD) | +| **`control`** | Yes | IOCTL commands above, `SET_INT`/`CLR_INT` | +| **`sendmsg`** | For blocking TX | `boxno` = mailbox index | +| **`recvmsg`** | For IRQ RX | `fifo` = HW RX FIFO index; fill `rt_can_msg` | +| **`sendmsg_nonblocking`** | For NB TX / ISR | Return **`RT_EOK`** or **`-RT_EBUSY`** | + +### Register + +```c +struct rt_can_device my_can; +struct can_configure cfg = CANDEFAULTCONFIG; +/* fill cfg, set my_can.config */ +my_can.config = cfg; + +rt_err_t rt_hw_can_register(struct rt_can_device *can, + const char *name, + const struct rt_can_ops *ops, + void *user_data); +``` + +Device name is typically **`can0`**, **`can1`**, … Auto-naming: **`rt_dm_dev_set_name_auto(&can->parent, "can")`** in DM drivers (Rockchip). + +### ISR contract — `rt_hw_can_isr()` + +BSP ISR calls **`rt_hw_can_isr(can, event)`**: + +| `event & 0xff` | Meaning | +| --- | --- | +| **`RT_CAN_EVENT_RX_IND`** (0x01) | Frame received; **`event >> 8`** = RX FIFO index | +| **`RT_CAN_EVENT_RXOF_IND`** (0x06) | RX overflow (still tries one recv) | +| **`RT_CAN_EVENT_TX_DONE`** (0x02) | Mailbox **`event >> 8`** completed OK | +| **`RT_CAN_EVENT_TX_FAIL`** (0x03) | Mailbox transmission failed | + +After **`TX_DONE`**, framework drains **`nb_tx_rb`** via **`sendmsg_nonblocking`**. + +**`recvmsg`** must return byte count or **-1** if no frame. Populate **`hdr_index`** when hardware reports filter match. + +### DM platform driver pattern (Rockchip) + +1. **`RT_PLATFORM_DRIVER_EXPORT`** with **`rt_ofw_node_id`** (`compatible`) +2. **`probe`**: map reg/IRQ, **`rt_clk_get`**, **`rt_reset_control_get`**, deassert reset +3. Fill **`can_configure`** (bit timing from DT or constants) +4. **`rt_hw_interrupt_install`**, **`rt_hw_can_register`** +5. **`remove`**: mask IRQ, **`rt_device_unregister`** + +Reference: `bsp/rockchip/dm/can/canfd-rockchip.c` — uses **`can_dm.h`** for FD DLC encoding. + +### BSP drivers using this framework (examples) + +| BSP / path | Notes | +| --- | --- | +| STM32 `drv_can.c` / `drv_fdcan.c` | Classic + FDCAN | +| NXP i.MX / MCX `drv_can.c` | FlexCAN | +| WCH, Renesas, Phytium, Synwit, Nuvoton | Various `rt_hw_can_register` | +| Rockchip `canfd-rockchip.c` | DM + OFW | + +--- + +## Engineer checklist + +1. **Probe**: reset → clock → pinmux → transceiver enable/regulator → init HW → **`rt_hw_can_register`** +2. **ISR**: keep minimal — **`recvmsg`** + **`rt_hw_can_isr`**; no blocking in ISR +3. **Filters / baud**: configure before **`RT_CAN_CMD_START`** +4. **Mailbox count**: set **`sndboxnumber`** to match hardware TX buffers +5. **FD**: increase **`RT_CAN_NB_TX_FIFO_SIZE`** — `sizeof(struct rt_can_msg)` is much larger with 64-byte payload +6. **Termination & transceiver STB**: board-level; not handled by framework + +## Pitfalls + +- **Open without `INT_RX`/`INT_TX`** — `read`/`write` return **`-RT_ENOSYS`** +- **Non-blocking without `sendmsg_nonblocking`** — `write` with `nonblocking=1` fails +- **RX FIFO full** — oldest frame dropped (`dropedrcvpkg`); increase **`msgboxsz`** or read faster +- **Bus-off** — check **`RT_CAN_CMD_GET_STATUS`**; recovery is driver/BSP specific +- **DLC vs length on FD** — use **`can_len2dlc`/`can_dlc2len`** consistently (@ref page_device_can_dm) +- **Multiple `open`** — `ref_count` > 1 skips FIFO teardown on close; design single-owner stacks + +## See also + +- @ref page_device_can_dm — FD DLC helpers +- `components/drivers/include/drivers/dev_can.h` — full API and application example +- `components/drivers/can/dev_can.c` +- `components/drivers/can/readme-zh.txt` — legacy Chinese notes for driver porting +- @ref page_device_ofw — DT matching for DM CAN +- @ref page_device_clk, @ref page_device_reset, @ref page_device_regulator — probe dependencies diff --git a/documentation/6.components/device-driver/can/dm.md b/documentation/6.components/device-driver/can/dm.md new file mode 100755 index 00000000000..3ef2b3a760b --- /dev/null +++ b/documentation/6.components/device-driver/can/dm.md @@ -0,0 +1,65 @@ +@page page_device_can_dm CAN FD DLC helpers (`can_dm`) + +# `can_dm` utilities + +Header: **`components/drivers/can/can_dm.h`** (also installable as `drivers/can_dm.h` via include path). Implementation: **`components/drivers/can/can_dm.c`** — **only** ISO 11898 DLC/length conversion; **no** device register, OFW, or ISR code. + +The CAN **device framework** is **`dev_can.c`** — see @ref page_device_can. + +## APIs + +```c +rt_uint8_t can_dlc2len(rt_uint8_t can_dlc); +rt_uint8_t can_len2dlc(rt_uint8_t len); +``` + +### `can_dlc2len` + +Maps **CAN FD DLC nibble** (0–15) to **payload byte length**: + +| DLC | 0–8 | 9–12 | 13–16 | 17–20 | 21–24 | 25–32 | 33–48 | 49–64 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- | +| Bytes | 0–8 | 12 | 16 | 20 | 24 | 32 | 48 | 64 | + +Only low **4 bits** of `can_dlc` are used (`can_dlc & 0x0F`). + +### `can_len2dlc` + +Inverse mapping for lengths **0–64**; returns **`0xf`** if **`len > 64`**. + +## Usage in controllers + +FD-capable drivers should: + +1. Store/transmit hardware DLC using **`can_len2dlc(actual_payload_len)`**. +2. On RX, convert to byte count with **`can_dlc2len(rx_frame.dlc)`** before copying to upper layers. + +Classic CAN (≤ 8 bytes) uses DLC 0–8 with identity mapping in both tables. + +## CAN ID flags (header only) + +`can_dm.h` also defines Linux-style ID flags for drivers that pack ID + type into one word: + +| Macro | Value | Meaning | +| --- | --- | --- | +| **`CAN_EFF_FLAG`** | `0x80000000` | Extended frame | +| **`CAN_RTR_FLAG`** | `0x40000000` | Remote frame | +| **`CAN_ERR_FLAG`** | `0x20000000` | Error frame | +| **`CAN_SFF_MASK`** / **`CAN_EFF_MASK`** | — | ID field masks | + +These are **not** used by `dev_can.c` (`rt_can_msg` uses separate `ide`/`rtr` bits). + +## Driver / BSP quick notes + +| Topic | Guidance | +| --- | --- | +| Not a DM bus | No `RT_CAN_OFW_DECLARE` in core — platform drivers (e.g. Rockchip `canfd-rockchip.c`) own OFW probe | +| FD vs classic | Do not use DLC > 8 on classic-only controllers | +| Include | `#include "can_dm.h"` or path from driver directory | +| **`can_get_dlc` / `canfd_get_dlc`** | Clamp DLC before `can_dlc2len` | + +## See also + +- @ref page_device_can +- `components/drivers/can/can_dm.c` +- `components/drivers/can/dev_can.c` diff --git a/documentation/6.components/device-driver/clk/clk.md b/documentation/6.components/device-driver/clk/clk.md new file mode 100755 index 00000000000..433d8286b95 --- /dev/null +++ b/documentation/6.components/device-driver/clk/clk.md @@ -0,0 +1,298 @@ +@page page_device_clk Clock framework (CLK) + +# Common Clock Framework (CCF) + +RT-Thread’s **CLK** subsystem (`components/drivers/clk/`) is the **Device Model (DM)** clock provider layer: device tree **`clocks`** / **`#clock-cells`** resolve to **`struct rt_clk`** handles, with **reference-counted** `prepare` / `enable` and optional **rate** / **parent** changes. It mirrors the Linux common clock model closely enough to reuse **dt-bindings** and DTS fragments. + +Header: `components/drivers/include/drivers/clk.h`. Core: `components/drivers/clk/clk.c`. + +Built-in providers in-tree: + +| Module | Role | +| --- | --- | +| **`clk-fixed-rate.c`** | DT **`fixed-clock`** — @ref page_device_clk_fixed | +| **`clk-scmi.c`** | Clocks owned by **ARM SCMI** firmware — needs **`RT_CLK_SCMI`** | +| **`bsp/*/dm/clk/`** (e.g. Rockchip) | SoC CCM / PLL / mux / gate — via **`SOC_DM_CLK_DIR`** | + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_DM`** | Required parent — CLK is a DM feature | +| **`RT_USING_CLK`** | Build `clk.c` + selected providers; selects **`RT_USING_ADT_REF`** | +| **`RT_CLK_SCMI`** | `clk-scmi.c`; depends on **`RT_FIRMWARE_ARM_SCMI`** | +| **`SOC_DM_CLK_DIR`** | SoC adds Kconfig/SConscript under BSP `dm/clk/` | + +Boot argument (OFW): **`clk_ignore_unused`** — when set, cells with **`RT_CLK_F_IGNORE_UNUSED`** skip **`disable`** even at refcount zero (debug / bring-up). + +--- + +## Architecture + +``` + Device tree consumer node + clocks = <&cru CLK_UART2_SCLK>; + clock-names = "baudclk", "apb_pclk"; + │ + ▼ + rt_clk_get_by_name(dev, "baudclk") / rt_ofw_get_clk(np, 0) + │ + ▼ + struct rt_clk (consumer handle: dev_id, con_id, min/max rate) + │ + ▼ + struct rt_clk_cell (one output: PLL child, gate, mux, fixed, …) + │ + ▼ + struct rt_clk_node (provider: registered with rt_clk_register) + │ + ▼ + rt_clk_ops (prepare/enable/recalc_rate/set_rate/set_parent/…) +``` + +| Object | Meaning | +| --- | --- | +| **`rt_clk_node`** | One **clock controller** in DT (e.g. `&cru`, `fixed-clock`, SCMI clock device) | +| **`rt_clk_cell`** | One **output clock** from that controller (index from `#clock-cells`) | +| **`rt_clk`** | **Consumer reference** to a cell; allocated per `get` | +| **`rt_clk_array`** | All clocks listed in consumer’s **`clocks`** property | + +Provider registration stores the node on a global list (`RT_CLK_NODE_OBJ_NAME` = **`"CLKNP"`**) and attaches **`rt_ofw_data(np)`** so phandle lookup works. + +--- + +## Consumer API (platform drivers) + +Typical **`probe`** sequence on a DM device (`struct rt_device *dev` with **`dev->ofw_node`**): + +```c +struct my_dev { + struct rt_clk *clk; + struct rt_clk *pclk; +}; + +/* By connection name (matches clock-names) */ +pdev->clk = rt_clk_get_by_name(dev, "spiclk"); +pdev->pclk = rt_clk_get_by_name(dev, "apb_pclk"); + +/* Or all clocks at once */ +pdev->clk_arr = rt_clk_get_array(dev); + +if (rt_clk_prepare_enable(pdev->pclk)) + return err; + +if (rt_clk_prepare_enable(pdev->clk)) + goto disable_pclk; + +freq = rt_clk_get_rate(pdev->clk); +``` + +| API | Role | +| --- | --- | +| **`rt_clk_get_array(dev)`** | Parse **`clocks`** + **`clock-names`** → `rt_clk_array` | +| **`rt_clk_get_by_index(dev, i)`** | Nth entry in **`clocks`** | +| **`rt_clk_get_by_name(dev, name)`** | Match **`clock-names`**; fallback: scan registered cells by **`cell->name`** | +| **`rt_clk_prepare` / `unprepare`** | May sleep (rails, firmware); refcounted per **cell** | +| **`rt_clk_enable` / `disable`** | Fast gate; enables **parent** first; refcounted | +| **`rt_clk_prepare_enable`** | `prepare` then `enable` (common in probe) | +| **`rt_clk_disable_unprepare`** | Reverse; use in `remove` / error paths | +| **`rt_clk_get_rate` / `set_rate` / `round_rate`** | Rate query and change | +| **`rt_clk_set_parent` / `get_parent`** | Reparent mux | +| **`rt_clk_set_rate_range`** | Consumer min/max constraints | +| **`rt_clk_put` / `rt_clk_array_put`** | Free consumer handles (does not disable) | + +**`NULL` clock**: `rt_clk_enable(NULL)` and `rt_clk_prepare(NULL)` return **`RT_EOK`** — useful for optional clocks; still check **`rt_clk_get_*`** return before use. + +Array helpers: **`rt_clk_array_prepare_enable`**, **`rt_clk_array_disable_unprepare`**, etc. — stop on first error during prepare/enable loops. + +### OFW-only helpers + +| API | Role | +| --- | --- | +| **`rt_ofw_get_clk(np, index)`** | Resolve phandle without `rt_device` | +| **`rt_ofw_get_clk_by_name(np, name)`** | By **`clock-names`** | +| **`rt_ofw_get_clk_array(np)`** | Full **`clocks`** list | +| **`rt_ofw_count_of_clk(clk_np)`** | Provider output count | +| **`rt_ofw_clk_get_parent_name(np, index)`** | Debug / parent name | +| **`rt_ofw_clk_set_defaults(np)`** | Apply **`assigned-clocks*`** (see below) | + +If the provider is not registered yet, **`ofw_get_clk`** may call **`rt_platform_ofw_request(clk_ofw_np)`** to defer probe. + +--- + +## Device tree (consumers) + +```dts +uart2: serial@ff1a0000 { + compatible = "rockchip,rk3588-uart", "snps,dw-apb-uart"; + reg = <0x0 0xff1a0000 0x0 0x100>; + clocks = <&cru CLK_UART2_SRC>, <&cru CLK_UART2_FRAC>, <&cru PCLK_UART2>; + clock-names = "baudclk", "apb_pclk"; + assigned-clocks = <&cru CLK_UART2_SRC>; + assigned-clock-rates = <24000000>; +}; +``` + +| Property | Role | +| --- | --- | +| **`clocks`** | Phandle + `#clock-cells` specifier per input | +| **`clock-names`** | Stable names for **`rt_clk_get_by_name`** | +| **`assigned-clocks`** | Clocks to configure when provider registers | +| **`assigned-clock-parents`** | Optional parent for each assigned clock | +| **`assigned-clock-rates`** | Optional Hz for each assigned clock | + +**`rt_clk_register()`** calls **`rt_ofw_clk_set_defaults(dev->ofw_node)`** when the provider has a bound OFW node — applies assigned settings **before** consumers rely on rates. + +Provider node exports clocks: + +```dts +cru: clock-controller@fd7c0000 { + compatible = "rockchip,rk3588-cru"; + #clock-cells = <1>; + clock-output-names = "clk_uart2_src", "..."; +}; +``` + +Custom providers with multi-cell specifiers implement **`clk_np->ofw_parse(np, args)`**; default parser uses **`args->args[0]`** as cell index. **`clock-indices`** remaps DT indices to internal cell order. + +--- + +## Provider API (clock drivers) + +### `struct rt_clk_ops` + +| Callback | Typical use | +| --- | --- | +| **`prepare` / `unprepare`** | PMIC / slow path; paired refcount | +| **`enable` / `disable`** | Clock gate bit | +| **`is_prepared` / `is_enabled`** | Query HW state (optional) | +| **`recalc_rate`** | Return Hz from parent rate + dividers | +| **`round_rate`** | Snap requested rate | +| **`set_rate`** | Program dividers / PLL | +| **`set_parent` / `get_parent`** | Mux select | +| **`set_phase` / `get_phase`** | DDR / SDIO phase (optional) | + +Leave unused ops **`NULL`**; core skips them. + +### `struct rt_clk_cell` flags + +| Flag | Effect | +| --- | --- | +| **`RT_CLK_F_SET_RATE_GATE`** | Gate across rate change | +| **`RT_CLK_F_SET_PARENT_GATE`** | Gate across reparent | +| **`RT_CLK_F_SET_RATE_PARENT`** | Propagate rate change to parent | +| **`RT_CLK_F_IGNORE_UNUSED`** | Skip disable when unused (with **`clk_ignore_unused`**) | +| **`RT_CLK_F_IS_CRITICAL`** | Never auto-disable | +| **`RT_CLK_F_GET_RATE_NOCACHE`** | Always call **`recalc_rate`** | + +### Register a provider + +```c +struct my_cru { + struct rt_clk_node clk_np; + struct rt_clk_cell *cells[N]; + /* … */ +}; + +static struct rt_clk_cell *my_cru_ofw_parse(struct rt_clk_node *np, + struct rt_ofw_cell_args *args) +{ + rt_uint32_t idx = args->args[0]; + if (idx >= np->cells_nr) + return RT_NULL; + return np->cells[idx]; +} + +/* In probe, after filling cells[] and ops */ +cru->clk_np.dev = dev; +cru->clk_np.cells = cru->cells; +cru->clk_np.cells_nr = num_clocks; +cru->clk_np.ofw_parse = my_cru_ofw_parse; + +err = rt_clk_register(&cru->clk_np); +``` + +If the provider itself has parent clocks in DT, set **`clk_np->dev`** before register — core calls **`rt_clk_get_array(dev)`** into **`clk_np->parents_clk`**. + +**`rt_clk_unregister()`** — rollback only; does not free cells. + +### Notifiers + +**`rt_clk_notifier_register(clk, notifier)`** — callback on **`RT_CLK_MSG_PRE_RATE_CHANGE`**, **`POST_RATE_CHANGE`**, **`ABORT_RATE_CHANGE`**. + +--- + +## Reference counting behavior + +- **`prepare_count`** / **`enable_count`** are per **`rt_clk_cell`**, shared by all consumers of that cell. +- **Enable** walks **parent** chain first; **disable** walks parent last (after local gate). +- **`rt_clk_put`** frees only the **consumer** `struct rt_clk` if it is not the cell’s primary binding — **does not** disable the clock; call **`rt_clk_disable_unprepare`** before remove. + +--- + +## SCMI clocks + +With **`RT_CLK_SCMI`**, firmware exposes clocks via **`struct rt_scmi_device`**. **`clk-scmi.c`** probes SCMI, enumerates clock IDs, and registers one **`rt_clk_node`** with many cells (`enable`/`disable`/`set_rate` via SCMI protocol). + +Consumers use the same **`rt_clk_get_*`** APIs; DT points at the SCMI clock provider node. Details: @ref page_device_scmi and `components/drivers/clk/clk-scmi.c`. + +--- + +## SoC drivers (Rockchip example) + +**`bsp/rockchip/dm/clk/`** implements **`rockchip,*-cru`** style controllers: PLL (`clk-rk-pll.c`), mux, gate, divider, composite, CPU clock, MMC phase, etc. Kconfig: **`RT_CLK_ROCKCHIP`**, per-SoC **`RT_CLK_ROCKCHIP_RK3588`**, … + +Consumer drivers (SPI, UART, ADC, PCIe, …) only call **`rt_clk_get_by_name`** + **`rt_clk_prepare_enable`** — they do not touch CCM registers directly. + +--- + +## Debug + +MSH ( **`RT_USING_CONSOLE`** + **`RT_USING_MSH`** ): + +```text +list_clk +``` + +Dumps each registered **cell**: name, enable/prepare counts, rate, consumer `dev_id` / `con_id`, parent name. + +--- + +## When to use CLK vs direct CCM writes + +| Use **`rt_clk_*`** | Direct register writes may remain | +| --- | --- | +| DT lists **`clocks = <&provider …>`** | Early asm/ROM before DM | +| Multiple drivers share PLL parents | One-off bring-up with no DT | +| **`assigned-clocks`** / DVFS coordination | Fixed, always-on root osc (often still modeled as **`fixed-clock`**) | + +## Engineer checklist + +1. **Provider**: implement **`rt_clk_ops`**, **`ofw_parse`**, **`rt_clk_register`**; publish **`clock-output-names`**. +2. **Consumer**: **`rt_clk_get_by_name`** → **`rt_clk_prepare_enable`** → **`rt_clk_get_rate`** for divider config. +3. **Remove**: **`rt_clk_disable_unprepare`** in reverse order, then **`rt_clk_put`** / **`rt_clk_array_put`**. +4. **Probe order**: provider must register before consumers resolve phandles (deferred probe handles late providers). +5. **Optional clocks**: missing phandle → **`NULL`**; do not enable. + +## Pitfalls + +- **`rt_clk_put` without disable** — leaves clocks gated on and burns power. +- **Wrong `#clock-cells`** — `ofw_get_clk` fails or picks wrong mux output. +- **Rate set before enable** — some IPs need **`RT_CLK_F_SET_RATE_GATE`** or enable-first ordering. +- **assigned-clocks on consumer vs provider** — defaults run on the node that **owns** the `assigned-clocks` property when **that** node’s provider registers. +- **ISR context** — do not call **`prepare`** / **`prepare_enable`** ( **`RT_DEBUG_NOT_IN_INTERRUPT`** ). + +## Detailed documents + +- @ref page_device_clk_fixed — **`fixed-clock`** provider + +## See also + +- `components/drivers/include/drivers/clk.h` +- `components/drivers/clk/clk.c` +- @ref page_device_ofw — phandle / `clock-indices` +- @ref page_device_scmi — SCMI clock protocol +- @ref page_device_reset — often sequenced with clock enable +- @ref page_device_regulator, @ref page_device_power_domain — rails with prepare diff --git a/documentation/6.components/device-driver/clk/fixed.md b/documentation/6.components/device-driver/clk/fixed.md new file mode 100755 index 00000000000..24e2e9ea814 --- /dev/null +++ b/documentation/6.components/device-driver/clk/fixed.md @@ -0,0 +1,120 @@ +@page page_device_clk_fixed Fixed clock provider + +# `fixed-clock` platform driver + +Implementation: **`components/drivers/clk/clk-fixed-rate.c`**. Driver name: **`clk-fixed-rate`**. Overview: @ref page_device_clk. + +Registers a **constant-frequency**, **always-on** clock with no gate, parent, or rate programming — the leaf most SoC trees hang PLLs from. + +--- + +## Device tree + +```dts +osc24m: fixed-clock { + compatible = "fixed-clock"; + #clock-cells = <0>; + clock-frequency = <24000000>; + clock-accuracy = <0>; /* optional, ppb */ + clock-output-names = "osc24m"; /* optional; becomes cell name */ +}; +``` + +Consumers: + +```dts +uart0: serial@40000000 { + clocks = <&osc24m>; + /* clock-names optional when only one clock */ +}; +``` + +With **`#clock-cells = <0>`**, the specifier in **`clocks = <&osc24m>`** has **no extra integers** (implicit index 0). + +--- + +## Probe flow + +1. **`rt_platform_driver_register`** via **`INIT_SUBSYS_EXPORT(fixed_clk_drv_register)`** — early registration so crystal nodes probe before CCM consumers. +2. **`fixed_clk_probe`**: `rt_calloc` **`struct clk_fixed`** (embeds **`rt_clk_node`** + one **`rt_clk_fixed_rate`**). +3. **`rt_dm_dev_prop_read_u32(dev, "clock-frequency", &val)`** — **required**; probe fails without it. +4. Optional **`clock-accuracy`**, **`clock-output-names`** → **`fcell.cell.name`**. +5. Wire **`cells[0]`**, **`fixed_clk_ops`** (only **`.recalc_rate`**). +6. **`rt_clk_register(&cf->parent)`** — may run **`rt_ofw_clk_set_defaults`** if the fixed-clock node has **`assigned-clocks*`** (unusual on leaf osc). + +--- + +## Data structures + +```c +struct clk_fixed { + struct rt_clk_node parent; + struct rt_clk_fixed_rate fcell; + struct rt_clk_cell *cells[1]; +}; + +struct rt_clk_fixed_rate { + struct rt_clk_cell cell; + rt_ubase_t fixed_rate; + rt_ubase_t fixed_accuracy; +}; +``` + +| Member | Role | +| --- | --- | +| **`fixed_rate`** | Hz from **`clock-frequency`** | +| **`fixed_accuracy`** | Optional ppb from DT | +| **`cell.ops->recalc_rate`** | Returns **`fixed_rate`**; ignores parent | + +No **`enable`/`disable`/`prepare`** — enabling is a no-op at ops level; framework refcount still works for consumers. + +--- + +## OFW matching + +```c +static const struct rt_ofw_node_id fixed_clk_ofw_ids[] = { + { .compatible = "fixed-clock" }, + { /* sentinel */ } +}; +``` + +Linux-compatible binding; same node can be shared across BSP DTS and upstream dt-bindings. + +--- + +## When to use vs SoC CCM + +| Use **`fixed-clock`** | Use SoC driver (e.g. `rockchip,*-cru`) | +| --- | --- | +| Crystal, RC oscillator, external clock input | PLL, mux, divider, gate | +| Rate fixed at boot | DVFS, dynamic reparent | +| No MMIO (or MMIO not managed here) | Large clock controller IP | + +--- + +## Consumer example + +```c +struct rt_clk *clk = rt_clk_get_by_name(dev, "uart_clk"); +/* or index 0 if no clock-names */ +rt_err_t err = rt_clk_prepare_enable(clk); +rt_ubase_t hz = rt_clk_get_rate(clk); +``` + +Fixed clocks rarely need **`rt_clk_set_rate`** — rate is fixed; **`rt_clk_get_rate`** reads **`recalc_rate`**. + +--- + +## Pitfalls + +- **Missing `clock-frequency`** — probe fails, phandle resolution returns NULL for consumers. +- **Wrong Hz in DT** — all derived PLL math wrong; UART baud, SDIO timing, etc. break silently. +- **Using `fixed-clock` for gated peripherals** — use a gate driver cell instead. +- **Name collisions** — **`clock-output-names`** should be unique enough for **`rt_clk_get_by_name`** fallback lookup. + +## See also + +- @ref page_device_clk +- `components/drivers/clk/clk-fixed-rate.c` +- `components/drivers/include/drivers/clk.h` diff --git a/documentation/6.components/device-driver/core/bus.md b/documentation/6.components/device-driver/core/bus.md new file mode 100755 index 00000000000..6169d142493 --- /dev/null +++ b/documentation/6.components/device-driver/core/bus.md @@ -0,0 +1,179 @@ +@page page_device_bus Bus and driver binding (core) + +# Device model: bus and driver + +RT-Thread **DM** binds **drivers** to **devices** through **`struct rt_bus`**: each bus keeps linked lists of devices and drivers, runs **`match`**, then **`probe`** / **`remove`** / **`shutdown`**. Most SoC drivers use the built-in **`platform`** bus; other stacks (e.g. **RPMsg**) register their own bus type on the same core. + +Headers: `components/drivers/include/drivers/core/bus.h`, `driver.h`. Core: `components/drivers/core/bus.c`. Reference bus: `components/drivers/core/platform.c`. + +Device-side helpers (`rt_dm_dev_iomap`, properties, IRQ): @ref page_device_dm. + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_DM`** | Enables **`bus.c`**, **`driver.c`**, platform bus, and DM helpers | +| **`RT_USING_DEV_BUS`** | Optional **`rt_device_bus_create`** — bus as **`RT_Device_Class_Bus`** device | +| **`RT_USING_OFW`** | Platform **`match`** uses **`compatible`**; devices carry **`dev->ofw_node`** | +| **`RT_USING_PINCTRL`** | Platform **`probe`** applies **`pinctrl-*`** before driver **`probe`** | +| **`RT_USING_CLK`** | Platform **`probe`** runs **`rt_ofw_clk_set_defaults`** | + +--- + +## Architecture + +``` + rt_bus_register(bus) + | + v + struct rt_bus (platform, rpmsg, ...) + dev_list --> rt_device + drv_list --> rt_driver + match / probe / remove / shutdown +``` + +**Binding** (`bus.c`): **`rt_bus_add_device`** walks **`drv_list`**; **`rt_bus_add_driver`** walks **`dev_list`**. **`match`** must succeed before **`dev->drv`** is set and **`bus->probe(dev)`** runs. Failed probe clears **`dev->drv`**. + +| Object | Role | +| --- | --- | +| **`struct rt_bus`** | Bus type and lifecycle hooks | +| **`struct rt_driver`** | Driver on a bus; **`ref_count`** = bound devices | +| **`struct rt_platform_device`** | Platform device embedding **`rt_device`** | + +--- + +## `struct rt_bus` callbacks + +| Callback | Contract | +| --- | --- | +| **`match`** | **Pure** — no HW access; return **`RT_TRUE`** to bind | +| **`probe`** | Bus-level setup (platform: pinctrl, clocks, power domain) then driver **`probe`** | +| **`remove`** | Teardown; platform calls **`pdrv->remove`** and **`rt_platform_ofw_free`** | +| **`shutdown`** | Invoked from **`rt_bus_shutdown()`** on system off | + +--- + +## Bus and driver API + +| API | Role | +| --- | --- | +| **`rt_bus_register`** | Register bus on global **`bus_nodes`** | +| **`rt_bus_add_device` / `rt_bus_add_driver`** | Add node and run matching | +| **`rt_bus_remove_device` / `rt_bus_remove_driver`** | Remove; driver busy if **`ref_count > 0`** | +| **`rt_bus_for_each_dev` / `rt_bus_for_each_drv`** | Iterate under spinlock | +| **`rt_bus_find_by_name`** | Find **`"platform"`** etc. | +| **`rt_bus_reload_driver_device`** | Move device to another bus | +| **`rt_bus_shutdown`** | Shutdown all devices on all buses | +| **`rt_driver_register` / `unregister`** | Driver attach/detach | + +--- + +## `RT_DRIVER_EXPORT` / platform + +```c +#define RT_DRIVER_EXPORT(driver, bus_name, mode) /* INIT_DEVICE_EXPORT */ +#define RT_PLATFORM_DRIVER_EXPORT(driver) \ + RT_DRIVER_EXPORT(driver, platform, BUILIN) +``` + +Example: + +```c +static const struct rt_ofw_node_id my_ids[] = { + { .compatible = "vendor,my-ip" }, + { /* sentinel */ }, +}; + +static struct rt_platform_driver my_drv = { + .name = "my-ip", + .ids = my_ids, + .probe = my_probe, + .remove = my_remove, +}; +RT_PLATFORM_DRIVER_EXPORT(my_drv); +``` + +--- + +## Platform bus + +**`INIT_CORE_EXPORT(platform_bus_init)`** registers **`name = "platform"`**. + +### `platform_match` + +1. **`rt_ofw_node_match(np, pdrv->ids)`** when **`dev->ofw_node`** is set. +2. Else compare **`pdev->name`** and **`pdrv->name`**. + +### `platform_probe` (before `pdrv->probe`) + +1. Pinctrl default / index 0 — @ref page_device_pinctrl +2. **`rt_ofw_clk_set_defaults`** — @ref page_device_clk +3. **`rt_dm_power_domain_attach(dev, RT_TRUE)`** — **`-RT_EEMPTY`** OK — @ref page_device_power_domain +4. **`pdrv->probe(pdev)`** — map MMIO, clocks, register **`rt_device`** + +| OFW API | Role | +| --- | --- | +| **`rt_platform_ofw_request`** | Instantiate from DT node | +| **`rt_platform_ofw_device_probe_child`** | Probe children | +| **`rt_platform_ofw_free`** | Free OFW state on remove | + +See @ref page_device_platform, @ref page_device_ofw. + +--- + +## Custom `rt_bus` + +| New bus when… | Use **platform** when… | +| --- | --- | +| Non-OFW enumeration (RPMsg, internal stack bus) | **`/soc`** MMIO + **`compatible`** | + +Implement **`match`/`probe`/`remove`**, **`rt_bus_register`** at init, then **`rt_bus_add_driver`** / **`rt_bus_add_device`**. + +--- + +## SDIO vs core + +MMC-specific DM: @ref page_device_sdio_dm. This page covers **`rt_bus` / `rt_driver`** only. + +--- + +## Power and probe order + +| Topic | Document | +| --- | --- | +| System shutdown | @ref page_device_dm_power | +| Power domains | @ref page_device_power_domain | +| Regulators | @ref page_device_regulator | + +Typical order: **regulator → power domain → clock/reset → `rt_dm_dev_iomap`**. + +--- + +## Engineer checklist + +1. Export driver with **`RT_PLATFORM_DRIVER_EXPORT`** (or subsystem **`RT_DRIVER_EXPORT`**). +2. Request device via **`rt_platform_ofw_request`** or manual register. +3. In **`probe`**: resources → HW → register functional device; unwind on error. +4. In **`remove`**: reverse order; disable IRQ before free. + +--- + +## Pitfalls + +- **`match` with side effects** breaks re-probe. +- **Double `rt_bus_add_device`** without remove. +- **`rt_driver_unregister` while devices bound** — **`-RT_EBUSY`**. +- **Link order is not probe order** — defer on missing phandles. + +--- + +## See also + +- @ref page_device_dm +- @ref page_device_dm_power +- @ref page_device_platform +- @ref page_device_ofw, @ref page_device_dtc +- @ref page_device_sdio_dm +- `components/drivers/core/bus.c`, `platform.c` diff --git a/documentation/6.components/device-driver/core/dm.md b/documentation/6.components/device-driver/core/dm.md new file mode 100755 index 00000000000..132c72ecf70 --- /dev/null +++ b/documentation/6.components/device-driver/core/dm.md @@ -0,0 +1,106 @@ +@page page_device_dm Device model helpers (dm.h) + +# Device-side DM helpers + +Header: `components/drivers/include/drivers/core/dm.h`. Implementation: `components/drivers/core/dm.c`. + +These APIs sit **below** bus matching (`documentation/6.components/device-driver/core/bus.md`) and **above** subsystem drivers: a `probe` typically calls **`rt_dm_dev_iomap`**, **`rt_dm_dev_get_irq`**, and property readers, then registers the functional device (block, netdev, etc.). + +## Driver / BSP quick notes + +| Topic | Guidance | +| --- | --- | +| **`ofw_node` required** | Most helpers return **`-RT_ENOSYS`** or **NULL** when **`dev->ofw_node`** is unset—bind the node in platform probe or **`rt_dm_dev_bind_fwdata`**. | +| **IRQ needs PIC** | **`rt_dm_dev_get_irq*`** is compiled only with **`RT_USING_OFW`** and **`RT_USING_PIC`**. | +| **Property errors** | Inline **`rt_dm_dev_prop_read_u32`** returns **`RT_EOK`** only when the read count is **> 0**; negative counts are cast to **`rt_err_t`**—check return values. | +| **IDA names** | **`rt_dm_ida_alloc`** is per **`master_id`**—do not mix block and other masters on the same **`struct rt_dm_ida`**. | + +## ID allocation (`rt_dm_ida`) + +Used when a subsystem needs stable numeric suffixes (e.g. **`nvme0`**, partition children): + +| API | Role | +| --- | --- | +| `rt_dm_ida_init(ida, id)` | Initialize bitmap + **`MASTER_ID_*`** from **`master_id.h`**. | +| `rt_dm_ida_alloc(ida)` | Allocate lowest free id in **0 … 255**. | +| `rt_dm_ida_take(ida, id)` | Reserve a specific id (returns **`RT_FALSE`** if taken). | +| `rt_dm_ida_free(ida, id)` | Release id. | +| `rt_dm_device_find(master_id, device_id)` | Find **`rt_device`** by master/slave id pair. | + +## Device naming + +| API | Role | +| --- | --- | +| `rt_dm_dev_set_name(dev, fmt, …)` | Format **`dev->parent.name`** (truncated to **`RT_NAME_MAX`**). | +| `rt_dm_dev_set_name_auto(dev, prefix)` | Append auto id from internal IDA. | +| `rt_dm_dev_get_name` / `rt_dm_dev_get_name_id` | Query current name / numeric suffix. | + +## Address and MMIO + +| API | Role | +| --- | --- | +| `rt_dm_dev_get_address_count` | Number of **`reg`** windows (OFW). | +| `rt_dm_dev_get_address` / `_by_name` | Physical base + size for index or named resource. | +| `rt_dm_dev_get_address_array` | Pack multiple regions into **`out_regs`**. | +| `rt_dm_dev_iomap` / `_by_name` | Map **`reg`** index or name to CPU virtual address. | + +Without **`RT_USING_OFW`**, address/IOMAP calls do not resolve hardware description. + +## Interrupts + +| API | Role | +| --- | --- | +| `rt_dm_dev_get_irq_count` | Number of interrupt specifiers. | +| `rt_dm_dev_get_irq(dev, index)` | Linux-style interrupt index (**0** = first cell group). | +| `rt_dm_dev_get_irq_by_name` | Match **`interrupt-names`**. | + +Install the handler in your driver after obtaining the vector; PIC maps OFW cells to a usable IRQ number. + +## Firmware properties + +Typed readers (arrays and index variants): + +- **`rt_dm_dev_prop_read_u8/u16/u32/u64`** and **`_array_index`** +- **`rt_dm_dev_prop_read_string`** / **`_array_index`** +- **`rt_dm_dev_prop_read_bool`** +- **`rt_dm_dev_prop_count_of_*`**, **`rt_dm_dev_prop_index_of_string`** +- **`rt_dm_dev_get_prop_fuzzy_name`** — suffix/substring match for property names + +**`rt_dm_dev_is_big_endian(dev)`** reflects the device endianness flag for MMIO accessors. + +## Bind driver private data to OFW node + +```c +void rt_dm_dev_bind_fwdata(rt_device_t dev, void *fw_np, void *data); +void rt_dm_dev_unbind_fwdata(rt_device_t dev, void *fw_np); +``` + +- Sets **`dev->ofw_node`** if it was NULL and assigns **`rt_ofw_data(np) = data`** (controller **`priv`**, **`rt_mbox_controller`**, etc.). +- Consumers resolve providers via **`rt_ofw_data(provider_np)`** after **`rt_platform_ofw_request`**. + +## SMP + +**`rt_dm_secondary_cpu_init()`** is the BSP hook for secondary CPUs in DM bring-up (weak/no-op if unused). + +## Typical `probe` sequence + +1. Match on **`compatible`** (platform bus). +2. **`rt_dm_dev_bind_fwdata(dev, np, priv)`**. +3. Optional **`rt_dm_power_domain_attach(dev, RT_TRUE)`** — see **`documentation/6.components/device-driver/core/power.md`**. +4. **`regs = rt_dm_dev_iomap(dev, 0)`**, **`irq = rt_dm_dev_get_irq(dev, 0)`**. +5. Read **`clocks` / `resets` / `dmas`** via property helpers or dedicated frameworks. +6. Register **`rt_device`** / subsystem object; on failure unwind in reverse order. + +## Pitfalls + +- **Double `bind_fwdata`** with conflicting **`data`** pointers confuses mailbox/clock clients—one owner per OFW node. +- **`iomap` without `reg`**: returns **NULL**; verify DTS and **`status = "okay"`**. +- **Assuming `prop_read_*` zero-fills**: on failure, output may be untouched—initialize locals first. + +## See also + +- Bus / driver binding: `documentation/6.components/device-driver/core/bus.md` +- Platform bus: `documentation/6.components/device-driver/platform/platform.md` +- OFW runtime: `documentation/6.components/device-driver/ofw/ofw.md` +- Power off / domains: `documentation/6.components/device-driver/core/power.md`, `documentation/6.components/device-driver/power_domain/power_domain.md` +- Header: `components/drivers/include/drivers/core/dm.h` diff --git a/documentation/6.components/device-driver/core/power.md b/documentation/6.components/device-driver/core/power.md new file mode 100755 index 00000000000..aca41f38eca --- /dev/null +++ b/documentation/6.components/device-driver/core/power.md @@ -0,0 +1,87 @@ +@page page_device_dm_power System power off and reboot (core) + +# DM power control (`power.h`) + +Header: `components/drivers/include/drivers/core/power.h`. Implementation: `components/drivers/core/power.c`. + +This layer handles **system-wide shutdown and reset**, **ordered power-off callbacks**, and **reboot mode** strings—not battery/charger UI (**`documentation/6.components/device-driver/power/supply.md`**) and not per-IP **power domains** (**`documentation/6.components/device-driver/power_domain/power_domain.md`**). + +## Driver / BSP quick notes + +| Topic | Guidance | +| --- | --- | +| **Callback context** | **`rt_dm_power_off_handler`** callbacks run from **`rt_hw_cpu_shutdown` / `rt_hw_cpu_reset`** paths—may be IRQ or dedicated power thread; **do not sleep** unless your platform guarantees thread context. | +| **Priority bands** | Handlers run **low index first** within each **`rt_dm_power_off_priority`** level, then next priority—register reboot-mode notifiers at **`RT_DM_POWER_OFF_PRIO_HIGH`** if they must run before generic GPIO poweroff. | +| **`-RT_EEMPTY` on domain attach** | **`rt_dm_power_domain_attach`** returning **`-RT_EEMPTY`** means no **`power-domains`** in DTS—often OK; RPMsg/platform code treats it as non-fatal. | + +## Power-off handlers + +```c +rt_err_t rt_dm_power_off_handler(struct rt_device *dev, int mode, int priority, + rt_err_t (*callback)(struct rt_device *)); +``` + +| `mode` | Use | +| --- | --- | +| `RT_DM_POWER_OFF_MODE_SHUTDOWN` | Full power down (PMIC off, etc.). | +| `RT_DM_POWER_OFF_MODE_RESET` | Warm reset path before **`rt_hw_cpu_reset`**. | + +| `priority` (increasing order) | Typical registrant | +| --- | --- | +| `RT_DM_POWER_OFF_PRIO_PLATFORM` | SoC-specific flush | +| `RT_DM_POWER_OFF_PRIO_LOW` / `DEFAULT` / `HIGH` | Drivers, storage sync | +| `RT_DM_POWER_OFF_PRIO_FIRMWARE` | Last-chance firmware handoff | + +Registration appends to a per-mode, per-priority singly linked list. On shutdown/reset the core walks **all priority levels** and invokes each callback; a non-**`RT_EOK`** return is logged but does not stop later handlers. + +**Examples:** `components/drivers/power/reset/gpio-poweroff.c` and **`gpio-restart.c`** register GPIO drivers via **`rt_dm_power_off_handler`**. + +## Machine hooks + +```c +extern void (*rt_dm_machine_shutdown)(void); +extern void (*rt_dm_machine_reset)(void); +``` + +BSP or SoC code sets these to enter PMIC/PSCI/firmware sequences after DM handlers run. + +## Reboot mode + +```c +rt_err_t rt_dm_reboot_mode_register(struct rt_device *dev, + rt_err_t (*callback)(struct rt_device *, char *cmd)); +rt_err_t rt_dm_reboot_mode_unregister(struct rt_device *dev); +void rt_hw_cpu_reset_mode(char *cmd); +``` + +- **`rt_hw_cpu_reset_mode`** updates the global command string (e.g. **`"recovery"`**) and calls **`rt_hw_cpu_reset()`**. +- On reset, handlers registered at **`RT_DM_POWER_OFF_MODE_RESET`** run—core init registers **`dm_reboot_notifiy`** at **`RT_DM_POWER_OFF_PRIO_HIGH`** to apply reboot-mode callbacks before the CPU resets. + +Use **`syscon-reboot-mode`** / NVMEM-backed mode drivers to persist **`cmd`** across boots. + +## Relation to power domains + +**`rt_dm_power_domain_attach(dev, on)`** (in **`power_domain.h`**) is called from **`platform` probe** and some buses (e.g. RPMsg) to power **on** the device’s IP block **before** driver init. That is independent of **system** shutdown: + +| API | Scope | +| --- | --- | +| `rt_dm_power_domain_*` | Per-device SoC power island while system is running. | +| `rt_dm_power_off_handler` | Whole-system off/reset. | +| `rt_power_supply_*` | Charger/battery reporting. | + +Recommended order for a consumer **`probe`**: **regulator enable → power domain on → clock/reset → `rt_dm_dev_iomap`**. + +## Pitfalls + +- **Missing `rt_dm_machine_shutdown`**: handlers run but hardware never powers off—set machine hooks in BSP. +- **Reboot mode without storage**: **`cmd`** is RAM-only unless a **`reboot-mode`** driver writes persistent storage. +- **GPIO poweroff polarity**: wrong active level looks like “hang on shutdown”—verify DTS and **`gpio-poweroff`** properties. + +## See also + +- Power domains: `documentation/6.components/device-driver/power_domain/power_domain.md` +- Power supply (battery/charger): `documentation/6.components/device-driver/power/supply.md` +- Regulator: `documentation/6.components/device-driver/regulator/regulator.md` +- Syscon reboot: `components/drivers/power/reset/syscon-reboot.c`, `syscon-reboot-mode.c` +- DM helpers: `documentation/6.components/device-driver/core/dm.md` +- Header: `components/drivers/include/drivers/core/power.h` diff --git a/documentation/6.components/device-driver/dma/dma.md b/documentation/6.components/device-driver/dma/dma.md new file mode 100755 index 00000000000..7a6cb213454 --- /dev/null +++ b/documentation/6.components/device-driver/dma/dma.md @@ -0,0 +1,73 @@ +@page page_device_dma DMA engine + +# DMA subsystem + +## 1. DMA controller (`rt_dma_controller` / `rt_dma_controller_ops`) + +Same style as **`ata/ata.md`**, for **`rt_dma_controller_register` / `rt_dma_controller_unregister`** (`components/drivers/dma/dma.c`). + +### `struct rt_dma_controller` (before register) + +| Member | Rule | Meaning | +| --- | --- | --- | +| `dev` | **Non-NULL** | Bound **`rt_device`**; checked at register; may **`rt_dm_dev_bind_fwdata`**. | +| `ops` | **Non-NULL** | **`rt_dma_controller_ops`**. | +| `dir_cap` | **At least one bit** | **`rt_dma_controller_add_direction`**; register fails if no direction is set. | +| `addr_mask` | **Caller** | **`RT_DMA_ADDR_MASK(n)`**, DMA-visible physical range. | +| `channels_nodes` / `mutex` | **Core** | Initialized in **`rt_dma_controller_register`**. | + +### `struct rt_dma_controller_ops` + +| Callback | Role | +| --- | --- | +| **`request_chan`** | Return **`rt_dma_chan`** for **`slave`** + **`fw_data`** (usually parsed **`dmas`/`dma-names`**). | +| **`release_chan`** | Pair of **`request_chan`**. | +| **`config` / `prep_*` / `start` / `pause` / `stop`** | Channel programming and run control. | + +Slaves use **`rt_dma_chan_request(dev, name)`**, then **`rt_dma_chan_config`**, **`rt_dma_prep_*`**, **`rt_dma_chan_start`**, etc. (see **`drivers/dma.h`**). + +### Buffers and `dma_map_ops` + +**`rt_dma_alloc` / `rt_dma_free` / `rt_dma_alloc_coherent`**, **`rt_dma_sync_*`**, **`rt_dma_device_set_ops`** — see **`drivers/dma.h`**. Coherent/CMA **pools** are documented in **`documentation/6.components/device-driver/dma/pool.md`**. + +### When DMA framework vs memcpy + +| Prefer **`rt_dma_chan_*`** when… | memcpy / PIO may be OK when… | +| --- | --- | +| **SDIO / SPI / UART / MAC** has **`dmas`** in DTS and high throughput. | **Bootloader-stage** tiny transfers or debug-only paths. | +| You need **scatter-gather** or **cyclic** audio/I2S. | Transfer is **< cache line** and latency matters more than CPU load. | + +### Slave driver checklist + +1. **`rt_dma_chan_request`** in `probe` with **`dma-names`** matching DTS. +2. **`rt_dma_chan_config`** with **`src_addr_width`**, **`direction`**, **`dst_maxburst`** aligned to FIFO depth—wrong burst causes underrun/overrun. +3. **`rt_dma_prep_*` + `rt_dma_chan_start`**: pass **physical or bus addresses** per `map_ops`—never feed unrelated virtual pointers unless your `dma_map_ops` handles them. +4. **`rt_dma_chan_release`** in `remove` even if `request` partially failed after cleanup. +5. **Completion**: ISR or **`rt_dma_chan_done`**—do not sleep in ISR; wake thread for heavy post-processing. + +### Controller author pitfalls + +- **`dir_cap` empty** → register fails—set at least one **`RT_DMA_DIR_*`** before register. +- **`addr_mask` too narrow** → peripherals above 4G cannot be targeted—set from SoC spec. + +--- + +## 2. API index (symbols) + +Quick index into **`drivers/dma.h`** (full definitions in the header). + +**Enums:** `enum rt_dma_slave_buswidth`, `enum rt_dma_transfer_direction`. + +**Macros:** `RT_DMA_ADDR_MASK(n)`, `RT_DMA_F_*`, `RT_DMA_PAGE_SIZE`. + +**Structs:** `struct rt_dma_chan`, `struct rt_dma_controller`, `struct rt_dma_controller_ops`, `struct rt_dma_map_ops`, `struct rt_dma_pool`, `struct rt_dma_slave_config`, `struct rt_dma_slave_transfer`. + +**Inlines:** `rt_dma_alloc_coherent`, `rt_dma_free_coherent`, `rt_dma_controller_add_direction`, `rt_dma_controller_set_addr_mask`, `rt_dma_device_is_coherent`, `rt_dma_device_set_ops`. + +**Functions:** `rt_dma_alloc` / `rt_dma_free`, `rt_dma_chan_config` / `done` / `pause` / `start` / `stop`, `rt_dma_chan_request` / `release`, `rt_dma_controller_register` / `unregister`, `rt_dma_pool_extract` / `install`, `rt_dma_prep_cyclic` / `memcpy` / `single`, `rt_dma_sync_in_data` / `out_data`. + +## See also + +- DMA memory pools: `documentation/6.components/device-driver/dma/pool.md` +- `components/drivers/include/drivers/dma.h` +- `components/drivers/dma/dma.c` diff --git a/documentation/6.components/device-driver/dma/pool.md b/documentation/6.components/device-driver/dma/pool.md new file mode 100755 index 00000000000..4326e5cb0fe --- /dev/null +++ b/documentation/6.components/device-driver/dma/pool.md @@ -0,0 +1,69 @@ +@page page_device_dma_pool DMA memory pools + +# DMA coherent / CMA pools + +Header: `struct rt_dma_pool` and **`rt_dma_pool_*`** in `components/drivers/include/drivers/dma.h`. Implementation: `components/drivers/dma/dma_pool.c`. + +**`rt_dma_alloc` / `rt_dma_free`** (and default **`rt_dma_map_ops`**) allocate from installed pools instead of the generic heap when **`RT_USING_DMA`** and memblock reservations are configured. Controllers and slaves still use **`documentation/6.components/device-driver/dma/dma.md`** for channel programming; pools only back **buffer memory**. + +## Driver / BSP quick notes + +| Topic | Guidance | +| --- | --- | +| **Boot reservation** | Call **`rt_dma_pool_extract`** early (memblock **`dma-pool`** region) before drivers **`rt_dma_alloc`**. | +| **Coherent vs CMA** | **`coherent_pool`** is carved from the **low** end of the reserved region; **CMA** uses the remainder—**`cma_size` must be ≥ `coherent_pool_size`**. | +| **32-bit DMA** | Pools below **4 GiB** set **`RT_DMA_F_32BITS`** on the pool—devices with limited addressing need buffers from these pools. | +| **`dma-coherent` DT** | **`rt_dma_device_is_coherent(dev)`** reads the property; map ops may skip flush on coherent devices. | + +## `struct rt_dma_pool` + +| Field | Meaning | +| --- | --- | +| `region` | Physical **`start` / `end`**, optional **`name`**. | +| `map` / `bits` | Page bitmap (**`ARCH_PAGE_SIZE`** granule). | +| `flags` | e.g. **`RT_DMA_F_32BITS`**, **`RT_DMA_F_NOCACHE`**. | +| `list` | Global pool list (internal). | + +## Public API + +| API | Role | +| --- | --- | +| **`rt_dma_pool_install(region)`** | Register a physical memory region as a pool; returns **`struct rt_dma_pool *`** or **NULL** on error. | +| **`rt_dma_pool_extract(cma_size, coherent_pool_size)`** | Find memblock region named **`dma-pool`**, split into **`coherent-pool`** + **`cma`** sub-regions, install both pools, mark memblock as consumed. Returns **`-RT_EEMPTY`** if no suitable region, **`-RT_EINVAL`** if sizes are inconsistent. | + +**`rt_dma_pool_install`** is also used directly when the BSP hands a fixed **`rt_region_t`** without going through memblock extract. + +## How allocation uses pools + +1. **`rt_dma_alloc(dev, size, &handle, flags)`** walks installed pools (under **`dma_pools_lock`**), picks a pool matching flags (coherent, 32-bit, device, etc.). +2. Default **`rt_dma_map_ops`**: + - **Coherent**: flush on **`sync_out_data`**, invalidate on **`sync_in_data`**. + - **Non-coherent**: physical address via **`rt_kmem_v2p`** without cache maintenance on sync-in. +3. With **`RT_USING_OFW`**, **`rt_dma_device_set_ops`** may install **`ofw_dma_map_*`** that translates CPU ↔ DMA bus addresses via **`rt_ofw_translate_cpu2dma`**. + +Slaves should use **`rt_dma_alloc_coherent`** for ring buffers the CPU and DMA both touch. + +## Boot flow (typical) + +1. Bootloader or memblock code reserves **`dma-pool`** in device tree / memblock. +2. Early init calls **`rt_dma_pool_extract(SIZE_MB(8), SIZE_MB(2))`** (example sizes). +3. Drivers **`probe`** and **`rt_dma_alloc`** for descriptor rings and payload buffers. + +## Debugging + +With **`RT_USING_CONSOLE`** and **`RT_USING_MSH`**, **`list_dma_pool`** prints installed regions (name, size, base). + +## Pitfalls + +- **Pool exhausted**: **`rt_dma_alloc`** returns **NULL**—size requests must fit contiguous free pages in a matching pool. +- **CMA smaller than coherent**: **`rt_dma_pool_extract`** rejects **`cma_size < coherent_pool_size`**. +- **High memory only**: if no sub-4G **`dma-pool`** chunk is large enough, extract may fall back with a warning—DMA to peripherals that cannot reach high memory will fail. +- **Double extract**: second call may find memblock already consumed—guard in BSP init. + +## See also + +- DMA engine and channels: `documentation/6.components/device-driver/dma/dma.md` +- OFW DMA range: `documentation/6.components/device-driver/ofw/ofw.md` +- NUMA / affinity: `documentation/6.components/device-driver/numa/numa.md` +- Source: `components/drivers/dma/dma_pool.c` +- Header: `components/drivers/include/drivers/dma.h` diff --git a/documentation/6.components/device-driver/framework/device.md b/documentation/6.components/device-driver/framework/device.md index bebf72e76c1..5ea718d71bc 100644 --- a/documentation/6.components/device-driver/framework/device.md +++ b/documentation/6.components/device-driver/framework/device.md @@ -1,5 +1,17 @@ @page page_device_framework I/O Device Framework +## For driver authors (short guide) + +| Layer | Responsibility | +| --- | --- | +| **Driver** | Map hardware, implement `init/read/write/control`, register `rt_device` (directly or via a sub-framework). | +| **Framework** | Share code across vendors (`serial`, `spi`, `pwm`, …)—override only deltas. | +| **App** | Never touch registers—use `rt_device_find` + standard APIs so swapping BSP does not break apps. | + +**Lifecycle**: `rt_device_register` → app `open` → `oflag` decides exclusive vs shared access → `close` should drain TX and disable IRQ/DMA. + +**Thread safety**: assume concurrent `read`/`write` unless your driver serializes with a mutex; document if not thread-safe. + Most embedded systems include some I/O (Input/Output) devices, data displays on instruments, serial communication on industrial devices, Flash or SD cards for saving data on data acquisition devices,as well as Ethernet interfaces for network devices, are examples of I/O devices that are commonly seen in embedded systems. This chapter describes how RT-Thread manages different I/O devices. @@ -21,7 +33,7 @@ The device driver framework layer is an abstraction of the same kind of hardware The device driver layer is a set of programs that drive the hardware devices to work, enabling access to hardware devices. It is responsible for creating and registering I/O devices. For devices with simple operation logic, you can register devices directly into the I/O Device Manager without going through the device driver framework layer. The sequence diagram is as shown below. There are mainly two points: * The device driver creates a device instance with hardware access capabilities based on the device model definition and registers the device with the `rt_device_register()` interface in the I/O Device Manager. -* The application finds the device through the`rt_device_find()` interface and then uses the I/O device management interface to access the hardware. +* The application finds the device through the `rt_device_find()` interface and then uses the I/O device management interface to access the hardware. ![Simple I/O Device Using Sequence Diagram](figures/io-call.png) diff --git a/documentation/6.components/device-driver/graphic/backlight.md b/documentation/6.components/device-driver/graphic/backlight.md new file mode 100755 index 00000000000..b854253e8e5 --- /dev/null +++ b/documentation/6.components/device-driver/graphic/backlight.md @@ -0,0 +1,53 @@ +@page page_device_graphic_backlight Backlight devices + +# Backlight subsystem + +Header: `components/drivers/include/drivers/backlight.h`. Core: **`components/drivers/graphic/backlight/backlight.c`**. PWM and GPIO backends: **`backlight-pwm.c`**, **`backlight-gpio.c`**. + +Graphic stack overview: @ref page_device_graphic_dm. + +## `struct rt_backlight_device` + +Extends **`rt_device`**. Properties include **`max_brightness`**, **`brightness`**, power state. Drivers implement brightness through ops registered with the core. + +### Core device behavior (`backlight.c`) + +| Op | Behavior | +| --- | --- | +| `read` | ASCII decimal of current brightness | +| `write` | Parse integer string, clamp to **`max_brightness`**, call **`rt_backlight_set_brightness`** | + +**`RT_DM_IDA_INIT(GRAPHIC_BACKLIGHT)`** assigns stable DM names on register. + +## PWM backlight (`pwm-backlight`) + +**Compatible**: typically **`pwm-backlight`** (see driver OFW table in source). + +| DT / property | Role | +| --- | --- | +| `pwms` | PWM channel for dimming | +| `brightness-levels` | Optional non-linear LUT | +| `default-brightness-level` | Boot brightness index | +| `enable-gpios` | Panel enable pin | +| `power-supply` | Optional **`rt_regulator`** before PWM | + +Probe enables regulator (if any), maps brightness levels, registers **`rt_backlight_device`**. + +## GPIO backlight (`gpio-backlight`) + +Simple on/off or few-level backlight using a GPIO line (see `backlight-gpio.c` OFW ids). + +## Driver / BSP quick notes + +| Topic | Guidance | +| --- | --- | +| Enable order | Regulator → enable GPIO → PWM duty | +| Thermal | High brightness may interact with @ref page_device_thermal policies | +| Userspace | Writing `"128\n"` to device node sets brightness (see core `write`) | + +## See also + +- @ref page_device_graphic_dm +- @ref page_device_regulator +- `components/drivers/graphic/backlight/backlight.c` +- `components/drivers/graphic/backlight/backlight-pwm.c` diff --git a/documentation/6.components/device-driver/graphic/framebuffer.md b/documentation/6.components/device-driver/graphic/framebuffer.md new file mode 100755 index 00000000000..b2edf214e2b --- /dev/null +++ b/documentation/6.components/device-driver/graphic/framebuffer.md @@ -0,0 +1,50 @@ +@page page_device_graphic_framebuffer Simple framebuffer (`simple-framebuffer`) + +# `simple-framebuffer` platform driver + +Implementation: **`components/drivers/graphic/framebuffer/fb-simple.c`**. Registers a **`struct rt_graphic_device`** from a fixed memory window described in device tree (Linux **`simple-framebuffer`** binding style). + +## Device tree (typical) + +```dts +framebuffer@0 { + compatible = "simple-framebuffer"; + reg = <0x0 0x00800000>; + width = <1920>; + height = <1080>; + stride = <7680>; + format = "a8r8g8b8"; /* see driver format table */ + clocks = <&disp_clk>; /* optional, RT_USING_CLK */ + power-supplies = <&vdd>; /* optional, RT_USING_REGULATOR */ +}; +``` + +## Probe flow + +1. Parse **`width`**, **`height`**, **`stride`**, **`format`** → internal **`simplefb_format`** (bits per pixel, RT graphic mode). +2. **`ioremap`** (or use physical) **`reg`** → **`screen_base`**, compute **`screen_size`**. +3. Optional **`rt_clk_array`** prepare/enable; optional regulator **`enable`** array. +4. Fill **`rt_graphic_device`** ops (`set_pixel`, `get_pixel`, `draw_hline`, etc.) and **`rt_device_register`**. + +## Supported formats + +Driver table maps string names (e.g. **`r5g6b5`**, **`a8r8g8b8`**, **`x8r8g8b8`**) to **`rt_graphic_info`** mode and bpp. Unknown **`format`** fails probe. + +## Remove + +Disable clocks/regulators, unmap framebuffer memory, unregister graphic device. + +## Driver / BSP quick notes + +| Topic | Guidance | +| --- | --- | +| Cache | CPU-accessible framebuffer RAM must use correct MMU cache attributes; if cacheable, maintain with **`rt_hw_cpu_dcache_ops`** — see @ref page_device_hwcache | +| Stride | Must match hardware line pitch (bytes), not always `width * bpp/8` | +| Primary display | Pair with @ref page_device_graphic_dm / `graphic_primary.c` if multiple heads | +| No GPU | This is **linear FB** only—no command queue; use a SoC GPU driver if you need acceleration | + +## See also + +- @ref page_device_graphic_dm +- `components/drivers/graphic/framebuffer/fb-simple.c` +- `components/drivers/graphic/graphic.c` diff --git a/documentation/6.components/device-driver/graphic/graphic_dm.md b/documentation/6.components/device-driver/graphic/graphic_dm.md new file mode 100644 index 00000000000..770d4d7fb07 --- /dev/null +++ b/documentation/6.components/device-driver/graphic/graphic_dm.md @@ -0,0 +1,251 @@ +@page page_device_graphic_dm Graphics (DM) + +# Graphics subsystem (device model) + +RT-Thread **graphic DM** (`components/drivers/include/drivers/graphic.h`, `components/drivers/graphic/graphic.c`) wraps display controllers as **`struct rt_graphic_device`**: multi-**plane** framebuffers, **EDID**, optional **backlight**, Linux-style **fb** ioctls, and legacy **`rt_device_graphic_ops`** via **`rt_graphic_device_switch_primary`**. + +App-facing pixel formats and **`RTGRAPHIC_CTRL_*`** live in **`drivers/classes/graphic.h`**. DM types, planes, and registration APIs are in **`graphic.h`**. + +Implementation split: + +| File | Role | +| --- | --- | +| **`graphic.c`** | Device register, `control`, planes, EDID parse, auto-update timer, OFW backlight | +| **`graphic_simple.c`** | Synthetic EDID + **`rt_graphic_device_simple_register`** | +| **`graphic_primary.c`** | **`rt_graphic_device_switch_primary`** → `set_pixel` / `draw_hline` on primary FB | +| **`framebuffer/fb-simple.c`** | DT **`simple-framebuffer`** — @ref page_device_graphic_framebuffer | + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_DM`** | Required parent | +| **`RT_USING_GRAPHIC`** | Core graphic stack (`graphic.c`, `graphic_primary.c`, `graphic_simple.c`) | +| **`RT_GRAPHIC_FB`** | Framebuffer drivers (e.g. **`simple-framebuffer`**) | +| **`RT_GRAPHIC_BACKLIGHT`** | Backlight class + PWM/GPIO — @ref page_device_graphic_backlight | +| **`RT_GRAPHIC_LOGO`** | Boot logo render on register — @ref page_device_graphic_logo | + +--- + +## Architecture + +``` + Controller driver (DSI, LCD, simple-fb, …) + | + | alloc_plane + plane_ops (update / fb_remap / fb_cleanup) + v + struct rt_graphic_device + primary_plane (required, one) + cursor_plane (optional, one) + overlay_nodes (0..N) + edid, ops, backlight, update_timer + | + | rt_graphic_device_register -> rt_device "fbN" + v + Apps: rt_device_find + RTGRAPHIC_CTRL_* / read-write FB + | + | rt_graphic_device_switch_primary(gdev) + v + dev->user_data = rt_device_graphic_ops (graphic_primary.c) +``` + +| Layer | Responsibility | +| --- | --- | +| **`rt_graphic_plane_ops`** | Hardware: allocate FB, push pixels to display (**`update`**), teardown (**`fb_cleanup`**) | +| **`rt_graphic_device_ops`** | Device-wide: **DPMS**, **vsync**, brightness, vendor **`control`** | +| **`graphic.c`** | Standard **`rt_device`** API, rect update, mode set, hotplug, periodic flush timer | + +--- + +## Core structures + +### `struct rt_graphic_plane` + +| Field | Meaning | +| --- | --- | +| **`type`** | **`PRIMARY`** (one), **`CURSOR`** (one), **`OVERLAY`** (list) | +| **`mode` / `modes[]`** | **`RTGRAPHIC_PIXEL_FORMAT_*`** from `classes/graphic.h` | +| **`framebuffer`**, **`line_length`**, **`screen_len`**, **`framebuffer_len`** | CPU-visible buffer; may be multi-buffer if **`framebuffer_len > screen_len`** | +| **`ops`** | Driver callbacks (see below) | + +### `struct rt_graphic_plane_ops` + +| Callback | When called | Contract | +| --- | --- | --- | +| **`fb_remap`** | Mode or resolution change | Allocate/map FB for **`mode`** + **`rect`**; set **`plane->framebuffer`** | +| **`fb_cleanup`** | Before remap / del_plane | Release HW resources (unmap, unref GPU resource) | +| **`update`** | **`RTGRAPHIC_CTRL_RECT_UPDATE`** or auto timer | Push **`rect`** region to display; **`width/height == 0`** may mean “position only” (cursor) | +| **`fb_pan_display`** | **`RTGRAPHIC_CTRL_PAN_DISPLAY`** / `open` | Point scanout at offset inside **`framebuffer`** | +| **`prop_set`** | Optional | Z-order, rotate, alpha (**`enum rt_graphic_plane_prop`**) | + +### `struct rt_graphic_device_ops` + +| Callback | Role | +| --- | --- | +| **`dpms_switch`** | **`RT_GRAPHIC_DPMS_*`** — often ties to **`rt_graphic_device_update_auto`** period | +| **`wait_vsync`** | **`RTGRAPHIC_CTRL_WAIT_VSYNC`** | +| **`set_brightness` / `get_brightness`** | Panel brightness; may defer to **`gdev->backlight`** | +| **`get_status`** | Optional connector status | +| **`current_plane`** | Override plane used for **`GET_INFO`** / updates (default: primary) | +| **`control`** | Vendor-specific commands (optional 3D / acceleration path) | + +### EDID + +**`struct edid`** in **`graphic.h`** mirrors VESA EDID layout. **`RTGRAPHIC_CTRL_GET_EXT`** copies **`gdev->edid`**. On register, if primary plane has no size, **`graphic_edid_res()`** picks mode from detailed timings. + +**`rt_graphic_device_simple_edid(gdev, w, h, refresh_hz)`** (`graphic_simple.c`) builds a minimal EDID when hardware has no DDC (fixed panels, simple bring-up). + +--- + +## Registration API + +| API | Role | +| --- | --- | +| **`rt_graphic_device_alloc_plane(gdev, priv_size, ops, modes, modes_nr, type)`** | Allocate plane; **`priv[0]`** tail for driver data | +| **`rt_graphic_device_add_plane(gdev, plane)`** | Attach primary/cursor/overlay; assigns **`plane_ida`** id | +| **`rt_graphic_device_register(gdev)`** | Requires **`primary_plane`**; names device **`fb%u`** via **`RT_DM_IDA`**; maps FB from EDID if size zero | +| **`rt_graphic_device_unregister(gdev)`** | DPMS off, stop timer, del all planes, **`rt_device_unregister`** | +| **`rt_graphic_device_simple_register(...)`** | Alloc primary plane + synthetic EDID + register (no full EDID path) | +| **`rt_graphic_device_simple_unregister(gdev)`** | Alias for **`unregister`** | + +**Naming**: **`RT_DM_IDA_INIT(GRAPHIC_FRAMEBUFFER)`** → devices **`fb0`**, **`fb1`**, … + +**OFW backlight**: during **`register`**, **`graphic_ofw_init`** resolves **`backlight`** phandle on **`dev->ofw_node`** and opens the backlight device. + +--- + +## Application / framework controls + +From **`classes/graphic.h`** (handled in **`graphic.c`** **`_graphic_control`**): + +| Command | Behavior | +| --- | --- | +| **`RTGRAPHIC_CTRL_RECT_UPDATE`** | **`plane->ops->update(plane, rect)`** on current plane | +| **`RTGRAPHIC_CTRL_SET_MODE`** | **`fb_remap`** if mode is in **`plane->modes[]`** | +| **`RTGRAPHIC_CTRL_GET_INFO`** | Fills **`rt_device_graphic_info`** from current plane | +| **`RTGRAPHIC_CTRL_POWERON` / `POWEROFF`** | DPMS + optional backlight power | +| **`RTGRAPHIC_CTRL_PAN_DISPLAY`** | Offset within framebuffer (double-buffer) | +| **`RTGRAPHIC_CTRL_WAIT_VSYNC`** | Driver **`wait_vsync`** | +| **`RT_DEVICE_CTRL_CURSOR_*`** | Cursor plane position / bitmap | +| **`FBIOGET_VSCREENINFO` / `FBIOPUT_VSCREENINFO`** | Linux fb var screeninfo (when enabled) | + +**Auto flush**: **`rt_graphic_device_update_auto(gdev, ms)`** starts a periodic timer (**default idea: `RT_GRAPHIC_UPDATE_MS` = 16**) that calls **`update`** on primary, overlays, and cursor. **`rt_graphic_device_enter` / `leave`** stop/restart the timer around modeset (hotplug). + +**Hotplug**: **`rt_graphic_device_hotplug_event(gdev)`** re-parses EDID size, **`fb_remap`** primary, then fires **`event_notify`**. + +--- + +## Primary display helpers (`graphic_primary.c`) + +```c +struct rt_device_graphic_ops *rt_graphic_device_switch_primary(struct rt_graphic_device *gdev); +``` + +- Sets global **`primary_gdev`** and returns **`rt_device_graphic_ops`** matching primary plane **bpp** (8/16/24/32/64). +- Legacy macro in **`graphic.h`**: **`rt_graphix_ops(dev)`** calls **`switch_primary`** then reads **`dev->user_data`**. +- Used by **finsh / LVGL / logo** code that draws via **`set_pixel`** / **`draw_hline`** without **`RTGRAPHIC_CTRL_RECT_UPDATE`**. + +After **`register`**, call **`switch_primary`** on the head you want for **`rt_graphix_ops`**. + +--- + +## Typical controller `probe` (SoC) + +```c +static const rt_uint32_t my_modes[] = { + RTGRAPHIC_PIXEL_FORMAT_RGB565, + RTGRAPHIC_PIXEL_FORMAT_ARGB888, +}; + +static const struct rt_graphic_plane_ops my_plane_ops = { + .fb_remap = my_fb_remap, + .fb_cleanup = my_fb_cleanup, + .update = my_fb_update, +}; + +static rt_err_t my_probe(struct rt_platform_device *pdev) +{ + struct my_drv *priv; + struct rt_graphic_plane *plane; + rt_err_t err; + + priv = rt_dm_dev_iomap(pdev->parent.parent, 0); /* or embed in pdev->priv */ + /* clocks, resets, regulator — @ref page_device_clk, @ref page_device_reset */ + + priv->gdev.ops = &my_gdev_ops; + + plane = rt_graphic_device_alloc_plane(&priv->gdev, sizeof(*priv), + &my_plane_ops, my_modes, RT_ARRAY_SIZE(my_modes), + RT_GRAPHIC_PLANE_TYPE_PRIMARY); + if (!plane) + return -RT_ENOMEM; + + err = rt_graphic_device_add_plane(&priv->gdev, plane); + if (err) + goto err; + + /* Fill EDID from DDC or rt_graphic_device_simple_edid() */ + err = rt_graphic_device_register(&priv->gdev); + if (err) + goto err; + + rt_graphic_device_switch_primary(&priv->gdev); + rt_graphic_device_update_auto(&priv->gdev, RT_GRAPHIC_UPDATE_MS); + return RT_EOK; +} +``` + +**`simple-framebuffer`**: maps fixed **`reg`** RAM, implements plane ops as CPU memcpy — see @ref page_device_graphic_framebuffer. + +--- + +## PHY, clocks, backlight + +| Resource | Document | +| --- | --- | +| **Pixel / link clock** | @ref page_device_clk | +| **DSI/DP PHY** | @ref page_device_phye | +| **Panel backlight** | @ref page_device_graphic_backlight (`backlight` DT property) | +| **Coherent FB / cache** | @ref page_device_hwcache, @ref page_device_dma | + +**Bring-up order**: regulator → clock → panel init → **`register`** → backlight **`POWERON`** → **`RTGRAPHIC_CTRL_POWERON`**. + +--- + +## Engineer checklist + +1. Implement **`rt_graphic_plane_ops`** (at minimum **`fb_remap`**, **`update`**, **`fb_cleanup`**). +2. **`add_plane` PRIMARY** before **`register`**; cursor/overlay after if needed. +3. Populate **`edid`** or use **`simple_register`** / **`simple_edid`**. +4. **`register`** → **`switch_primary`** for legacy drawing APIs. +5. **`remove`**: **`unregister`** (frees planes); release clocks/reset/regulator in reverse order. +6. Hotplug: call **`hotplug_event`** after EDID/mode change under **`enter`/`leave`**. + +--- + +## Pitfalls + +- **Missing `update` with auto timer off**: apps must **`RTGRAPHIC_CTRL_RECT_UPDATE`** after CPU writes or nothing reaches the panel. +- **Primary plane size 0 at register**: core tries EDID **`fb_remap`** — bogus EDID causes probe failure. +- **Two PRIMARY planes**: **`add_plane`** returns **`-RT_EINVAL`**. +- **DMA / cache**: CPU-drawn FB must use correct MMU attributes; sync cache before **`update`** if scanout is non-coherent — @ref page_device_hwcache. +- **`switch_primary` global**: only one **`primary_gdev`** — multi-head apps should use **`rt_device_find("fb1")`** + graphic ctrl, not **`rt_graphix_ops`** alone. +- **DPMS off without stopping updates**: use **`rt_graphic_device_update_auto(gdev, 0)`** or **`enter`** during suspend. + +--- + +## Detailed documents + +- @ref page_device_graphic_framebuffer — **`simple-framebuffer`** +- @ref page_device_graphic_backlight — PWM/GPIO backlight +- @ref page_device_graphic_logo — boot logo (PPM) + +## See also + +- `components/drivers/include/drivers/graphic.h` +- `components/drivers/include/drivers/classes/graphic.h` +- `components/drivers/graphic/graphic.c`, `graphic_simple.c`, `graphic_primary.c` +- @ref page_device_dm — `rt_dm_dev_iomap`, naming +- @ref page_device_platform — `RT_PLATFORM_DRIVER_EXPORT` diff --git a/documentation/6.components/device-driver/graphic/logo.md b/documentation/6.components/device-driver/graphic/logo.md new file mode 100755 index 00000000000..fe0c4aff160 --- /dev/null +++ b/documentation/6.components/device-driver/graphic/logo.md @@ -0,0 +1,202 @@ +@page page_device_graphic_logo Boot Logo + +# Boot Logo (PPM) + +Implementation: **`components/drivers/graphic/logo/logo.c`**. Build converts a **PPM** asset into **`logo.inc`** (RGB triplets) at compile time. At runtime **`rt_graphic_logo_render()`** draws the logo centered on the framebuffer when a **`rt_graphic_device`** registers. + +Graphic stack overview: @ref page_device_graphic_dm. + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_GRAPHIC_LOGO`** | Enable logo subsystem; selects **`RT_GRAPHIC_FB`** | +| **`RT_GRAPHIC_LOGO_NONE`** | No built-in image — use **`rt_graphic_logo_change()`** at runtime | +| **`RT_GRAPHIC_LOGO_RT_THREAD_CLUT224`** | Embed **`logo-rt-thread-clut224.ppm`** (default) | +| **`RT_GRAPHIC_LOGO_RT_THREAD_WHITE_CLUT224`** | White variant PPM | +| **`SOC_DM_GRAPHIC_LOGO_DIR`** | BSP adds custom logo choices via **`Kconfig`** / **`Kconfig.path`** | + +Custom BSP logos: define **`RT_GRAPHIC_LOGO_`** in Kconfig and **`RT_GRAPHIC_LOGO__PATH`** (string, path to `.ppm`). **`logo/SConscript`** parses the file and generates **`logo.inc`**. + +--- + +## When the logo is drawn + +``` +rt_graphic_device_register(gdev) + | + v +rt_graphic_logo_render(gdev) /* graphic.c; errors logged at DBG only */ + | + +-- open fb device, GET_INFO, POWERON + +-- blit PPM RGB -> framebuffer (centered) + +-- RTGRAPHIC_CTRL_RECT_UPDATE + WAIT_VSYNC + +-- startup_logo = NULL (one-shot; frees built-in use) +``` + +- Runs when a graphic device successfully **registers** (see **`graphic.c`**). +- Requires **`RT_GRAPHIC_LOGO`** and non-empty **`startup_logo`** (built-in or **`rt_graphic_logo_change`**). +- If logo is larger than **`var.xres` / `var.yres`**, render fails with **`-RT_EINVAL`**. + +--- + +## Runtime API (`graphic.h`) + +### `rt_graphic_logo_change` + +```c +rt_err_t rt_graphic_logo_change(void *data, int width, int height, int color_max); +``` + +| Arguments | Meaning | +| --- | --- | +| **`data`** | Pointer to **packed RGB** bytes (`width * height * 3`), same order as PPM pixmap data | +| **`width` / `height`** | Logo size in pixels | +| **`color_max`** | Max channel value in **`data`** (PPM **max pixel value**, e.g. **255** or **15**) | + +| Call pattern | Effect | +| --- | --- | +| **`data == NULL` && all sizes 0** | Disable logo (**`startup_logo = NULL`**) | +| Valid **`data` + dimensions** | Replace logo used on next **`rt_graphic_logo_render`** | +| Otherwise | **`-RT_EINVAL`** | + +Use with **`RT_GRAPHIC_LOGO_NONE`** or to override the built-in clut224 asset before the first **`register`**. + +### `rt_graphic_logo_render` + +```c +rt_err_t rt_graphic_logo_render(struct rt_graphic_device *gdev); +``` + +Normally called from **`rt_graphic_device_register()`** — drivers rarely invoke it directly. Steps: + +1. **`rt_device_open`** on the graphic **`rt_device`** +2. **`FBIOGET_VSCREENINFO`** — grayscale vs RGB bitfield layout +3. **`RTGRAPHIC_CTRL_GET_INFO`** — framebuffer base + pitch +4. **`RTGRAPHIC_CTRL_POWERON`** +5. Center logo: **`rect.x/y = (screen - logo) / 2`** +6. Per-pixel color remap into **`bits_per_pixel`** +7. **`RTGRAPHIC_CTRL_RECT_UPDATE`** + **`RTGRAPHIC_CTRL_WAIT_VSYNC`** +8. Clears **`startup_logo`** (one-shot) + +Without **`RT_GRAPHIC_LOGO`**, both APIs compile to inline no-ops returning **`RT_EOK`**. + +--- + +## Build-time PPM → `logo.inc` + +**`logo/SConscript`** (when a logo path is selected): + +1. Read PPM header (**P1–P3** ASCII path in script). +2. Parse **`width height`**, **`max_pixel_value`**, pixel stream. +3. Emit **`logo.inc`**: rows of **`R,G,B`** byte triplets. +4. Define **`__STARTUP_LOGO_WIDTH__`**, **`__STARTUP_LOGO_HEIGHT__`**, **`__STARTUP_LOGO_COLOR_MAX__`**. + +**`logo.c`** includes **`logo.inc`** into **`builtin_logo[]`** when all three macros are non-zero. + +--- + +## Supported PPM variants + +| Magic | Kind | Encoding | Notes | +| --- | --- | --- | --- | +| P1 | Bitmap | ASCII | Build script ASCII path | +| P2 | Graymap | ASCII | Build script ASCII path | +| P3 | Pixmap | ASCII | Typical for clut224 logos | +| P4 | Bitmap | Binary | Convert to P3 for embed | +| P5 | Graymap | Binary | Convert to P3 for embed | +| P6 | Pixmap | Binary | Use **`pnmnoraw`** → P3 for SConscript | + +Runtime rendering always uses **RGB triplets** in memory. + +--- + +## ASCII PPM layout + +```text +# : P1 / P2 / P3 … +# + + + +``` + +### Example (P3, 4×4) + +```text +P3 +# Standard 15-color test logo +4 4 +15 +0 0 0 0 0 0 0 0 0 15 0 15 +0 0 0 0 15 7 0 0 0 0 0 0 +0 0 0 0 0 0 0 15 7 0 0 0 +15 0 15 0 0 0 0 0 0 0 0 0 +``` + +| | Col 0 | Col 1 | Col 2 | Col 3 | +| --- | --- | --- | --- | --- | +| **Row 0** | 0,0,0 | 0,0,0 | 0,0,0 | 15,0,15 | +| **Row 1** | 0,0,0 | 0,15,7 | 0,0,0 | 0,0,0 | +| **Row 2** | 0,0,0 | 0,0,0 | 0,15,7 | 0,0,0 | +| **Row 3** | 15,0,15 | 0,0,0 | 0,0,0 | 0,0,0 | + +--- + +## Generating a logo file + +1. Convert source image to PPM: + +```bash +magick .png .ppm +``` + +2. Reduce to **224** colors (clut224 path): + +```bash +ppmquant 224 .ppm > _224.ppm +``` + +3. ASCII PPM for SConscript: + +```bash +pnmnoraw _224.ppm > logo--clut224.ppm +``` + +4. Point BSP **`RT_GRAPHIC_LOGO__PATH`** at the file, or replace in-tree **`logo-rt-thread-clut224.ppm`**. + +--- + +## Offline preview + +Open **`components/drivers/graphic/logo/logo.html`** in a browser to preview a PPM before building. + +--- + +## BSP / integration quick notes + +| Topic | Guidance | +| --- | --- | +| **Asset size** | Large PPM bloats **`logo.inc`** and slows boot — keep resolution modest. | +| **Display readiness** | Logo runs at **`register`** — enable backlight after first frame if needed (@ref page_device_graphic_backlight). | +| **Format** | Invalid PPM fails SConscript at build time; validate in CI. | +| **One-shot** | After first render, **`startup_logo`** is cleared — call **`rt_graphic_logo_change`** again for another splash. | + +--- + +## Pitfalls + +- **Logo bigger than mode**: **`-RT_EINVAL`** — match PPM to panel resolution. +- **`RT_GRAPHIC_LOGO_NONE` without `logo_change`**: no logo (by design). +- **Binary P6 without conversion**: SConscript may not parse — use **`pnmnoraw`**. + +--- + +## See also + +- @ref page_device_graphic_dm +- @ref page_device_graphic_framebuffer +- `components/drivers/graphic/logo/logo.c`, `logo/SConscript` +- `components/drivers/include/drivers/graphic.h` diff --git a/documentation/6.components/device-driver/hwcache/hwcache.md b/documentation/6.components/device-driver/hwcache/hwcache.md new file mode 100644 index 00000000000..d6c6ee664a9 --- /dev/null +++ b/documentation/6.components/device-driver/hwcache/hwcache.md @@ -0,0 +1,216 @@ +@page page_device_hwcache Hardware cache (hwcache) + +# CPU cache maintenance (hwcache) + +On RT-Thread, **CPU D-cache / I-cache** maintenance for DMA coherence, MMU table updates, and dynamic code loading is done through architecture code in **`libcpu`** and, when **`RT_USING_HWCACHE`** is enabled, the DM helper module **`components/drivers/hwcache/`**. + +**Most drivers in the tree call the BSP APIs** in **`include/rthw.h`** (`rt_hw_cpu_dcache_ops`, `rt_hw_cpu_icache_ops`). The **`rt_hwcache_*`** functions in **`drivers/hwcache.h`** are a thin DM layer that forwards to **`struct rt_hwcache_ops`** pointers filled from device tree stubs or SoC code. + +Documentation path: **`hwcache/hwcache.md`** (not `cache/` — the subsystem name is **hwcache**). + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_CACHE`** | Enables **`rthw.h`** cache APIs (required for real flush/invalidate) | +| **`RT_USING_HWCACHE`** | Builds **`components/drivers/hwcache/hwcache.c`**; depends on **`RT_USING_DM`** and **`RT_USING_CACHE`** | +| **`SOC_DM_HWCACHE_DIR`** | BSP may add extra Kconfig under SoC DM hwcache drivers | + +If **`RT_USING_CACHE`** is off, **`rthw.h`** defines **`rt_hw_cpu_*_ops`** as empty macros — maintenance becomes no-ops. + +--- + +## Two API layers (do not mix up) + +``` + Application / NVMe / SDIO / DMA pool / … + │ + ▼ + rt_hw_cpu_dcache_ops() / rt_hw_cpu_icache_ops() ← include/rthw.h (usual) + │ + ▼ + libcpu/*/cache.c (per architecture) + + Optional parallel path when RT_USING_HWCACHE: + │ + ▼ + rt_hwcache_dcache_ops() / rt_hwcache_icache_ops() ← drivers/hwcache.h + │ + ▼ + rt_dm_cpu_dcache_ops / rt_dm_cpu_icache_ops + │ + ▼ + struct rt_hwcache_ops (flush/invalidate from OFW stub or SoC init) +``` + +| Layer | Header | Typical caller | +| --- | --- | --- | +| **Architecture (primary)** | `rthw.h` | **`nvme`**, **`sdio`**, **`ahci`**, **`ufs`**, **`dma_pool`**, USB glue, **`lwp`**, **`dlmodule`** | +| **DM hwcache** | `drivers/hwcache.h` | Code that explicitly uses **`rt_hwcache_*`** or SoC **`RT_HWCACHE_OFW_DECLARE`** setup | + +Unless your BSP connects **`rt_dm_cpu_dcache_ops`** to the same implementation as **`rt_hw_cpu_dcache_ops`**, calling only one layer may be insufficient. **Driver authors: use `rt_hw_cpu_dcache_ops` unless the BSP documents `rt_hwcache_*` as the canonical path.** + +--- + +## Architecture API (`rthw.h`) + +Enabled when **`RT_USING_CACHE`** is set. + +### Opcodes + +```c +enum RT_HW_CACHE_OPS { + RT_HW_CACHE_FLUSH = 0x01, /* clean / write-back to RAM */ + RT_HW_CACHE_INVALIDATE = 0x02, /* discard cached copy, refetch from RAM */ +}; +``` + +**`RT_CPU_CACHE_LINE_SZ`** (default 32) is the usual alignment hint for maintenance ranges. + +### D-cache + +| Function | Role | +| --- | --- | +| **`rt_hw_cpu_dcache_enable()`** | Enable D-cache | +| **`rt_hw_cpu_dcache_disable()`** | Disable D-cache | +| **`rt_hw_cpu_dcache_status()`** | Non-zero if enabled | +| **`rt_hw_cpu_dcache_ops(ops, addr, size)`** | Flush or invalidate **`[addr, addr+size)`** | + +### I-cache + +| Function | Role | +| --- | --- | +| **`rt_hw_cpu_icache_enable()`** | Enable I-cache | +| **`rt_hw_cpu_icache_disable()`** | Disable I-cache | +| **`rt_hw_cpu_icache_status()`** | Non-zero if enabled | +| **`rt_hw_cpu_icache_ops(ops, addr, size)`** | Typically **invalidate** after loading code | + +### Example (DMA TX buffer) + +```c +/* CPU filled buffer; device DMA will read RAM */ +rt_hw_cpu_dcache_ops(RT_HW_CACHE_FLUSH, buf, len); + +/* After device DMA wrote RAM; CPU will read buffer */ +rt_hw_cpu_dcache_ops(RT_HW_CACHE_INVALIDATE, buf, len); +``` + +--- + +## DM hwcache API (`drivers/hwcache.h`) + +Implementation: **`components/drivers/hwcache/hwcache.c`**. + +### `struct rt_hwcache_ops` + +| Callback | Role | +| --- | --- | +| **`enable` / `disable`** | Turn cache on/off | +| **`status`** | Query enabled state | +| **`flush(vaddr, size)`** | Clean D-cache (or unified) for region | +| **`invalidate(vaddr, size)`** | Invalidate region | + +Global pointers (set by platform init or OFW stub): + +- **`rt_dm_cpu_dcache_ops`** +- **`rt_dm_cpu_icache_ops`** + +### `rt_hwcache_*` wrappers + +| Function | Maps to | +| --- | --- | +| **`rt_hwcache_dcache_enable/disable/status`** | **`rt_dm_cpu_dcache_ops`** | +| **`rt_hwcache_dcache_ops(op, addr, size)`** | **`flush`** if **`op == RT_HW_CACHE_FLUSH`**, **`invalidate`** if **`RT_HW_CACHE_INVALIDATE`** | +| **`rt_hwcache_icache_*`** | Same for **`rt_dm_cpu_icache_ops`** | + +If the corresponding **`rt_dm_cpu_*_ops`** is **`NULL`**, calls are no-ops (safe but ineffective). + +### Initialization + +```c +rt_err_t rt_hwcache_init(void); +``` + +With **`RT_USING_OFW`**, walks all DT nodes and runs **`rt_ofw_stub_probe_range`** for stubs in the **`hwcache`** linker range (**`RT_OFW_STUB_RANGE_EXPORT(hwcache, ...)`** in `hwcache.c`). Stub handlers typically assign **`rt_dm_cpu_dcache_ops`** / **`rt_dm_cpu_icache_ops`**. + +### OFW registration macro + +```c +RT_HWCACHE_OFW_DECLARE(name, ids, handler); +``` + +Expands to **`RT_OFW_STUB_EXPORT(name, ids, hwcache, handler)`**. **`handler(struct rt_ofw_node *np, const struct rt_ofw_node_id *id)`** should install **`struct rt_hwcache_ops`** into the global pointers. See @ref page_device_ofw (stub sections). + +SoC-specific drivers may live under **`SOC_DM_HWCACHE_DIR`** (per **`hwcache/Kconfig`**). + +--- + +## DMA and coherence + +| Path | Cache maintenance | +| --- | --- | +| **`dma-coherent` DT property** | **`rt_dma_device_is_coherent`** — map ops may skip explicit flush (platform-dependent) | +| **Coherent DMA pool** (`dma_pool.c`) | **`sync_out`**: **`rt_hw_cpu_dcache_ops(FLUSH)`**; **`sync_in`**: **`INVALIDATE`** | +| **Non-coherent pool** | Physical address only; CPU must still maintain if CPU touches the buffer | + +Coordinate with @ref page_device_dma and @ref page_device_dma_pool — avoid **double flush** and **invalidate-before-flush** on dirty TX buffers. + +--- + +## Drivers that use cache maintenance (examples) + +| Subsystem | File | Typical pattern | +| --- | --- | --- | +| **NVMe** | `nvme/nvme.c` | Flush PRP/list buffers before doorbell; invalidate after read | +| **SDHCI / SDIO** | `sdio/dev_sdhci.c`, `sdio-dw.c` | Flush before TX DMA; invalidate after RX | +| **AHCI** | `ata/ahci.c` | Flush/invalidate command tables and DMA buffers | +| **UFS** | `ufs/ufs.c` | Flush UTRD/UCD before run; invalidate after completion | +| **DMA PL330** | `dma/dma-pl330.c` | Flush microcode buffer | +| **USB** | CherryUSB glue | Platform-specific flush/invalidate hooks | +| **GICv3 ITS** | `pic/pic-gicv3-its.c` | Flush ITS command/ITT tables | +| **DFS pcache** | `dfs_v2/dfs_pcache.c` | Flush + I-invalidate on mapped pages | +| **Dynamic load** | `libc/dlmodule.c`, `lwp/` | Flush data, invalidate I-cache for new code | + +These use **`rt_hw_cpu_*_ops`** from **`rthw.h`**, not **`rt_hwcache_*`**, unless your port unifies them. + +--- + +## Use cases + +1. **Non-coherent DMA** — CPU writes → **flush** before device read; device writes → **invalidate** before CPU read. +2. **MMU / page tables** — flush page table memory after CPU fills entries (see **`libcpu/*/mmu.c`**). +3. **Self-modifying / relocated code** — flush D-cache if instructions were stored through D-cache, then **invalidate I-cache**. +4. **Framebuffer / uncached mapping** — prefer correct **MMU memory attributes** (@ref page_device_graphic_framebuffer); use explicit ops only when mapping is cacheable. + +--- + +## Driver / BSP quick notes + +| Topic | Guidance | +| --- | --- | +| **Prefer `rt_hw_cpu_*`** | Matches existing storage and DMA drivers in **`components/drivers`** | +| **Range size** | Cover full DMA buffer; align to cache line when port requires it | +| **TX ordering** | **Flush** before kicking device — never **invalidate** dirty lines first | +| **RX ordering** | **Invalidate** before CPU reads device-written RAM | +| **Harvard cores** | After loading code via data path: flush D + invalidate I | +| **`rt_hwcache_init`** | Call during early init if SoC uses **`RT_HWCACHE_OFW_DECLARE`** to publish ops | +| **Performance** | Scope maintenance to touched regions; do not flush entire RAM | + +## Pitfalls + +- **`rt_hwcache_*_disable()`** in **`hwcache.c`** currently invokes **`ops->enable`** in **`hwcache_disable()`** (likely a bug). Prefer **`rt_hw_cpu_dcache_disable()`** or fix SoC ops until upstream is corrected. +- **Double maintenance** with **`rt_dma_sync_*`** and manual **`rt_hw_cpu_dcache_ops`** on the same buffer. +- **`RT_USING_CACHE` off** — silent no-ops; DMA corruption on non-coherent SoCs. +- **Assuming `rt_hwcache_*` runs on all boards** — only effective after **`rt_dm_cpu_*_ops`** are assigned. +- **I-cache only invalidate** after external loaders without D-cache flush when stores went through D-cache. + +## See also + +- `components/drivers/include/drivers/hwcache.h` +- `components/drivers/hwcache/hwcache.c` +- `include/rthw.h` — **`RT_HW_CACHE_*`**, **`rt_hw_cpu_dcache_ops`** +- @ref page_device_dma +- @ref page_device_dma_pool +- @ref page_device_ofw — OFW stub mechanism diff --git a/documentation/6.components/device-driver/hwspinlock/hwspinlock.md b/documentation/6.components/device-driver/hwspinlock/hwspinlock.md new file mode 100755 index 00000000000..9dd085b1008 --- /dev/null +++ b/documentation/6.components/device-driver/hwspinlock/hwspinlock.md @@ -0,0 +1,215 @@ +@page page_device_hwspinlock Hardware spinlock + +# Hardware spinlock (hwspinlock) + +RT-Thread **hwspinlock** (`components/drivers/hwspinlock/`) models **SoC hardware mutex registers** shared across **heterogeneous CPUs** (AMP), **remote processors**, or firmware—not the same as a single-OS **`rt_spinlock`**. A **bank** (one controller IP) exports many **`rt_hwspinlock`** instances; consumers acquire through **`rt_hwspin_*`** or device-tree **`hwlocks`**. + +Public API: **`components/drivers/include/drivers/hwspinlock.h`**. DM types: **`components/drivers/hwspinlock/hwspinlock_dm.h`**. Core: **`components/drivers/hwspinlock/hwspinlock.c`**. + +SoC-specific controllers are added under **`SOC_DM_HWSPINLOCK_DIR`** in the BSP Kconfig. + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_DM`** | Required parent | +| **`RT_USING_OFW`** | Required — phandle parsing for **`hwlocks`** | +| **`RT_USING_HWSPINLOCK`** | Build **`hwspinlock.c`**; selects **`RT_USING_ADT`** / **`RT_USING_ADT_REF`** | +| **`SOC_DM_HWSPINLOCK_DIR`** | BSP adds platform drivers (e.g. mailbox companion lock IP) | + +--- + +## Architecture + +``` + SoC hwspinlock controller (platform driver) + | + | hwspinlock_bank_alloc + fill ops + | rt_hwspinlock_bank_register(bank) + v + Global bank list + rt_ofw_data(provider_np) = bank + | + | Consumer: hwlocks = <&hwlock0 3>; hwlock-names = "mbox"; + v + rt_ofw_get_hwspinlock_by_name(np, "mbox") + | + v + rt_hwspin_lock_timeout_irqsave(hwlock, ms, &flags) + ... shared MMIO / mailbox ... + rt_hwspin_unlock_irqrestore(hwlock, &flags) + | + v + rt_hwspinlock_put(hwlock) /* release allocation, not unlock */ +``` + +| Object | Role | +| --- | --- | +| **`struct rt_hwspinlock_bank`** | One controller: **`dev`**, **`ops`**, **`base_id`**, **`locks[]`** | +| **`struct rt_hwspinlock`** | One hardware lock index; per-lock software **`rt_spinlock`** guards metadata | +| **`struct rt_hwspinlock_ops`** | **`trylock`**, **`unlock`**, optional **`relax`** (WFE / yield in busy-wait) | + +**Two layers of locking**: + +1. **Software** **`hwlock->lock`** — protects the bank’s bookkeeping while calling ops (optional **irqsave** via **`out_irq_level`**). +2. **Hardware** **`ops->trylock/unlock`** — the actual cross-core exclusion in silicon. + +**`rt_hw_dmb()`** is issued after acquire and before release for visibility ordering. + +--- + +## Provider: register a bank + +```c +struct my_hwlock_drv { + struct rt_hwspinlock_bank *bank; +}; + +static rt_err_t my_hwlock_trylock(struct rt_hwspinlock *hwlock) +{ + int id = hwspinlock_find_id(hwlock); + /* read lock register: RT_EOK or -RT_EBUSY */ +} + +static void my_hwlock_unlock(struct rt_hwspinlock *hwlock) +{ + int id = hwspinlock_find_id(hwlock); + /* write release */ +} + +static void my_hwlock_relax(struct rt_hwspinlock *hwlock) +{ + rt_hw_cpu_relax(); /* or WFE per TRM */ +} + +static const struct rt_hwspinlock_ops my_hwlock_ops = { + .trylock = my_hwlock_trylock, + .unlock = my_hwlock_unlock, + .relax = my_hwlock_relax, +}; + +static rt_err_t my_probe(struct rt_platform_device *pdev) +{ + struct my_hwlock_drv *priv; + int num_locks = 32; + + priv->bank = hwspinlock_bank_alloc(priv, num_locks); + priv->bank->dev = &pdev->parent; + priv->bank->ops = &my_hwlock_ops; + priv->bank->base_id = 0; /* global ID offset for DT indices */ + priv->bank->locks_nr = num_locks; + + return rt_hwspinlock_bank_register(priv->bank); +} +``` + +**`rt_hwspinlock_bank_register`** requirements: + +- **`bank`**, **`bank->dev`**, **`bank->ops`**, **`locks_nr > 0`** +- Initializes each **`locks[i].bank`**, **`used = false`**, software spinlock +- Links bank on global list; **`rt_dm_dev_bind_fwdata(dev, NULL, bank)`** for OFW lookup + +**`rt_hwspinlock_bank_unregister`**: succeeds only when **`rt_ref_read(&bank->ref) == 1`** (no outstanding **`get`** references)—else **`-RT_EBUSY`**. + +--- + +## Consumer API + +### Lock / unlock + +| API | IRQ save | Behavior | +| --- | --- | --- | +| **`rt_hwspin_trylock_raw`** | optional **`out_irq_level`** | One try; **`-RT_EBUSY`** if hardware busy | +| **`rt_hwspin_lock_timeout_raw`** | optional | Retries until success or **`timeout_ms`** → **`-RT_ETIMEOUT`**; calls **`ops->relax`** while waiting | +| **`rt_hwspin_unlock_raw`** | restore if provided | **`ops->unlock`** then release software lock | + +Convenience inlines: **`rt_hwspin_trylock`**, **`rt_hwspin_trylock_irqsave`**, **`rt_hwspin_lock_timeout`**, **`rt_hwspin_lock_timeout_irqsave`**, matching **`unlock`** variants. + +**Rules**: + +- **Do not sleep** while holding the hardware lock (same as spinlocks). +- **Pair lock/unlock** on the same core that acquired the hardware lock (AMP contract). +- **`unlock` does not clear `used`** — allocation is separate from locking. + +### Allocate a lock handle + +| API | Role | +| --- | --- | +| **`rt_hwspinlock_get()`** | First free lock in any registered bank | +| **`rt_hwspinlock_get_by_index(dev, index)`** | Via consumer’s OFW node | +| **`rt_hwspinlock_get_by_name(dev, name)`** | Match **`hwlock-names`** | +| **`rt_ofw_get_hwspinlock_by_index(np, index)`** | Parse **`hwlocks`** + **`#hwlock-cells`** | +| **`rt_ofw_get_hwspinlock_by_name(np, name)`** | Index from **`hwlock-names`** | +| **`rt_hwspinlock_put(hwlock)`** | Clear **`used`**, **`rt_ref_put`** on bank — **does not unlock** | + +**OFW parse** (`hwspinlock.c`): + +```dts +hwspinlock: hwspinlock@... { + compatible = "vendor,hwspinlock"; + #hwlock-cells = <1>; +}; + +remote_ip: device@... { + hwlocks = <&hwspinlock 5>; + hwlock-names = "ipc"; +}; +``` + +1. **`rt_ofw_parse_phandle_cells(np, "hwlocks", "#hwlock-cells", index, &args)`** +2. **`rt_platform_ofw_request(bank_np)`** if provider not probed +3. **`bank = rt_ofw_data(bank_np)`**; lock id = **`bank->base_id + args.args[0]`** (requires **`args_count == 1`**) +4. Marks **`hwlock->used`** and **`rt_ref_get(&bank->ref)`** + +--- + +## When to use hwspinlock vs `rt_spinlock` + +| Use **hwspinlock** when… | Use **`rt_spinlock`** when… | +| --- | --- | +| **Another CPU / DSP / M-core** can touch the same resource without sharing RT-Thread. | Only RT-Thread SMP CPUs coordinate, all under one OS. | +| TRM or firmware defines **hardware lock index** before mailbox/shared SRAM access. | Exclusion inside one kernel with IRQ disable or SMP spinlock is enough. | +| **Mailbox / rpmsg** handshake doc lists lock IDs. | Short critical section on local driver private data. | + +Typical scenarios: **AMP**, **heterogeneous multicore**, **CPU ↔ remoteproc** register blocks, **shared message SRAM** between Linux and RT-Thread. + +--- + +## Integration with mailbox / rpmsg + +Agree on a **fixed lock index map** in DTS and in remote firmware (e.g. lock **0** = mailbox header, lock **1** = virtio queue). See @ref page_device_mailbox and @ref page_device_rpmsg. + +Suggested **lock order** (document in BSP): acquire **hwspinlock before** ringing doorbell / sending mailbox, release **after** MMIO writes visible—avoid holding hwlock while waiting on a blocking IPC call from the other side. + +--- + +## Engineer checklist + +1. **Provider**: **`hwspinlock_bank_alloc`**, implement **`trylock`/`unlock`/`relax`**, set **`base_id`** consistently with DT indices, **`rt_hwspinlock_bank_register`**. +2. **Consumer**: **`rt_ofw_get_hwspinlock_by_name`** in **`probe`**; **`put`** in **`remove`**. +3. **Critical section**: **`lock_timeout_irqsave`** → short MMIO → **`unlock_irqrestore`**. +4. **Teardown**: ensure all locks **unlocked** and **`put`** before **`bank_unregister`**. + +--- + +## Pitfalls + +- **`rt_hwspinlock_put` without unlock** — next **`get`** may see hardware still owned by this or another core. +- **Wrong `#hwlock-cells` or index** — **`rt_ofw_get_*`** returns **`RT_ENOSYS`** or wrong slot → silent corruption. +- **Sleep while holding hwlock** — deadlock remote core or violate **`relax`** busy-wait assumptions. +- **Recursive acquire** — not supported (hardware + software locks). +- **Cross-core unlock** — undefined on most IPs; always unlock on acquirer CPU. +- **Deadlock with mailbox**: ISR holds lock A, thread waits on IPC that needs A on other core—define global lock ordering. +- **`unregister` with active refs** — **`-RT_EBUSY`**; remote side may still hold hardware lock even if ref count looks idle. + +--- + +## See also + +- `components/drivers/include/drivers/hwspinlock.h` +- `components/drivers/hwspinlock/hwspinlock_dm.h`, `hwspinlock.c` +- @ref page_device_dm — **`rt_dm_dev_bind_fwdata`**, **`rt_platform_ofw_request`** +- @ref page_device_ofw — phandle / **`#hwlock-cells`** +- @ref page_device_mailbox, @ref page_device_rpmsg +- @ref page_device_platform diff --git a/documentation/6.components/device-driver/hwtimer/hwtimer.md b/documentation/6.components/device-driver/hwtimer/hwtimer.md new file mode 100755 index 00000000000..52161985594 --- /dev/null +++ b/documentation/6.components/device-driver/hwtimer/hwtimer.md @@ -0,0 +1,408 @@ +@page page_device_hwtimer HWTIMER Device + +# Introduction to the Timer + +## Driver / BSP quick notes + +| Topic | Guidance | +| --- | --- | +| **Prescaler reload** | Some timers glitch if prescaler changes while counter running—stop, reload, restart. | +| **Cascade / 32-bit** | Chaining 16-bit timers for long timeouts—handle overflow ISR race vs application read. | +| **Power** | Timer clock domain may gate in sleep—wake source setup must re-enable bus clock. | + +Hardware timers generally have two modes of operation, timer mode and counter mode. No matter which mode is operated, it works by counting the pulse signal counted by the internal counter module. Here are some important concepts of timers. + +**Timer mode**: Counts the internal pulse. Timers are often used as timing clocks for timing detection, timing response, and timing control. + +**Counter mode**: The counter can count up or down. The maximum count value of a 16-bit counter is 65535, and the maximum value of a 32-bit counter is 4 294 967 295. + +**Counting frequency**:Since the input frequency is usually fixed, the time it takes for the counter to reach its desired count number can be calculated from just the given frequency - `time = count value / count frequency`. For example, if the counting frequency is 1 MHz, the counter counts once every 1 / 1000000 seconds. That is, every 1 microsecond, the counter is incremented by one (or subtracted by one), at this time, the maximum timing capability of the 16-bit counter is 65535 microseconds, or 65.535 milliseconds. + +# Access Hardware Timer Device + +The application accesses the hardware timer device through the I/O device management interface provided by RT-Thread. The related interfaces are as follows: + +| **Function** | **Description** | +| -------------------- | ---------------------------------- | +| rt_device_find() | to look up the timer device | +| rt_device_open() | to open the timer device in read-write mode | +| rt_device_set_rx_indicate() | to set the timeout callback function | +| rt_device_control() | to control the timer device, you can set the timing mode (single time /cycle),counting frequency, or stop the timer | +| rt_device_write() | to set the timeout value of the timer. The timer then starts | +| rt_device_read() | to get the current value of the timer | +| rt_device_close() | to turn off the timer device. | + +## Find Timer Device + +The application obtains the device handle based on the hardware timer device name, and thus can operate the hardware timer device. The device function is as follows: + +```c +rt_device_t rt_device_find(const char* name); +``` + +| Parameter | **Description** | +| -------- | ---------------------------------- | +| name | hardware timer device name | +| **return** | —— | +| timer device handle | will return to the corresponding device handle if the corresponding device is found | +| RT_NULL | No device found | + +In general, the hardware timer device name registered to the system is timer0, timer1, etc. The usage examples are as follows: + +```c +#define HWTIMER_DEV_NAME "timer0" /* timer name */ +rt_device_t hw_dev; /* timer device handle */ +/* find timer device */ +hw_dev = rt_device_find(HWTIMER_DEV_NAME); +``` + +## Open Timer Device + +With the device handle, the application can open the device. When the device is open, it will detect whether the device has been initialized. If it is not initialized, it will call the initialization interface to initialize the device by default. Open the device with the following function: + +```c +rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags); +``` + +| Parameter | Description | +| ---------- | ------------------------------- | +| dev | hardware timer device handle | +| oflags | device open mode, is generally opened in read and write mode, which is to take the value:RT_DEVICE_OFLAG_RDWR | +| **return** | —— | +| RT_EOK | device opened successfully | +| other error code | device fail to open | + +An example of use is as follows: + +```c +#define HWTIMER_DEV_NAME "timer0" /* timer name */ +rt_device_t hw_dev; /* timer device handle */ +/* find timer device */ +hw_dev = rt_device_find(HWTIMER_DEV_NAME); +/* to open the timer device in read-write mode */ +rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR); +``` + +## Set the Timeout Callback Function + +Set the timer timeout callback function with the following function - this is the function that will be called when the timer reaches its set count value: + +```c +rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev, rt_size_t size)) +``` + +| Parameter | **Description** | +| ---------- | ------------------------------- | +| dev | device handle | +| rx_ind | timeout callback function, provided by the caller | +| **return** | —— | +| RT_EOK | success | + +An example of use is as follows: + +```c +#define HWTIMER_DEV_NAME "timer0" /* timer name */ +rt_device_t hw_dev; /* timer device handle */ + +/* timer timeout callback function */ +static rt_err_t timeout_cb(rt_device_t dev, rt_size_t size) +{ + rt_kprintf("this is hwtimer timeout callback fucntion!\n"); + rt_kprintf("tick is :%d !\n", rt_tick_get()); + + return 0; +} + +static int hwtimer_sample(int argc, char *argv[]) +{ + /* find timer device */ + hw_dev = rt_device_find(HWTIMER_DEV_NAME); + /* open the device in read and write mode */ + rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR); + /* set the timeout callback function */ + rt_device_set_rx_indicate(hw_dev, timeout_cb); +} +``` + +## Control the Timer Device + +By sending control words, the application can configure the hardware timer device with the following function: + +```c +rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg); +``` + +| Parameter | **Description** | +| ---------------- | ------------------------------ | +| dev | device handle | +| cmd | command control word | +| arg | controlled parameter | +| **return** | —— | +| RT_EOK | function executed successfully | +| -RT_ENOSYS | execution failed,dev is null | +| other error code | execution failed | + +The command control words available for the hardware timer device are as follows: + +| **Control word** | Description | +| ---------------------- | ------------------------ | +| HWTIMER_CTRL_FREQ_SET | set the counting frequency | +| HWTIMER_CTRL_STOP | stop the timer | +| HWTIMER_CTRL_INFO_GET | get timer feature information | +| HWTIMER_CTRL_MODE_SET | set timer mode | + +Get the timer parameter argument, which is a pointer to the structure struct rt_hwtimer_info, to save the obtained information. + +>Setting frequency is valid only when the timer hardware and included driver set the counting frequency. Generally, the default frequency of the driving setting can be used. + +When setting the timer mode, the parameter argument can take the following values: + +```c +HWTIMER_MODE_ONESHOT /* Single timing */ +HWTIMER_MODE_PERIOD /* Periodic timing */ +``` + +An example of using the timer count frequency and timing mode is as follows: + +```c +#define HWTIMER_DEV_NAME "timer0" /* timer name */ +rt_device_t hw_dev; /* timer device handle */ +rt_hwtimer_mode_t mode; /* timer mode */ +rt_uint32_t freq = 10000; /* couting frequency */ + +/* Timer timeout callback function */ +static rt_err_t timeout_cb(rt_device_t dev, rt_size_t size) +{ + rt_kprintf("this is hwtimer timeout callback fucntion!\n"); + rt_kprintf("tick is :%d !\n", rt_tick_get()); + + return 0; +} + +static int hwtimer_sample(int argc, char *argv[]) +{ + /* find timer device */ + hw_dev = rt_device_find(HWTIMER_DEV_NAME); + /* open the device in read and write mode */ + rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR); + /* Set the timeout callback function */ + rt_device_set_rx_indicate(hw_dev, timeout_cb); + + /* Set the counting frequency (1Mhz or the supported minimum counting frequency by default) */ + rt_device_control(hw_dev, HWTIMER_CTRL_FREQ_SET, &freq); + /* Set the mode to periodic timer */ + mode = HWTIMER_MODE_PERIOD; + rt_device_control(hw_dev, HWTIMER_CTRL_MODE_SET, &mode); +} +``` + +## Set the Timer Timeout Value + +The timer timeout value can be set by the following function: + +```c +rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size); +``` + +| **Parameter** | Description | +| ---------- | ------------------------------------------ | +| dev | device handle | +| pos | write data offset, unused now, can set 0 value | +| buffer | pointer to the timer timeout structure | +| size | timeout structure size | +| **return** | —— | +| The actual size of the written data | | +| 0 | fail | + +The prototype of the timeout structure is shown below : + +```c +typedef struct rt_hwtimerval +{ + rt_int32_t sec; /* second */ + rt_int32_t usec; /* microsecond */ +} rt_hwtimerval_t; +``` + +An example of using the timer timeout value is as follows: + +```c +#define HWTIMER_DEV_NAME "timer0" /* timer name */ +rt_device_t hw_dev; /* timer device handle */ +rt_hwtimer_mode_t mode; /* timer mode */ +rt_hwtimerval_t timeout_s; /* Timer timeout value */ + +/* Timer timeout callback function */ +static rt_err_t timeout_cb(rt_device_t dev, rt_size_t size) +{ + rt_kprintf("this is hwtimer timeout callback fucntion!\n"); + rt_kprintf("tick is :%d !\n", rt_tick_get()); + + return 0; +} + +static int hwtimer_sample(int argc, char *argv[]) +{ + /* find timer device */ + hw_dev = rt_device_find(HWTIMER_DEV_NAME); + /* open the device in read-write mode */ + rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR); + /* set the timeout callback function */ + rt_device_set_rx_indicate(hw_dev, timeout_cb); + /* set the mode as periodic timer */ + mode = HWTIMER_MODE_PERIOD; + rt_device_control(hw_dev, HWTIMER_CTRL_MODE_SET, &mode); + + /* Set the timer timeout value to 5s and start the timer */ + timeout_s.sec = 5; /* second */ + timeout_s.usec = 0; /* microsecond */ + rt_device_write(hw_dev, 0, &timeout_s, sizeof(timeout_s)); +} +``` + +## Obtain the Current Value of the Timer + +The current value of the timer can be obtained by the following function: + +```c +rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void* buffer, rt_size_t size); +``` + +| **Parameter** | Description | +| ---------- | ------------------------------------------ | +| dev | timer device handle | +| pos | write data offset, unused now , can set 0 value | +| buffer | output parameter, a pointer point to the timeout structure | +| size | timeout structure size | +| **return** | —— | +| Timeout structure size | success | +| 0 | fail | + +An example of use is shown below: + +```c +rt_hwtimerval_t timeout_s; /* Used to save the time the timer has elapsed */ +/* Read the elapsed time of the timer */ +rt_device_read(hw_dev, 0, &timeout_s, sizeof(timeout_s)); +``` + +## Close the Timer Device + +The timer device can be closed with the following function: + +```c +rt_err_t rt_device_close(rt_device_t dev); +``` + +| Parameter | Description | +| ---------- | ---------------------------------- | +| dev | timer device handle | +| **return** | —— | +| RT_EOK | close device successfully | +| -RT_ERROR | the device has been completely shut down and cannot be closed repeatedly | +| other error code | fail to close the device | + +When a timer device has been used and is not necessary anymore, it should be closed, otherwise the device will remain in an open status. + + An example of use is shown below: + +```c +#define HWTIMER_DEV_NAME "timer0" /* timer name */ +rt_device_t hw_dev; /* timer device handle */ +/* find timer device */ +hw_dev = rt_device_find(HWTIMER_DEV_NAME); +... ... +rt_device_close(hw_dev); +``` + +>Timing errors may occur. Assume that the counter has a maximum value of 0xFFFF, a counting frequency of 1Mhz, and a timing time of 1 second and 1 microsecond. Since the timer can only count up to 65535us at a time, the timing requirement for 1000001us can be completed 20 times at 50000us, and the calculation error will be 1us. + +# Hardware Timer Device Usage Example + +The specific use of the hardware timer device can refer to the following sample code. The main steps of the sample code are as follows: + +1. First find the device handle based on the timer device name "timer0". +2. Open the device "timer0" in read-write mode. +3. Set the timer timeout callback function. +4. Set the timer mode to periodic timer and set the timeout period to 5 seconds. At this time, the timer starts. +5. Read the timer after 3500ms delay, the read value will be displayed in seconds and microseconds. + +```c + /* + * Program listing: This is an hwtimer device usage routine +  * The routine exports the hwtimer_sample command to the control terminal +  * Command call format: hwtimer_sample +  * Program function: The hardware timer timeout callback function periodically prints the current tick value, and the difference between the two tick values is converted to the time equivalent to the timing time value. + */ + +#include +#include + +#define HWTIMER_DEV_NAME "timer0" /* timer name */ + +/* Timer timeout callback function */ +static rt_err_t timeout_cb(rt_device_t dev, rt_size_t size) +{ + rt_kprintf("this is hwtimer timeout callback fucntion!\n"); + rt_kprintf("tick is :%d !\n", rt_tick_get()); + + return 0; +} + +static int hwtimer_sample(int argc, char *argv[]) +{ + rt_err_t ret = RT_EOK; + rt_hwtimerval_t timeout_s; /* timer timeout value */ + rt_device_t hw_dev = RT_NULL; /* timer device value */ + rt_hwtimer_mode_t mode; /* timer mode */ + + /* find timer device */ + hw_dev = rt_device_find(HWTIMER_DEV_NAME); + if (hw_dev == RT_NULL) + { + rt_kprintf("hwtimer sample run failed! can't find %s device!\n", HWTIMER_DEV_NAME); + return -RT_ERROR; + } + + /* Open the device in read-write mode */ + ret = rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR); + if (ret != RT_EOK) + { + rt_kprintf("open %s device failed!\n", HWTIMER_DEV_NAME); + return ret; + } + + /* set timeout callback function */ + rt_device_set_rx_indicate(hw_dev, timeout_cb); + + /* Setting mode is periodic timer */ + mode = HWTIMER_MODE_PERIOD; + ret = rt_device_control(hw_dev, HWTIMER_CTRL_MODE_SET, &mode); + if (ret != RT_EOK) + { + rt_kprintf("set mode failed! ret is :%d\n", ret); + return ret; + } + + /* Set the timer timeout value to 5s and start the timer. */ + timeout_s.sec = 5; /* second */ + timeout_s.usec = 0; /* microsecond */ + + if (rt_device_write(hw_dev, 0, &timeout_s, sizeof(timeout_s)) != sizeof(timeout_s)) + { + rt_kprintf("set timeout value failed\n"); + return -RT_ERROR; + } + + /* delay 3500ms */ + rt_thread_mdelay(3500); + + /* read the current value of timer */ + rt_device_read(hw_dev, 0, &timeout_s, sizeof(timeout_s)); + rt_kprintf("Read: Sec = %d, Usec = %d\n", timeout_s.sec, timeout_s.usec); + + return ret; +} +/* Export to the msh command list */ +MSH_CMD_EXPORT(hwtimer_sample, hwtimer sample); +``` diff --git a/documentation/6.components/device-driver/i2c/dm.md b/documentation/6.components/device-driver/i2c/dm.md new file mode 100755 index 00000000000..17a7a53132f --- /dev/null +++ b/documentation/6.components/device-driver/i2c/dm.md @@ -0,0 +1,299 @@ +@page page_device_i2c_dm I2C device model (DM) + +# I2C bus framework under `RT_USING_DM` + +When **`RT_USING_DM`** and **`RT_USING_I2C`** are enabled, RT-Thread adds an **I2C-specific `rt_bus`** (`name = "i2c"`) on top of the core DM bus layer (@ref page_device_bus). **Bus controllers** still register as **`RT_Device_Class_I2CBUS`** devices (`i2c0`, `i2c1`, …); **slave drivers** bind as **`struct rt_i2c_client`** devices discovered from device tree children. + +Legacy **manual** usage (`rt_device_find("i2c1")` + `rt_i2c_transfer`) remains valid — see @ref page_device_i2c. This page covers **automatic DT binding** and **`RT_I2C_DRIVER_EXPORT`**. + +Sources: + +| File | Role | +| --- | --- | +| **`dev_i2c_bus.c`** | **`struct rt_bus i2c_bus`**, match/probe, **`i2c_bus_scan_clients`** | +| **`dev_i2c_dm.c`** | **`i2c_timings_ofw_parse`** | +| **`dev_i2c_core.c`** | Calls **`i2c_bus_scan_clients`** after **`rt_i2c_bus_device_register`** | +| **`dev_i2c.h`** | **`rt_i2c_client`**, **`rt_i2c_driver`**, **`RT_I2C_DRIVER_EXPORT`** | +| **`dev_i2c_dm.h`** | **`struct i2c_timings`**, scan/timings declarations | + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_I2C`** | Core I2C stack (`dev_i2c_core.c`, `dev_i2c_dev.c`, …) | +| **`RT_USING_DM`** | Adds **`dev_i2c_bus.c`** + **`dev_i2c_dm.c`** (I2C bus + OFW scan) | +| **`RT_USING_OFW`** | Required for **`i2c_bus_scan_clients`** and **`i2c_timings_ofw_parse`** | +| **`SOC_DM_I2C_DIR`** | BSP SoC adapters (e.g. `i2c-rk3x.c`) | + +Without **`RT_USING_DM`**, **`struct rt_i2c_client`** has no embedded **`rt_device`** — only **`bus`** + **`client_addr`**. + +--- + +## Architecture + +``` + Platform I2C controller (e.g. rk3x, bit-bang) + | + | probe: i2c_timings_ofw_parse, rt_dm_dev_iomap, clocks + | bus->parent.ofw_node = controller np + | rt_i2c_bus_device_register(&bus->parent, "i2cN") + v + rt_device "i2cN" (RT_Device_Class_I2CBUS, user_data -> rt_i2c_bus_device) + | + | i2c_bus_scan_clients(bus) /* dev_i2c_core.c */ + v + For each available child in DT: + alloc rt_i2c_client, reg -> client_addr, ofw_node + rt_i2c_device_register(client) -> rt_bus_add_device("i2c", ...) + v + i2c_match: driver ids[] name OR ofw_ids compatible + | + v + i2c_probe -> driver->probe(client) + | + v + Client driver uses client->bus + rt_i2c_transfer / master_send +``` + +**Two bus layers**: + +| Layer | Object | Example | +| --- | --- | --- | +| **I2C bus device** | **`struct rt_i2c_bus_device`** | Hardware adapter; **`master_xfer`**, mutex **`lock`** | +| **DM I2C bus** | **`struct rt_bus` `i2c_bus`** | Binds **`rt_i2c_driver`** ↔ **`rt_i2c_client`** | + +Core DM **`platform`** bus probes the **controller**; subsystem **`i2c`** bus probes **clients** under that controller’s OFW node. + +--- + +## `struct rt_bus i2c` (dev_i2c_bus.c) + +Registered at boot: **`INIT_CORE_EXPORT(i2c_bus_init)`** → **`rt_bus_register(&i2c_bus)`**. + +| Callback | Behavior | +| --- | --- | +| **`i2c_match`** | 1) Match **`driver->ids[].name`** to **`client->name`** (from DT node name). 2) Else **`rt_ofw_node_match(client->ofw_node, driver->ofw_ids)`**. Sets **`client->id`** or **`client->ofw_id`**. | +| **`i2c_probe`** | Requires **`client->bus`**; calls **`driver->probe(client)`**. | +| **`i2c_remove` / `i2c_shutdown`** | Forward to driver hooks. | + +**Driver registration**: + +```c +rt_err_t rt_i2c_driver_register(struct rt_i2c_driver *driver); +/* RT_I2C_DRIVER_EXPORT(drv) -> RT_DRIVER_EXPORT(drv, i2c, BUILIN) */ +``` + +Sets **`driver->parent.bus = &i2c_bus`** then **`rt_driver_register`**. + +**Client registration**: + +```c +rt_err_t rt_i2c_device_register(struct rt_i2c_client *client); +/* internally: rt_bus_add_device(&i2c_bus, &client->parent) */ +``` + +--- + +## Device tree: controller + clients + +### Controller node + +```dts +i2c1: i2c@ff160000 { + compatible = "rockchip,rk3588-i2c", "rockchip,rk3066-i2c"; + reg = <0x0 0xff160000 0x0 0x1000>; + interrupts = ; + clocks = <&cru CLK_I2C1>, <&cru PCLK_I2C1>; + clock-names = "i2c", "pclk"; + #address-cells = <1>; + #size-cells = <0>; + + rtc@68 { + compatible = "dallas,ds1307"; + reg = <0x68>; + }; +}; +``` + +| Property | Role | +| --- | --- | +| **`clock-frequency`** | Parsed into **`struct i2c_timings.bus_freq_hz`** | +| **`i2c-scl-rising-time-ns`**, **`i2c-scl-falling-time-ns`**, … | Optional timing cells — **`i2c_timings_ofw_parse`** | +| **`#address-cells = <1>`**, **`#size-cells = <0>`** | Standard I2C child addressing | +| **Child `reg`** | 7-bit (or 10-bit) slave address → **`client->client_addr`** | + +### Client discovery (`i2c_bus_scan_clients`) + +Called automatically when **`rt_i2c_bus_device_register`** succeeds (**`RT_USING_DM`** only). + +For each **available** child of **`bus->parent.ofw_node`**: + +1. If child has **`compatible`**, use it as client node; else look one level deeper (**i2c-mux** style). +2. **`rt_calloc`** **`struct rt_i2c_client`**. +3. Read **`reg`** → **`client_addr`**. +4. **`client->parent.ofw_node`**, **`client->name = rt_ofw_node_name`**, **`client->bus = bus`**. +5. **`rt_dm_dev_set_name(&client->parent, "%s", name)`**. +6. **`rt_i2c_device_register(client)`** → triggers **`i2c_match`** + **`probe`** if a driver is already registered. + +**Order note**: If the **client driver** registers before the **bus**, clients are not scanned yet. Typically **controller `probe` runs first** (platform init), then scan instantiates clients while **I2C drivers** are already linked via **`RT_I2C_DRIVER_EXPORT`**. If a driver loads late, you may need manual **`rt_i2c_device_register`** or re-scan (not provided in-tree — ensure init order). + +--- + +## Bus controller driver (adapter) + +Pattern from SoC drivers (e.g. **`bsp/rockchip/dm/i2c/i2c-rk3x.c`**): + +```c +static rt_err_t my_i2c_probe(struct rt_platform_device *pdev) +{ + struct rt_device *dev = &pdev->parent; + struct my_i2c *i2c = rt_calloc(1, sizeof(*i2c)); + + i2c_timings_ofw_parse(dev->ofw_node, &i2c->timings, RT_TRUE); + + i2c->regs = rt_dm_dev_iomap(dev, 0); + i2c->irq = rt_dm_dev_get_irq(dev, 0); + /* clocks: rt_clk_get_by_name, prepare_enable */ + + rt_dm_dev_set_name_auto(&i2c->parent.parent, "i2c"); + + i2c->parent.ops = &my_i2c_ops; + i2c->parent.parent.ofw_node = dev->ofw_node; + dev->user_data = i2c; + + return rt_i2c_bus_device_register(&i2c->parent, rt_dm_dev_get_name(dev)); +} +``` + +| Step | API | +| --- | --- | +| Timings | **`i2c_timings_ofw_parse(np, &timings, use_defaults)`** | +| Resources | **`rt_dm_dev_iomap`**, **`rt_dm_dev_get_irq`**, **`rt_clk_*`** (@ref page_device_clk) | +| Naming | **`rt_dm_dev_set_name`** / **`rt_dm_dev_set_name_auto`** → **`i2c0`**, **`i2c1`**, … | +| Publish bus | **`rt_i2c_bus_device_register(&i2c->parent, name)`** — triggers **client scan** | + +**`rt_i2c_bus_device`** embeds **`struct rt_device parent`**; **`user_data`** on that device points at the full adapter struct (controller private data). + +Bit-bang buses (**`RT_USING_I2C_BITOPS`**) call **`rt_i2c_bit_add_bus`** → same **`rt_i2c_bus_device_register`** path. + +--- + +## Client driver (I2C slave) + +```c +static const struct rt_ofw_node_id my_ids[] = { + { .compatible = "vendor,my-sensor", .data = (void *)&chip_info }, + { /* sentinel */ }, +}; + +static const struct rt_i2c_device_id my_tbl[] = { + { .name = "my-sensor", .data = (void *)&chip_info }, + { /* sentinel */ }, +}; + +static struct rt_i2c_driver my_driver = { + .ids = my_tbl, /* optional: match by DT node name */ + .ofw_ids = my_ids, /* match by compatible */ + .probe = my_probe, + .remove = my_remove, +}; +RT_I2C_DRIVER_EXPORT(my_driver); + +static rt_err_t my_probe(struct rt_i2c_client *client) +{ + struct rt_i2c_bus_device *bus = client->bus; + rt_uint16_t addr = client->client_addr; + const void *cfg = rt_i2c_client_id_data(client); + + /* Use rt_i2c_master_send/recv or rt_i2c_transfer on client->bus */ + return RT_EOK; +} +``` + +| Field / API | Role | +| --- | --- | +| **`client->bus`** | Parent adapter — **must** be set (scan sets it; manual clients must assign). | +| **`client->client_addr`** | From DT **`reg`** (scan) or driver setup. | +| **`client->parent.ofw_node`** | Child DT node for **`rt_dm_dev_get_irq`** etc. | +| **`rt_i2c_client_id_data(client)`** | **`id->data`** or **`ofw_id->data`** from match table | + +After **`probe`**, many drivers register a functional device (RTC, sensor, regulator) on top of the client, using **`client->parent`** only as the DM binding handle. + +--- + +## `struct i2c_timings` (OFW) + +Defined in **`dev_i2c_dm.h`**. **`i2c_timings_ofw_parse(dev_np, timings, use_defaults)`** reads: + +| DT property | Field | +| --- | --- | +| **`clock-frequency`** | **`bus_freq_hz`** (default 100 kHz if missing and **`use_defaults`**) | +| **`i2c-scl-rising-time-ns`** | **`scl_rise_ns`** | +| **`i2c-scl-falling-time-ns`** | **`scl_fall_ns`** | +| **`i2c-scl-internal-delay-ns`** | **`scl_int_delay_ns`** | +| **`i2c-sda-falling-time-ns`** | **`sda_fall_ns`** | +| **`i2c-sda-hold-time-ns`** | **`sda_hold_ns`** | +| **`i2c-digital-filter-width-ns`** | **`digital_filter_width_ns`** | +| **`i2c-analog-filter-cutoff-frequency`** | **`analog_filter_cutoff_freq_hz`** | + +Controller **`probe`** passes parsed timings into IP-specific **`calc_timings`** (SoC-specific). + +--- + +## Transfer path (unchanged by DM) + +DM only changes **how clients appear**. Data path is still: + +```c +rt_i2c_bus_lock(bus, timeout); +rt_i2c_transfer(bus, msgs, num); /* -> ops->master_xfer */ +rt_i2c_bus_unlock(bus); +``` + +Or **`rt_i2c_master_send` / `rt_i2c_master_recv`**. Bus mutex serializes masters on one adapter. + +--- + +## DM vs non-DM summary + +| Topic | With **`RT_USING_DM`** | Without DM | +| --- | --- | --- | +| Client struct | **`rt_i2c_client`** embeds **`rt_device parent`** | **`bus` + `client_addr` only | +| Binding | **`RT_I2C_DRIVER_EXPORT`** + DT scan | App finds bus, bit-bangs address manually | +| Bus register side effect | **`i2c_bus_scan_clients`** | No scan | +| Timing helpers | **`i2c_timings_ofw_parse`** | Driver parses DT itself or fixed Hz | + +--- + +## Engineer checklist + +1. **Controller**: platform **`probe`** → timings + resources → **`rt_i2c_bus_device_register`** with **`ofw_node`** on **`bus->parent`**. +2. **Client driver**: **`ofw_ids`** + optional **`ids`**, **`RT_I2C_DRIVER_EXPORT`**, **`probe`** uses **`client->bus`** and **`client_addr`**. +3. **DT**: controller **`#address-cells` / `#size-cells`**, child **`reg`** + **`compatible`**. +4. **Transfers**: always through **`client->bus`**, respect **`rt_i2c_bus_lock`** if sharing with other code. +5. **Remove**: controller **`remove`** should **`rt_device_unregister`** bus; clients removed via **`i2c_remove`** when bus tears down (verify BSP order). + +--- + +## Pitfalls + +- **`client->bus == NULL`** in **`i2c_probe`** → **`-RT_EINVAL`** — scan did not run or client registered manually without **`bus`**. +- **Wrong `reg` address** — probe succeeds but **`rt_i2c_transfer`** NACKs on wire. +- **Driver before bus** — no automatic re-scan; child never **`probe`** until bus registers. +- **i2c-mux depth** — scan only handles **one** level of non-compatible wrapper; deeper mux trees may need explicit client creation. +- **Name vs compatible match** — **`ids[].name`** must match DT **node name**, not **`compatible`** string; prefer **`ofw_ids`** for compatible-based binding. +- **Mixing DM client with legacy app** — app can still **`rt_i2c_bus_device_find("i2c1")`**; ensure mutex discipline if both access the same bus. + +--- + +## See also + +- @ref page_device_i2c — protocol, **`rt_i2c_transfer`**, application usage +- @ref page_device_bus — core **`rt_bus`** / **`rt_driver`** +- @ref page_device_dm — **`rt_dm_dev_iomap`**, naming +- @ref page_device_platform — controller **`RT_PLATFORM_DRIVER_EXPORT`** +- @ref page_device_ofw — DT properties +- `components/drivers/include/drivers/dev_i2c.h` +- `components/drivers/i2c/dev_i2c_bus.c`, `dev_i2c_core.c`, `dev_i2c_dm.c` diff --git a/documentation/6.components/device-driver/iio/dm.md b/documentation/6.components/device-driver/iio/dm.md new file mode 100755 index 00000000000..f5b5029a59d --- /dev/null +++ b/documentation/6.components/device-driver/iio/dm.md @@ -0,0 +1,249 @@ +@page page_device_iio_dm IIO device model (DM) + +# Industrial I/O channel discovery under `RT_USING_DM` + +RT-Thread **IIO** support is intentionally **small**: it does **not** implement a full Linux IIO subsystem (no `iio_device` bus, no buffer/trigger framework in-tree). With **`RT_USING_DM`**, **`components/drivers/iio/iio.c`** parses device-tree **`io-channels`** / **`io-channel-names`** (Linux-compatible binding names) and returns an **opaque provider handle** plus a **channel index**. + +Data path (read raw ADC counts, scale to mV, trigger buffers, etc.) remains **entirely in the provider driver** (ADC, thermal, PMIC ADC, …). + +Overview of the IIO concept: @ref page_device_iio. This page documents **DM phandle resolution** and **provider obligations**. + +Sources: **`components/drivers/iio/iio.c`**, **`components/drivers/include/drivers/iio.h`**. + +--- + +## Kconfig / build + +| Requirement | Role | +| --- | --- | +| **`RT_USING_DM`** | **`iio/SConscript`** builds **`iio.c`** only when DM is on | +| **`RT_USING_OFW`** | **`ofw_iio_channel_get_by_index`** parses phandles; without OFW, **`get_*` returns `NULL`** | +| Provider driver | Separate Kconfig (e.g. **`RT_USING_ADC`**, SoC SARADC in BSP) — not part of **`iio.c`** | + +**`drivers/iio.h`** is included from **`rtdevice.h`**; link **`iio.c`** by enabling **`RT_USING_DM`**. + +There is **no** separate **`RT_USING_IIO`** menu entry — IIO glue is a DM companion module. + +--- + +## Architecture + +``` + Consumer (platform device, e.g. PMIC / headset / fuel gauge logic) + | + | dev->ofw_node has io-channels / io-channel-names + v + rt_iio_channel_get_by_name(dev, "vdd", &ch) + | + | rt_dm_dev_prop_index_of_string("io-channel-names") + | rt_ofw_parse_phandle_cells("io-channels", "#io-channel-cells", index) + v + Provider OFW node (e.g. saradc, tsadc) + | + | rt_platform_ofw_request if not probed + | return rt_ofw_data(provider_np) /* opaque void * */ + | *out_channel = specifier args[0] + v + Consumer casts cookie to provider private type + + uses out_channel as logical channel index + + calls provider-specific read API (rt_adc_*, custom fn, …) +``` + +**Unlike @ref page_device_i2c_dm**, IIO has **no `rt_bus`** and **no automatic client probe**. Only **phandle lookup** is centralized. + +--- + +## Public API (`iio.h`) + +```c +void *rt_iio_channel_get_by_index(struct rt_device *dev, int index, int *out_channel); +void *rt_iio_channel_get_by_name(struct rt_device *dev, const char *name, int *out_channel); +``` + +| API | Behavior | +| --- | --- | +| **`get_by_index`** | Parse Nth entry in consumer’s **`io-channels`** list | +| **`get_by_name`** | Resolve index via **`io-channel-names`**, then same as **`get_by_index`** | +| **Return value** | **`void *`** = **`rt_ofw_data(provider_np)`** after optional **`rt_platform_ofw_request`** | +| **`out_channel`** | First cell of provider **`#io-channel-cells`** specifier (logical channel number) | + +| Input check | Result | +| --- | --- | +| **`dev == NULL`**, **`index < 0`**, **`name == NULL`** | **`NULL`** | +| Consumer has **no `dev->ofw_node`** | **`NULL`** | +| Missing **`io-channels`** / parse error | **`NULL`** | +| Provider not probed | **`rt_platform_ofw_request`** then retry **`rt_ofw_data`** | + +There is **no `rt_iio_channel_put`** in the header — lifetime follows the **provider `rt_device`** / platform driver **`remove`**. Do not use the cookie after the provider unregisters. + +--- + +## Device tree bindings + +### Provider (ADC / sensor front-end) + +```dts +saradc: adc@ff100000 { + compatible = "rockchip,rk3588-saradc"; + reg = <0x0 0xff100000 0x0 0x100>; + interrupts = ; + clocks = <&cru CLK_SARADC>, <&cru PCLK_SARADC>; + clock-names = "saradc", "pclk"; + vref-supply = <&vcc_1v8_s0>; + + #io-channel-cells = <1>; + io-channel-ranges; +}; +``` + +| Property | Role | +| --- | --- | +| **`#io-channel-cells = <1>`** | One integer per phandle in consumer **`io-channels`** | +| **`io-channel-ranges`** | Optional (Linux); presence documents channel map semantics for porters | +| Channel index | **0 … N-1** — meaning defined by **provider driver** (SARADC channel id, TSADC sensor input, …) | + +### Consumer + +```dts +fuel-gauge { + compatible = "vendor,fg"; + io-channels = <&saradc 4>, <&saradc 5>; + io-channel-names = "bat-voltage", "bat-current"; +}; +``` + +| Property | Role | +| --- | --- | +| **`io-channels`** | Phandle + one cell per channel (provider + index) | +| **`io-channel-names`** | Optional strings aligned with **`io-channels`** order | + +**`get_by_name(dev, "bat-voltage", &ch)`** → cookie = saradc private struct, **`ch == 4`**. + +--- + +## Provider driver contract + +The provider must expose **`rt_ofw_data(provider_np)`** before consumers resolve channels. + +Typical pattern (from **`adc-rockchip_saradc.c`**): + +```c +static rt_err_t rockchip_saradc_probe(struct rt_platform_device *pdev) +{ + struct rt_device *dev = &pdev->parent; + struct rockchip_saradc *rk_saradc = rt_calloc(1, sizeof(*rk_saradc)); + + /* clocks, regulator, iomap, irq, rt_hw_adc_register(...) */ + + rt_dm_dev_bind_fwdata(dev, RT_NULL, rk_saradc); + return RT_EOK; +} + +static rt_err_t rockchip_saradc_remove(struct rt_platform_device *pdev) +{ + rt_dm_dev_unbind_fwdata(dev, RT_NULL); + /* unregister ADC, free resources */ +} +``` + +| Obligation | Detail | +| --- | --- | +| **`rt_dm_dev_bind_fwdata(dev, NULL, priv)`** | Sets **`rt_ofw_data(dev->ofw_node) = priv`** (@ref page_device_dm) | +| **`#io-channel-cells`** in DTS | Must match **`args_count == 1`** in **`iio.c`** (otherwise lookup fails) | +| **Probe before consumer** | Or rely on **`rt_platform_ofw_request`** during **`get_*`** | +| **Document channel map** | What index **0, 1, 2…** mean (pin / internal mux / TSADC sensor) | + +Returned cookie is usually the **provider’s private struct** (`struct rockchip_saradc *`, etc.) — **not** a portable `struct iio_channel`. Consumers in the same BSP cast to the known type. + +--- + +## Consumer driver pattern + +```c +#include + +static rt_err_t my_consumer_probe(struct rt_platform_device *pdev) +{ + struct rt_device *dev = &pdev->parent; + void *iio_priv; + int ch; + + iio_priv = rt_iio_channel_get_by_name(dev, "bat-voltage", &ch); + if (!iio_priv) + { + return -RT_EINVAL; + } + + priv->saradc = (struct rockchip_saradc *)iio_priv; + priv->channel = ch; + + /* Use priv->saradc + priv->channel with provider helpers + * e.g. rt_adc_enable/read on &priv->saradc->parent, or direct HW access */ + return RT_EOK; +} +``` + +| Step | Notes | +| --- | --- | +| Call **`get_*` in `probe`** | After consumer **`ofw_node`** is set; provider may be deferred-probed by **`rt_platform_ofw_request`** | +| Store **cookie + `out_channel`** | Cookie = provider priv; **`ch`** selects logical input | +| **No standard read API** | Pair with **`rt_adc_*`**, **`rt_sensor_*`**, or vendor functions | +| **`remove`** | Drop pointers; do not call **`get_*`** again | + +--- + +## Relation to ADC / sensor frameworks + +| Framework | Role vs IIO | +| --- | --- | +| **@ref page_device_adc** | **`rt_adc_device`** is often the **provider** registered by SARADC; IIO only **discovers** which channel another driver needs | +| **Sensor framework** | Separate path (`rt_sensor_*`); may coexist — do not confuse sensor bus with IIO phandles | + +IIO is **glue for DT `io-channels`**, not a replacement for **`rt_device_find("adc0")`**. + +--- + +## Comparison with Linux IIO + +| Linux | RT-Thread DM IIO | +| --- | --- | +| `struct iio_channel` + `iio_channel_get()` | Opaque **`void *`** + **`out_channel` int | +| `iio_read_channel_raw()` etc. | **Not in-tree** — provider-specific | +| IIO core + buffers/triggers | **Not implemented** | + +Porting focus: reproduce **DT property names** and **channel indices**; reimplement read path against local ADC driver. + +--- + +## Engineer checklist + +1. **Provider DTS**: **`#io-channel-cells = <1>`**; document channel index meanings. +2. **Provider probe**: **`rt_dm_dev_bind_fwdata`** with stable **`priv`** pointer. +3. **Consumer DTS**: **`io-channels`** + optional **`io-channel-names`**. +4. **Consumer probe**: **`rt_iio_channel_get_by_*`**, check **`NULL`**, store cookie + index. +5. **Reads**: use provider APIs; honor **`rt_adc_enable` / mutex** rules of that driver. +6. **Teardown**: consumer **`remove`** first or ensure no use-after-free when provider exits. + +--- + +## Pitfalls + +- **Opaque pointer is provider-specific** — do not cast to a fictional `struct iio_channel`. +- **`args_count != 1`** — **`iio.c` returns failure**; multi-cell specifiers are unsupported today. +- **Missing `bind_fwdata`** — **`get_*` returns NULL** even if ADC device registered. +- **Consumer without `ofw_node`** — always **`NULL`** (no manual fallback in **`iio.c`**). +- **Wrong channel index** — parse succeeds but reads wrong pin / garbage data. +- **No refcount on cookie** — provider **`remove`** while consumer still holds pointer → UAF. +- **Assuming full IIO stack** — scaling, sysfs, buffer DMA are out of scope for **`iio.c`**. + +--- + +## See also + +- @ref page_device_iio — role and limits summary +- @ref page_device_dm — **`rt_dm_dev_bind_fwdata`**, property helpers +- @ref page_device_ofw — **`rt_ofw_parse_phandle_cells`** +- @ref page_device_platform — consumer/provider **`probe`** +- @ref page_device_adc — typical provider (**`rt_hw_adc_register`**) +- `components/drivers/iio/iio.c` +- `components/drivers/include/drivers/iio.h` diff --git a/documentation/6.components/device-driver/iio/iio.md b/documentation/6.components/device-driver/iio/iio.md new file mode 100755 index 00000000000..2e798f9e904 --- /dev/null +++ b/documentation/6.components/device-driver/iio/iio.md @@ -0,0 +1,44 @@ +@page page_device_iio Industrial I/O (IIO) + +# IIO (Industrial I/O) + +Device-tree **`io-channels`** binding and DM integration: @ref page_device_iio_dm. + +## Overview + +`drivers/iio.h` offers a small **channel discovery** API: `rt_iio_channel_get_by_index` and `rt_iio_channel_get_by_name` resolve a channel handle and index from a `rt_device`. IIO is a light abstraction for **ADC, DAC, temperature, accelerometer**, and similar sensors; data format and calibration are driver-specific. + +## Usage + +1. Associate consumer and provider via device tree `io-channels` / `io-channel-names` or private matching. +2. Call the `get` helpers for an opaque channel pointer and logical index, then use the provider’s read/write path (usually wrapped inside the driver). + +## DM relation + +Providers are commonly `platform` devices: `probe` brings up clocks, resets, and calibration, then registers the device. Phandle-based dependencies mirror Linux IIO bindings and ease porting. + +## Practical limits (current API) + +`drivers/iio.h` only exposes **`rt_iio_channel_get_by_index`** and **`rt_iio_channel_get_by_name`** returning an **opaque `void *`** channel cookie plus **`out_channel`** index. There is **no standard `put`** in this header—lifetime is **owned by the provider `rt_device`**; do not use the handle after the provider unregisters. + +## When IIO glue helps + +| Use IIO discovery when… | Skip it when… | +| --- | --- | +| Porting **Linux `io-channels`** consumers and you want matching property names. | You already have a **private `ioctl`** between two in-tree drivers. | + +## Consumer flow + +1. **`get_by_*`** after provider `probe` has finished (phandles resolved). +2. Pass **`out_channel`** index into **your provider’s** read/write routine (implemented alongside the ADC driver). +3. On remove, drop references to the **`rt_device`** only—no separate channel free unless your provider documents one. + +## Pitfalls + +- **Opaque pointer** is not a `struct iio_channel` like Linux—do not cast blindly across drivers. +- **Missing provider**: returns **`NULL`**—always check before dereference. + +## See also + +- `components/drivers/include/drivers/iio.h` +- Platform: `documentation/6.components/device-driver/platform/platform.md` diff --git a/documentation/6.components/device-driver/input/dm.md b/documentation/6.components/device-driver/input/dm.md new file mode 100755 index 00000000000..d0714155645 --- /dev/null +++ b/documentation/6.components/device-driver/input/dm.md @@ -0,0 +1,237 @@ +@page page_device_input_dm Input device model (DM) + +# Input subsystem under `RT_USING_DM` + +The **input core** (`components/drivers/input/input.c`) is built only when **`RT_USING_INPUT`** is enabled. That Kconfig option **`depends on RT_USING_DM`** — there is **no** legacy non-DM input stack in-tree. Drivers publish Linux-style **`EV_*`** events through **`struct rt_input_device`**; consumers attach via **`rt_input_add_handler`** or POSIX **`/dev/inputN`** (@ref page_device_input_uapi). + +Event reporting API, polling rules, and pitfalls: @ref page_device_input. + +| Topic | Page | +| --- | --- | +| Userspace **`open`/`read`/`ioctl`** | @ref page_device_input_uapi | +| **`rt_touch_device`** bridge | @ref page_device_input_touch | +| **`KEY_POWER` / `KEY_RESTART`** shutdown/reset | @ref page_device_input_power | + +Sources: + +| File | Role | +| --- | --- | +| **`input.c`** | Global device list, **`rt_dm_ida`** naming (`input0`…), **`rt_input_device_register`** | +| **`input_power.c`** | @ref page_device_input_power | +| **`input_touch.c`** | @ref page_device_input_touch | +| **`input_uapi.c`** | @ref page_device_input_uapi | +| **`keyboard/`**, **`joystick/`**, **`touchscreen/`**, **`misc/`** | OFW platform/SPI drivers | + +--- + +## Kconfig / build + +| Option | Role | +| --- | --- | +| **`RT_USING_DM`** | Parent — input cannot be enabled without DM | +| **`RT_USING_INPUT`** | Core **`input.c`**; selects **`RT_USING_ADT`** + bitmap ADT | +| **`RT_INPUT_POWER`** | @ref page_device_input_power (default **y**) | +| **`RT_INPUT_UAPI`** | @ref page_device_input_uapi | +| **`RT_INPUT_KEYBOARD`** | **`gpio-keys`**, **`adc-keys`** (see sub-options) | +| **`RT_INPUT_JOYSTICK`** | **`adc-joystick`** | +| **`RT_INPUT_TOUCHSCREEN`** | @ref page_device_input_touch | +| **`RT_INPUT_MISC`** | e.g. **`ettus,e3x0-button`** | +| **`SOC_DM_INPUT_*_DIR`** | BSP adds more platform drivers via **`osource`** in sub-Kconfig | + +**Unlike @ref page_device_i2c_dm**, input has **no dedicated `rt_bus`**. Each hardware block is a normal **`struct rt_platform_driver`** (or **`struct rt_spi_driver`**) that embeds or owns **`struct rt_input_device`** and calls **`rt_input_device_register`** from **`probe`**. + +--- + +## Architecture + +``` + Device tree node (compatible = "gpio-keys", "adc-keys", …) + | + | platform bus match (rt_ofw_node_id) or SPI bus match + v + driver probe(pdev) + | + | parse OFW (gpios, io-channels, irq, …) + | rt_input_set_capability(idev, EV_KEY|EV_ABS, code) + | optional: rt_input_setup_polling / rt_input_setup_touch + v + rt_input_device_register(idev) + | + | rt_dm_ida_alloc → name "inputN" + | rt_device_register (RT_Device_Class_Char, DEACTIVATE) + | [RT_INPUT_UAPI] input_uapi_init + | list on input_device_nodes + v + [RT_INPUT_TOUCHSCREEN] input_touch_register → rt_touch_device + [RT_INPUT_POWER] global KEY_POWER / KEY_RESTART handlers + | + v + Hardware IRQ / timer poll → rt_input_report_* → rt_input_sync + | + v + rt_input_event → handlers / UAPI queue +``` + +| Layer | Object | Notes | +| --- | --- | --- | +| **Input device** | **`struct rt_input_device`** | Embeds **`struct rt_device parent`**; capabilities in bitmaps | +| **DM naming** | **`input_ida`** in **`input.c`** | **`rt_dm_dev_set_name(&idev->parent, "input%u", id)`** | +| **Platform binding** | **`RT_PLATFORM_DRIVER_EXPORT`** | Probe/remove on **`struct rt_platform_device`** | +| **SPI touch** | **`RT_SPI_DRIVER_EXPORT`** | e.g. ADS7846 — see @ref page_device_input_touch | + +--- + +## Core registration (`input.c`) + +```c +rt_err_t rt_input_device_register(struct rt_input_device *idev); +rt_err_t rt_input_device_unregister(struct rt_input_device *idev); +``` + +| Step | Behavior | +| --- | --- | +| **Allocate ID** | **`rt_dm_ida_alloc(&input_ida)`** — failure → **`-RT_EFULL`** | +| **Init lists / lock** | Per-device **`handler_nodes`**, **`rt_spinlock`** | +| **Register char dev** | **`RT_Device_Class_Char`**, flag **`RT_DEVICE_FLAG_DEACTIVATE`** | +| **Post-register** | Start poller timer if **`idev->poller`**; **`input_touch_register`**; UAPI fops if enabled | +| **Unregister** | Refcount check; UAPI/touch teardown; **`rt_dm_ida_free`** | + +**Driver pattern** — embed input at a known offset: + +```c +struct my_input { + struct rt_input_device parent; + /* private */ +}; + +static void my_report(struct my_input *mi) +{ + rt_input_report_key(&mi->parent, KEY_ENTER, 1); + rt_input_sync(&mi->parent); +} +``` + +Use **`rt_container_of(idev, struct my_input, parent)`** in poll/IRQ callbacks. + +--- + +## In-tree OFW drivers + +| Compatible / binding | Driver file | Bus | Summary | +| --- | --- | --- | --- | +| **`gpio-keys`** | **`keyboard/keys-gpio.c`** | platform | Parent node; **each child** → one **`inputN`** per key | +| **`adc-keys`** | **`keyboard/keys-adc.c`** | platform | **`io-channels`** via @ref page_device_iio_dm; polling | +| **`adc-joystick`** | **`joystick/js-adc.c`** | platform | Per-child **`reg`**, IIO, **`EV_ABS`** polling | +| **`ti,ads7846`** … **`ti,xpt2046`** | **`touchscreen/ts-ads7846.c`** | SPI | @ref page_device_input_touch | +| **`ettus,e3x0-button`** | **`misc/button-e3x0.c`** | platform | IRQ **`press`/`release`** → **`KEY_POWER`** | + +### `gpio-keys` (platform + children) + +- Parent **`compatible = "gpio-keys"`**; **subnodes** are individual buttons. +- Each child: **`rt_ofw_get_named_pin`**, property matching **`*,code$`** (e.g. **`linux,code`**), optional **`debounce-interval`**. +- **One `rt_input_device` per child** (not one device for the whole parent). + +### `adc-keys` / `adc-joystick` (IIO consumers) + +- Resolve ADC through **`rt_iio_channel_get_by_name`** / **`rt_iio_channel_get_by_index`** (@ref page_device_iio_dm). +- Default **`poll-interval`** **200 ms** if property missing. + +### ADS7846 family (SPI) + +- **`RT_SPI_DRIVER_EXPORT(ads7846_driver)`**; regulator, IRQ, worker thread. +- DT axis/touch properties consumed by **`input_touch_parse`** — @ref page_device_input_touch. + +--- + +## Device tree examples + +### GPIO key (child node) + +```dts +gpio-keys { + compatible = "gpio-keys"; + + power-key { + label = "Power"; + gpios = <&gpio0 5 GPIO_ACTIVE_LOW>; + linux,code = <116>; /* KEY_POWER — see page_device_input_power */ + debounce-interval = <15>; + }; +}; +``` + +### ADC resistor ladder + +```dts +adc-keys { + compatible = "adc-keys"; + io-channels = <&saradc 0>; + io-channel-names = "buttons"; + poll-interval = <100>; + keyup-threshold-microvolt = <1800000>; + + up { + press-threshold-microvolt = <500000>; + linux,code = <103>; /* KEY_UP */ + }; +}; +``` + +### SPI resistive touch + +```dts +touch: touchscreen@0 { + compatible = "ti,ads7846"; + reg = <0>; + interrupt-parent = <&gpio1>; + interrupts = <25 IRQ_TYPE_EDGE_FALLING>; + ti,x-min = /bits/ 16 <0>; + ti,x-max = /bits/ 16 <4095>; + ti,y-min = /bits/ 16 <0>; + ti,y-max = /bits/ 16 <4095>; + vcc-supply = <®_3v3>; +}; +``` + +(SPI **`reg`** / **`spi-max-frequency`** belong on the child under the SPI controller node per your BSP.) + +--- + +## New platform input driver checklist + +1. Enable **`RT_USING_DM`**, **`RT_USING_OFW`**, **`RT_USING_INPUT`** (and subsystem menu if you add under **`keyboard/`** etc.). +2. **`struct rt_platform_driver`** with **`ofw_ids[]`** matching Linux binding where possible. +3. In **`probe`**: allocate driver private struct with **`struct rt_input_device`** embedded first (or pointer field). +4. **`rt_input_set_capability`** for every **`type`/`code`** you will report; for **`EV_ABS`**, call **`rt_input_set_absinfo`** (after capability allocates **`absinfo`**). +5. IRQ or **`rt_input_setup_polling`** — poller **`poll`** must not block (@ref page_device_input). +6. On each logical event frame: **`rt_input_report_*`** then **`rt_input_sync`**. +7. **`rt_input_device_register`** only when hardware is ready; store private data in **`pdev->parent.user_data`** or **`rt_ofw_data(np)`** for **`remove`**. +8. **`remove`**: stop IRQ/poller, **`rt_input_device_unregister`**, free memory. +9. **`RT_PLATFORM_DRIVER_EXPORT(drv)`** (or **`RT_SPI_DRIVER_EXPORT`** on SPI slaves). + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **Missing `rt_input_sync`** | Consumers see partial frames — always end with **`rt_input_sync`**. | +| **Capability after register** | Set capabilities **before** **`rt_input_device_register`**. | +| **`EV_ABS` without absinfo** | Call **`rt_input_set_capability(EV_ABS, axis)`** then **`rt_input_set_absinfo`**. | +| **Unregister with open UAPI fd** | Close **`/dev/inputN`** first — @ref page_device_input_uapi | +| **gpio-keys child vs parent** | Driver registers **per child**; do not expect one **`input0`** for the whole node. | +| **IIO channel NULL** | Fix DT **`io-channels`** — @ref page_device_iio_dm | +| **Power handler surprise** | @ref page_device_input_power | + +--- + +## See also + +- @ref page_device_input +- @ref page_device_input_uapi +- @ref page_device_input_touch +- @ref page_device_input_power +- @ref page_device_iio_dm +- @ref page_device_bus +- `components/drivers/include/drivers/input.h` +- `components/drivers/input/Kconfig` diff --git a/documentation/6.components/device-driver/input/input.md b/documentation/6.components/device-driver/input/input.md new file mode 100755 index 00000000000..fa45817eabc --- /dev/null +++ b/documentation/6.components/device-driver/input/input.md @@ -0,0 +1,59 @@ +@page page_device_input Input subsystem + +Device-tree binding and DM registration: @ref page_device_input_dm. + +| Topic | Page | +| --- | --- | +| Userspace API | @ref page_device_input_uapi | +| Touch class bridge | @ref page_device_input_touch | +| Power / restart keys | @ref page_device_input_power | + +# Input subsystem (API) + +Header: `drivers/input.h`. Typical stack: low-level driver → **`rt_input_event` / reporters** → handlers (GUI, shell) or **`/dev/inputN`** (@ref page_device_input_uapi). + +| Path | API | +| --- | --- | +| In-kernel listener | **`rt_input_add_handler`** | +| POSIX process (Smart) | **`open` / `read` / `ioctl` / `poll` on `/dev/inputN`** — @ref page_device_input_uapi | +| Legacy touch GUI | **`struct rt_touch_device`** — @ref page_device_input_touch | + +## When to use the input core + +| Use **`rt_input_*`** when… | Consider raw **`rt_device`** read when… | +| --- | --- | +| You want **Linux-like** `EV_KEY` / `EV_REL` / `EV_ABS` / multitouch slot semantics and multiple listeners. | You have a **single proprietary** app talking to a vendor IOCTL-only touch stack. | +| You need **polling** (`**rt_input_setup_polling**`) for GPIO keys without IRQ storms. | GPIO IRQ latency is fine and you do not need unified event routing. | + +## Event contract + +1. **`rt_input_set_capability`** before **`rt_input_device_register`** so users know which **`type`/`code`** pairs are valid. +2. For each hardware change, call **`rt_input_report_*`** then **`rt_input_sync`** to close the frame (**`SYN_REPORT`**). Missing **`sync`** merges events poorly for consumers. +3. **`rt_input_trigger`** routes through optional **`idev->trigger`**—use when you wrap another input device; otherwise **`rt_input_event`** is enough. + +## Callback / context rules + +- **`rt_input_add_handler` → `callback`**: invoked from **`rt_input_event`** path—assume **thread or workqueue** unless your driver documents IRQ delivery; keep **`callback` short**. +- **`poller->poll`**: runs on timer tick—**must not block** or take mutexes that can deadlock with the same thread calling **`rt_input_sync`**. +- **Touch helpers**: **`rt_input_setup_touch`** / **`rt_input_report_touch_*`** require **`RT_INPUT_TOUCHSCREEN`** — @ref page_device_input_touch. + +## Registration lifecycle + +- **`rt_input_device_register`**: registers underlying **`rt_device`**; failures leave partially init state—clean up partial setup in error paths. +- **`rt_input_remove_config`**: tears down polling/touch dynamic config before unregister if you toggled features at runtime. + +## Pitfalls + +- **ABS without `rt_input_set_absinfo`**: consumers cannot clamp—set min/max/resolution for axes you report. +- **Key autorepeat**: not automatic unless a layer implements it—do not assume Linux repeat behavior. +- **Re-entrancy**: posting events from inside a handler for the same device can recurse—prefer queueing. + +## See also + +- @ref page_device_input_dm +- @ref page_device_input_uapi +- @ref page_device_input_touch +- @ref page_device_input_power +- `components/drivers/include/drivers/input.h` +- `components/drivers/include/drivers/input_uapi.h` +- `dt-bindings/input/event-codes.h` diff --git a/documentation/6.components/device-driver/input/power.md b/documentation/6.components/device-driver/input/power.md new file mode 100755 index 00000000000..870b5595524 --- /dev/null +++ b/documentation/6.components/device-driver/input/power.md @@ -0,0 +1,103 @@ +@page page_device_input_power Input power keys + +# Power / restart key handlers (`RT_INPUT_POWER`) + +**`input_power.c`** installs global **`rt_input_handler`** instances that map **`KEY_POWER`** and **`KEY_RESTART`** release events to **`rt_hw_cpu_shutdown()`** and **`rt_hw_cpu_reset()`**. Any input device that advertises these keys in its capability bitmap can trigger system power actions without a dedicated PMIC driver hook. + +DM and keyboard drivers: @ref page_device_input_dm. Handler API: @ref page_device_input. + +Source: **`components/drivers/input/input_power.c`**. + +--- + +## Kconfig + +| Option | Default | Role | +| --- | --- | --- | +| **`RT_INPUT_POWER`** | **y** | Build **`input_power.c`**, run **`input_event_power_init`** at env init | + +Depends on **`RT_USING_INPUT`**. Disable on boards where **`KEY_POWER`** must only be handled by application code. + +--- + +## Architecture + +``` + INIT_ENV_EXPORT(input_event_power_init) + | + +-- power_handler_install(KEY_POWER) + +-- power_handler_install(KEY_RESTART) + | + | (1) tmp_handler: identify=test, callback=power_input_callback + | rt_input_add_handler → scans all idevs, counts matches + v + (2) For each matching input device index: + alloc rt_input_handler[N] + identify=power_input_identify (binds to Nth device with key) + callback=power_input_callback + rt_input_add_handler + | + v + Any idev reports KEY_POWER/KEY_RESTART release (value==0) + → rt_input_event → handler → shutdown/reset +``` + +| Phase | Behavior | +| --- | --- | +| **Probe pass** | Temporary handler increments **`cap.value`** for each **`rt_input_device`** with **`key_map[code]`** set | +| **Install pass** | One persistent handler per matching device; **`identify`** matches the **`current`**-th device | +| **Runtime** | **`power_input_callback`**: on **`value == 0`** (key release), call BSP hook | + +Handlers return **`RT_TRUE`** from **`callback`** to stop further handler dispatch for that event. + +--- + +## Key actions + +| Linux code | Event | Action | +| --- | --- | --- | +| **`KEY_POWER`** (116) | **`EV_KEY`**, **`value == 0`** (release) | **`rt_hw_cpu_shutdown()`** | +| **`KEY_RESTART`** | **`EV_KEY`**, **`value == 0`** | **`rt_hw_cpu_reset()`** | + +Press (**`value == 1`**) does nothing in **`input_power.c`** — only release triggers power ops. + +BSP must implement **`rt_hw_cpu_shutdown`** / **`rt_hw_cpu_reset`** (weak symbols or SoC-specific). + +--- + +## Interaction with drivers + +- **`gpio-keys`** child with **`linux,code = <116>`** registers an **`inputN`** with **`KEY_POWER`** in **`key_map`** — power handler attaches automatically when **`RT_INPUT_POWER`** is on. +- **`ettus,e3x0-button`** reports **`KEY_POWER`** on IRQ — same path. +- Does **not** require a dedicated compatible string; only the **advertised key code** matters. + +If multiple devices expose **`KEY_POWER`**, each gets its own handler instance; any of them can shut down the system on release. + +--- + +## Disabling or overriding + +| Goal | Approach | +| --- | --- | +| **No automatic shutdown** | Set **`RT_INPUT_POWER=n`** in Kconfig | +| **Custom policy** | Disable **`RT_INPUT_POWER`** and use **`rt_input_add_handler`** with higher priority logic, or handle **`KEY_POWER`** only in userspace via @ref page_device_input_uapi | +| **Hold-to-power-off** | Not implemented here — implement in driver (long-press timer) before reporting release | + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **Unexpected shutdown** | Any stray **`KEY_POWER`** release shuts down — verify DT **`linux,code`** and test harnesses | +| **Missing capability** | Driver must **`rt_input_set_capability(idev, EV_KEY, KEY_POWER)`** before register | +| **Press-only testing** | Release is required — press alone does not call shutdown | +| **No matching devices** | **`power_handler_install`** exits early if count is zero (no handlers allocated) | + +--- + +## See also + +- @ref page_device_input_dm — **`gpio-keys`** DT example with **`KEY_POWER`** +- @ref page_device_input +- `dt-bindings/input/linux-event-codes.h` (or **`event-codes.h`**) diff --git a/documentation/6.components/device-driver/input/touch.md b/documentation/6.components/device-driver/input/touch.md new file mode 100755 index 00000000000..abd81aa904b --- /dev/null +++ b/documentation/6.components/device-driver/input/touch.md @@ -0,0 +1,160 @@ +@page page_device_input_touch Input touch bridge + +# Touch bridge (`RT_INPUT_TOUCHSCREEN`) + +**`input_touch.c`** bridges **`struct rt_input_device`** (Linux-style **`EV_ABS`** / multitouch slots) to **`struct rt_touch_device`** so GUI stacks can use the legacy **touch class** API (**`rt_device_find`**, **`RT_TOUCH_CTRL_*`**, **`rt_hw_touch_isr`**) without duplicating panel drivers. + +Input core and OFW drivers (e.g. ADS7846): @ref page_device_input_dm. Event helpers (**`rt_input_setup_touch`**, **`rt_input_report_touch_*`**): @ref page_device_input. + +Sources: **`components/drivers/input/input_touch.c`**, **`components/drivers/input/touchscreen/`**. + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_INPUT_TOUCHSCREEN`** | Build **`input_touch.c`**; **selects `RT_USING_TOUCH`** | +| **`RT_INPUT_TOUCHSCREEN_ADS7846`** | In-tree SPI resistive driver **`ts-ads7846.c`** | +| **`SOC_DM_INPUT_TOUCHSCREEN_DIR`** | BSP adds more panel drivers | + +Requires **`RT_USING_INPUT`** and **`RT_USING_DM`**. + +--- + +## Architecture + +``` + Panel driver (e.g. ads7846) + | + | rt_input_set_capability(EV_ABS, …) + | rt_input_setup_touch(idev, num_slots, &touch_info) + | rt_input_device_register(idev) + v + input_touch_register (after idev on global list) + | + | rt_hw_touch_register → device "touchN" + | rt_input_add_handler → input_touch_cb + v + Driver reports: rt_input_report_touch_* / rt_input_sync + | + v + input_touch_cb: EV_ABS → struct rt_touch_data + SYN_REPORT → rt_hw_touch_isr() + | + v + GUI: rt_device_read / touch ISR callback +``` + +| Object | Role | +| --- | --- | +| **`struct input_touch_properties`** | Stored in **`idev->touch`**: DT-derived min/max, invert, swap, slot count | +| **`struct input_touch_device`** | Embeds **`struct rt_touch_device`** + **`rt_input_handler`** + per-slot **`rt_touch_data`** | +| **`input_touch_ops`** | **`touch_readpoint`**, **`touch_control`** (range, enable/disable, status) | + +**`input_touch_register`** only creates the **`rt_touch_device`** when **`rt_input_setup_touch`** was called with non-NULL **`struct rt_touch_info *info`** ( **`prop->touch_dev`** allocated). + +--- + +## Driver API (`input.h`) + +```c +rt_err_t rt_input_setup_touch(struct rt_input_device *idev, + rt_uint32_t num_slots, struct rt_touch_info *info); +rt_err_t rt_input_parse_touch_position(struct rt_input_device *idev, + rt_uint32_t *out_x, rt_uint32_t *out_y); +rt_bool_t rt_input_report_touch_inactive(struct rt_input_device *idev, rt_bool_t active); +void rt_input_report_touch_position(struct rt_input_device *idev, + rt_uint32_t x, rt_uint32_t y, rt_bool_t multitouch); +``` + +| API | Role | +| --- | --- | +| **`rt_input_setup_touch`** | Allocate **`idev->touch`**, parse DT, optional multitouch absinfo (**`ABS_MT_SLOT`**, **`ABS_MT_TRACKING_ID`**) | +| **`num_slots == 0`** | Single-touch (**`ABS_X`/`ABS_Y`**); **`num_slots > 0`** | Multitouch slot protocol | +| **`rt_input_report_touch_position`** | Apply invert/swap from DT, then **`rt_input_report_abs`** | +| **`rt_input_report_touch_inactive`** | Emit **`ABS_MT_TRACKING_ID = -1`** on release | +| **`rt_input_parse_touch_position`** | Map raw x/y through **`touchscreen-inverted-*`** / **`touchscreen-swapped-x-y`** | + +Call **`rt_input_setup_touch`** before **`rt_input_device_register`**. **`input_touch_register`** runs inside register after the device is linked globally. + +--- + +## Device tree properties (`input_touch_parse`) + +Read from the **`rt_input_device`**'s **`rt_device`** OFW node via **`rt_dm_dev_prop_*`**: + +| Property | Effect | +| --- | --- | +| **`touchscreen-min-x`**, **`touchscreen-size-x`**, **`touchscreen-fuzz-x`** | Override **`ABS_X`** or **`ABS_MT_POSITION_X`** absinfo | +| **`touchscreen-min-y`**, **`touchscreen-size-y`**, **`touchscreen-fuzz-y`** | Y axis | +| **`touchscreen-max-pressure`**, **`touchscreen-fuzz-pressure`** | Pressure axis | +| **`touchscreen-inverted-x`**, **`touchscreen-inverted-y`** | Flip coordinates in **`rt_input_parse_touch_position`** | +| **`touchscreen-swapped-x-y`** | Swap X/Y absinfo and coordinates | + +Missing properties fall back to values already set by **`rt_input_set_absinfo`** in the panel driver. + +--- + +## Handler translation (`input_touch_cb`) + +When **`touch_dev->enabled`**: + +| Input event | Touch side | +| --- | --- | +| **`ABS_MT_SLOT`** | Select active slot index | +| **`ABS_MT_TRACKING_ID == -1`** | **`RT_TOUCH_EVENT_UP`**, **`rt_hw_touch_isr`** | +| **`ABS_MT_TRACKING_ID` valid** | DOWN / MOVE, increment **`down`** count | +| **`ABS_MT_POSITION_X/Y`** or **`ABS_X/Y`** | Scale raw value to **`range_x`/`range_y`** using absinfo min/max | +| **`EV_SYN` + `SYN_REPORT`** | **`rt_hw_touch_isr`** (frame complete) | + +**`input_touch_readpoint`** returns one **`struct rt_touch_data`** for the current slot (GUI pull model). + +--- + +## Example (driver probe sketch) + +```c +struct rt_touch_info info = { + .type = RT_TOUCH_TYPE_CAPACITANCE, + .vendor = RT_TOUCH_VENDOR_UNKNOWN, + .point_num = 1, + .range_x = 800, + .range_y = 480, +}; + +rt_input_set_capability(idev, EV_ABS, ABS_X); +rt_input_set_capability(idev, EV_ABS, ABS_Y); +rt_input_set_absinfo(idev, ABS_X, 0, max_x, 0, 0); +rt_input_set_absinfo(idev, ABS_Y, 0, max_y, 0, 0); + +rt_input_setup_touch(idev, 0, &info); /* 0 slots = single touch */ +rt_input_device_register(idev); + +/* in IRQ / thread: */ +rt_input_report_touch_position(idev, x, y, RT_FALSE); +rt_input_sync(idev); +``` + +For multitouch, pass **`num_slots > 0`**, use **`rt_input_report_touch_slot`**, tracking id helpers, then **`rt_input_sync`** per frame. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **`setup_touch` after register** | **`input_touch_register`** won't run correctly — setup before register | +| **`info == NULL`** | Properties-only mode: no **`rt_touch_device`** exported (handler-only path not used for HW touch register) | +| **Missing `rt_input_sync`** | Touch ISR won't see a full frame | +| **ABS range zero** | Scaling in **`input_touch_cb`** divides by **`maximum - minimum`** — set absinfo first | +| **Unregister order** | **`input_touch_unregister`** from **`rt_input_device_unregister`** — drop touch fd users first | + +--- + +## See also + +- @ref page_device_input_dm — **`ti,ads7846`** SPI binding +- @ref page_device_input +- @ref page_device_input_uapi — parallel POSIX event path on same **`inputN`** +- `components/drivers/include/drivers/dev_touch.h` diff --git a/documentation/6.components/device-driver/input/uapi.md b/documentation/6.components/device-driver/input/uapi.md new file mode 100755 index 00000000000..4ca1673db35 --- /dev/null +++ b/documentation/6.components/device-driver/input/uapi.md @@ -0,0 +1,162 @@ +@page page_device_input_uapi Input userspace API (UAPI) + +# Input event UAPI (`RT_INPUT_UAPI`) + +**`input_uapi.c`** exposes each **`rt_input_device`** as a **Linux-compatible** character device for POSIX userspace (Smart / LWP + DFS). IOCTLs and **`struct input_event`** are defined in **`drivers/input_uapi.h`** (aligned with **`include/uapi/linux/input.h`**). + +DM overview and driver binding: @ref page_device_input_dm. In-kernel reporters and handlers: @ref page_device_input. + +Sources: **`components/drivers/input/input_uapi.c`**, **`components/drivers/include/drivers/input_uapi.h`**. + +--- + +## Kconfig + +| Option | Default | Role | +| --- | --- | --- | +| **`RT_INPUT_UAPI`** | **y** if **`RT_USING_SMART`**, else **n** | Build **`input_uapi.c`**, attach **`dfs_file_ops`** in **`input_uapi_init`** | +| **`RT_INPUT_UAPI_EVENT_MAX`** | **128** | Ring buffer slots (**`struct input_event`** per device) | +| **`RT_UAPI_FAKE_BLOCK`** | **y** | **`O_NONBLOCK`** empty **`read`** returns synthetic **`SYN_REPORT`** instead of **`-EAGAIN`** | + +**Dependencies**: **`RT_USING_INPUT`**, **`RT_USING_KTIME`**, **`RT_USING_POSIX_DEVIO`**. + +--- + +## Architecture + +``` + rt_input_report_* → rt_input_sync + → rt_input_event (input.c) + → input_uapi_event (if ref_count > 0) + ring buffer + ktime timestamp + SYN_REPORT → sync_count++, wake wait_queue + → read() / poll(POLLIN) on /dev/inputN +``` + +| Hook | When | +| --- | --- | +| **`input_uapi_init`** | **`rt_input_device_register`** — allocates **`struct input_uapi`**, sets **`idev->parent.fops`** | +| **`input_uapi_event`** | Every **`rt_input_event`** while UAPI enabled | +| **`input_uapi_finit`** | **`rt_input_device_unregister`** — free UAPI state | + +Registered device names are **`input0`**, **`input1`**, … (**`rt_dm_ida`** in **`input.c`**). Open via devfs: + +```c +int fd = open("/dev/input0", O_RDONLY); +``` + +**`input_uapi_event`** returns without queuing when **`idev->parent.ref_count == 0`** — userspace must **`open`** the node before events appear. + +--- + +## Event format + +| | Kernel handler path | UAPI queue | +| --- | --- | --- | +| Struct | **`struct rt_input_event`** (`drivers/input.h`) | **`struct input_event`** (`input_uapi.h`) | +| Time | **`rt_tick_t tick`** | **`struct timeval`** from **`rt_ktime_boottime_get_us`** | +| Codes | **`dt-bindings/input/event-codes.h`** | Same | + +**`struct input_absinfo`** returned by **`EVIOCGABS`** matches **`struct rt_input_absinfo`** (**`RT_ASSERT`** on layout in **`input_uapi_init`**). + +--- + +## Ring buffer and framing + +**`struct input_uapi`** holds: + +- Atomic **`read_idx`**, **`write_idx`**, **`sync_count`** +- **`events[RT_INPUT_UAPI_EVENT_MAX]`** +- Optional **`grabbed_file`** for **`EVIOCGRAB`** + +On each kernel event copy, **`write_idx`** advances. When **`type == EV_SYN`** and **`code == SYN_REPORT`**, **`sync_count`** increments and **`rt_wqueue_wakeup`** runs. + +**`read`** drains events until the user buffer is full or a **`SYN_REPORT`** completes one frame; each consumed frame decrements **`sync_count`**. + +**Overflow**: if the next write would overwrite unread data, the event is **dropped** (warning log). Raise **`RT_INPUT_UAPI_EVENT_MAX`** for high-rate touch streams. + +--- + +## `read()` / `poll()` / `write()` + +| Behavior | Detail | +| --- | --- | +| **Min size** | **`count != 0`** and **`count < sizeof(struct input_event)`** → **`-EINVAL`** | +| **`count == 0`** | Allowed (availability check after wait) | +| **Blocking** | Waits on **`idev->parent.wait_queue`** until **`sync_count > 0`** | +| **`O_NONBLOCK`** | Empty queue → **`-EAGAIN`**, or fake **`SYN_REPORT`** if **`RT_UAPI_FAKE_BLOCK`** | +| **`poll`** | **`POLLIN`** when **`sync_count > 0`** (**`O_RDONLY`** / **`O_RDWR`** only) | +| **`write`** | **`-ENOSYS`** | + +**Grab**: if another **`dfs_file`** holds **`EVIOCGRAB`**, **`read`** returns **`-EAGAIN`**. + +--- + +## `ioctl()` (implemented) + +| IOCTL | Purpose | +| --- | --- | +| **`EVIOCGVERSION`** | **`EV_VERSION`** (`0x010001`) | +| **`EVIOCGID`** | Virtual ID: bustype **`0x06`**, vendor **`0x5354`**, product **`0x4556`**, version **`RT_VER_NUM >> 16`** | +| **`EVIOCGNAME(len)`** | Device name (**`inputN`**) | +| **`EVIOCGPROP(len)`** | Property bitmap (direct-touch bit when buffer large enough) | +| **`EVIOCGBIT(ev, len)`** | Type bitmap (**`ev==0`**) or **`EV_KEY`/`EV_REL`/`EV_ABS`** maps | +| **`EVIOCGABS(abs)`** | Axis **`struct input_absinfo`** | +| **`EVIOCGRAB`** | Exclusive grab; non-zero **`args`** grabs; second grabber **`-EBUSY`** | + +**`EVIOCSABS`**, force-feedback, and other Linux input IOCTLs → **`-EINVAL`**. + +--- + +## Userspace example + +```c +#include +#include +#include +#include + +void listen_input0(void) +{ + struct input_event ev[64]; + struct pollfd pfd = { .fd = open("/dev/input0", O_RDONLY), .events = POLLIN }; + + for (;;) + { + if (poll(&pfd, 1, -1) <= 0) + continue; + + ssize_t n = read(pfd.fd, ev, sizeof(ev)); + if (n <= 0) + continue; + + for (int i = 0; i < n / (int)sizeof(struct input_event); ++i) + { + /* ev[i].type, ev[i].code, ev[i].value */ + } + } +} +``` + +Use **`rt_input_add_handler`** in kernel; use UAPI when a POSIX process (e.g. **`evtest`**) reads the same event stream. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **No events before `open`** | **`ref_count`** gate in **`input_uapi_event`** | +| **Unregister with open fd** | **`rt_input_device_unregister`** checks **`ref_count`** — close fds first | +| **Grab conflicts** | Release with **`EVIOCGRAB`** and zero argument | +| **Touch bursts** | Increase **`RT_INPUT_UAPI_EVENT_MAX`** or read faster | +| **`RT_UAPI_FAKE_BLOCK`** | Disable for strict **`-EAGAIN`** on empty non-blocking reads | + +--- + +## See also + +- @ref page_device_input_dm +- @ref page_device_input +- `components/drivers/include/drivers/input_uapi.h` +- `components/drivers/input/Kconfig` diff --git a/documentation/6.components/device-driver/led/dm.md b/documentation/6.components/device-driver/led/dm.md new file mode 100755 index 00000000000..fad46c05ad3 --- /dev/null +++ b/documentation/6.components/device-driver/led/dm.md @@ -0,0 +1,198 @@ +@page page_device_led_dm LED device model (DM) + +# LED subsystem under `RT_USING_DM` + +**`RT_USING_LED`** (`components/drivers/led/`) depends on **`RT_USING_DM`**. The core **`led.c`** registers **`struct rt_led_device`** as char devices **`led0`**, **`led1`**, … via **`rt_dm_ida`**. In-tree OFW drivers bind Linux-style **`gpio-leds`**, **`pwm-leds`**, and **`register-bit-led`** nodes. + +Public API and char-device **`read`/`write`**: @ref page_device_led. + +Sources: + +| File | Role | +| --- | --- | +| **`led.c`** | **`rt_led_register`**, software blink timer, sysfs-style **`read`/`write`** | +| **`led-gpio.c`** | **`gpio-leds`** parent + per-child GPIO LED | +| **`led-pwm.c`** | **`pwm-leds`** + **`pwms`** phandle, brightness | +| **`led-syscon.c`** | **`register-bit-led`** via @ref page_device_syscon | + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_DM`** | Required parent | +| **`RT_USING_LED`** | Core **`led.c`** | +| **`RT_LED_GPIO`** | **`led-gpio.c`** — needs **`RT_USING_PINCTRL`**, **`RT_USING_OFW`** | +| **`RT_LED_PWM`** | **`led-pwm.c`** — needs **`RT_USING_PWM`**, **`RT_USING_OFW`** | +| **`RT_LED_SYSCON`** | **`led-syscon.c`** — needs **`RT_MFD_SYSCON`** | +| **`SOC_DM_LED_DIR`** | BSP adds more LED drivers via **`osource`** | + +**No `rt_bus`**: each LED is a **`RT_PLATFORM_DRIVER_EXPORT`** that calls **`rt_led_register`** from **`probe`**. + +--- + +## Architecture + +``` + DT: gpio-leds / pwm-leds / register-bit-led + | + | platform match (rt_ofw_node_id) + v + probe: per-child or single node + | + | parse gpios / pwms / syscon offset+mask + | fill struct rt_led_ops + | rt_led_register(&led->parent) → "ledN" + v + rt_device (RT_Device_Class_Char, RDWR) + | + +-- rt_led_set_state / set_brightness / set_period + +-- optional software blink (led.c timer) + +-- write "on"|"off"|digits / read state string +``` + +| Backend | Compatible | One **`rt_led_device` per… | +| --- | --- | --- | +| GPIO | **`gpio-leds`** | **Child subnode** (like Linux) | +| PWM | **`pwm-leds`** | **Child subnode** | +| Syscon | **`register-bit-led`** | **Platform node** (one LED per compatible node) | + +--- + +## Core registration (`led.c`) + +```c +rt_err_t rt_led_register(struct rt_led_device *led); +rt_err_t rt_led_unregister(struct rt_led_device *led); +``` + +| Requirement | Detail | +| --- | --- | +| **`led->ops`** | Must be non-NULL at register | +| **Naming** | **`rt_dm_dev_set_name(..., "led%u", id)`** | +| **Blink fallback** | If **`set_state`** exists but **`set_period`** is NULL, **`led.c`** allocates **`blink_timer`** in **`sysdata`** for **`RT_LED_S_BLINK`** | +| **Unregister** | Forces **`RT_LED_S_OFF`**, detaches blink timer | + +--- + +## GPIO LEDs (`led-gpio.c`) + +**Parent** node: + +```dts +/ { + leds { + compatible = "gpio-leds"; + pinctrl-0 = <&led_pins>; /* optional: apply once for all children */ + + status { + gpios = <&gpio0 10 GPIO_ACTIVE_HIGH>; + label = "status"; + default-state = "off"; + linux,default-trigger = "heartbeat"; + }; + }; +}; +``` + +| Property | Role | +| --- | --- | +| **`gpios`** | **`rt_ofw_get_named_pin`** — **`active_val`** from DT flags | +| **`default-state`** | **`"on"`** → **`RT_LED_S_ON`** at probe; else off | +| **`linux,default-trigger`** (fuzzy **`default-trigger$`**) | **`heartbeat`** or **`timer`** → **`RT_LED_S_BLINK`** (software blink in **`led.c`**) | +| **`pinctrl-0` on parent** | Applied once for all children; else per-child **`rt_pin_ctrl_confs_apply_by_name`** | + +**Ops**: **`set_state`** (off/on/toggle), **`get_state`** (reads pin level). **`RT_LED_S_BLINK`** not implemented in GPIO ops — handled by core timer when **`set_state`** returns **`-RT_ENOSYS`**. + +--- + +## PWM LEDs (`led-pwm.c`) + +```dts +pwm-leds { + compatible = "pwm-leds"; + + backlight { + pwms = <&pwm0 0 1000000>; /* channel, period (ns) */ + max-brightness = <255>; + active-low; + default-state = "on"; + }; +}; +``` + +| Property | Role | +| --- | --- | +| **`pwms`** | Phandle + **`#pwm-cells`** — args[0] channel, args[1] **period (ns)** | +| **`max-brightness`** | Required; scales **`rt_led_set_brightness`** | +| **`active-low`** | Inverts duty calculation | +| **`default-state`** | **`on`**, **`keep`** (preserve existing PWM duty), or off | +| **`default-trigger`** | Same as GPIO — software blink | + +**Ops**: **`set_state`**, **`get_state`** (tracks **`enabled`**), **`set_brightness`**. Brightness maps to **`rt_pwm_set`** pulse width. + +PWM controller must be probed (**`rt_platform_ofw_request`** on provider if needed); cookie **`rt_ofw_data(pwm_np)`** → **`struct rt_device_pwm *`**. + +--- + +## Syscon bit LEDs (`led-syscon.c`) + +```dts +syscon: syscon@10000000 { + compatible = "syscon"; + reg = <0x10000000 0x1000>; +}; + +status-led { + compatible = "register-bit-led"; + offset = <0x10>; + mask = <0x1>; + default-state = "on"; +}; +``` + +| Property | Role | +| --- | --- | +| **`offset`** | Byte offset into parent **syscon** register block | +| **`mask`** | Bit mask for **`rt_syscon_update_bits`** | +| Parent | **`rt_syscon_find_by_ofw_node`** on **parent OFW node** | + +**Ops**: **`set_state`**, **`get_state`** via **`rt_syscon_read`**. One platform device per LED node (**`pdev->parent.user_data`**). + +--- + +## New LED backend checklist + +1. Enable **`RT_USING_DM`**, **`RT_USING_LED`**, and backend Kconfig. +2. Embed **`struct rt_led_device parent`** first in private struct. +3. Implement **`struct rt_led_ops`** — at minimum **`set_state`**; add **`set_brightness`** for dimming, **`set_period`** for hardware blink. +4. **`rt_led_register`** after hardware/pinctrl/PWM is ready. +5. Apply **`default-state`** / **`default-trigger`** like in-tree drivers. +6. Store **`rt_ofw_data(child_np) = &led->parent`** (GPIO/PWM) or **`user_data`** (syscon) for **`remove`**. +7. **`remove`**: **`rt_led_unregister`**, free private data. +8. **`RT_PLATFORM_DRIVER_EXPORT`**. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **Double GPIO ownership** | Do not export the same pin as **`RT_USING_PIN`** device and LED | +| **PWM not probed** | Ensure PWM controller **`compatible`** probes before **`pwm-leds`** children | +| **Missing `max-brightness`** | PWM child probe fails with **`-RT_EINVAL`** | +| **Syscon parent** | **`register-bit-led`** must sit under / reference probed **syscon** | +| **Blink on GPIO** | Uses **software** timer in **`led.c`** — not hardware PWM blink | +| **Char write brightness** | Only works when **`set_brightness`** op exists (PWM path) | + +--- + +## See also + +- @ref page_device_input +- @ref page_device_syscon +- @ref page_device_pwm +- @ref page_device_pinctrl +- `components/drivers/include/drivers/led.h` +- `components/drivers/led/Kconfig` diff --git a/documentation/6.components/device-driver/led/led.md b/documentation/6.components/device-driver/led/led.md new file mode 100755 index 00000000000..cf32e9cb591 --- /dev/null +++ b/documentation/6.components/device-driver/led/led.md @@ -0,0 +1,144 @@ +@page page_device_led LED subsystem + +Device-tree binding and platform drivers: @ref page_device_led_dm. + +# LED subsystem (API) + +Header: **`components/drivers/include/drivers/led.h`**. Core: **`components/drivers/led/led.c`**. + +Each **`struct rt_led_device`** registers as a char device **`led0`**, **`led1`**, … under **`RT_USING_DM`**. Control via **`rt_led_*`** APIs or by **`read`/`write`** on the device node (shell / DFS). + +--- + +## When to use the LED core + +| Use **`rt_led_*`** when… | Raw GPIO/PWM may suffice when… | +| --- | --- | +| DT **`gpio-leds`** / **`pwm-leds`** should create uniform **`ledN`** devices. | One debug LED toggled directly in board code. | +| **`power_supply`** or policy code drives charging/status indicators. | LED is bootloader-only, never touched at runtime. | +| Userspace or MSH writes **`on`/`off`** or a brightness number to the device file. | No registration or naming needed. | + +--- + +## States and operations + +```c +enum rt_led_state { + RT_LED_S_OFF, + RT_LED_S_ON, + RT_LED_S_TOGGLE, + RT_LED_S_BLINK, +}; + +rt_err_t rt_led_register(struct rt_led_device *led); +rt_err_t rt_led_unregister(struct rt_led_device *led); + +rt_err_t rt_led_set_state(struct rt_led_device *led, enum rt_led_state state); +rt_err_t rt_led_get_state(struct rt_led_device *led, enum rt_led_state *out_state); +rt_err_t rt_led_set_period(struct rt_led_device *led, rt_uint32_t period_ms); +rt_err_t rt_led_set_brightness(struct rt_led_device *led, rt_uint32_t brightness); +``` + +| API | Behavior | +| --- | --- | +| **`set_state`** | Calls **`led->ops->set_state`** under spinlock; stops software blink timer when leaving blink | +| **`RT_LED_S_BLINK`** | If ops returns **`-RT_ENOSYS`** and core allocated **`blink_timer`**, starts periodic timer (**heartbeat-style** timing in **`led.c`**) | +| **`get_state`** | Requires **`ops->get_state`** — else **`-RT_ENOSYS`** | +| **`set_period`** | Uses **`ops->set_period`** if present; else adjusts software blink timer period | +| **`set_brightness`** | Requires **`ops->set_brightness`** (PWM backend) | + +### `struct rt_led_ops` + +```c +struct rt_led_ops { + rt_err_t (*set_state)(struct rt_led_device *led, enum rt_led_state state); + rt_err_t (*get_state)(struct rt_led_device *led, enum rt_led_state *out_state); + rt_err_t (*set_period)(struct rt_led_device *led, rt_uint32_t period_ms); + rt_err_t (*set_brightness)(struct rt_led_device *led, rt_uint32_t brightness); +}; +``` + +Implement only the callbacks your hardware supports; missing hooks return **`-RT_ENOSYS`** from the matching **`rt_led_*`** wrapper. + +--- + +## Char device interface (`led.c`) + +Registered with **`RT_DEVICE_FLAG_RDWR`**. + +### `write` + +| Input | Action | +| --- | --- | +| **`off`**, **`on`**, **`toggle`**, **`blink`** | String match → **`rt_led_set_state`** | +| Decimal digits | **`rt_led_set_brightness`** (e.g. **`128`** on PWM LED) | + +### `read` + +Returns ASCII state name: **`off`**, **`on`**, **`toggle`**, or **`blink`** (from **`rt_led_get_state`**). + +Example (MSH / POSIX): + +```text +echo on > /dev/led0 +echo 200 > /dev/led1 /* brightness, PWM LED */ +cat /dev/led0 +``` + +--- + +## Software blink (`led.c`) + +When **`ops->set_period`** is NULL but **`ops->set_state`** exists, **`rt_led_register`** creates a **`blink_timer`** in **`led->sysdata`**: + +- **`RT_LED_S_BLINK`** with GPIO/syscon ops (no native blink) triggers the timer path in **`rt_led_set_state`**. +- Timer toggles off/on with **~80 ms** on and **~1000 ms** off phases (heartbeat-like pattern). +- **`rt_led_set_period`** can change the long off interval via **`RT_TIMER_CTRL_SET_TIME`**. + +For **hardware** PWM blink, implement **`set_period`** in **`rt_led_ops`** instead. + +--- + +## Driver embed pattern + +```c +struct my_led { + struct rt_led_device parent; + /* hardware private */ +}; + +static const struct rt_led_ops my_led_ops = { + .set_state = my_led_set_state, + .get_state = my_led_get_state, +}; + +static int my_led_init(void) +{ + struct my_led *ml = ...; + ml->parent.ops = &my_led_ops; + return rt_led_register(&ml->parent); +} +``` + +OFW platform drivers should use **`rt_led_register`** in **`probe`** and **`rt_led_unregister`** in **`remove`** — see @ref page_device_led_dm. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **`ops` NULL at register** | **`rt_led_register`** returns **`-RT_EINVAL`** | +| **Unregister while blinking** | **`rt_led_unregister`** stops timer and forces off | +| **Brightness on GPIO LED** | **`set_brightness`** not implemented — use **`set_state`** only | +| **Concurrent access** | Core uses **`led->spinlock`** — keep **`ops`** callbacks short | +| **Same pin as GPIO driver** | One owner only — @ref page_device_led_dm | + +--- + +## See also + +- @ref page_device_led_dm — **`gpio-leds`**, **`pwm-leds`**, **`register-bit-led`** +- @ref page_device_syscon +- @ref page_device_power_supply — charging LEDs +- `components/drivers/include/drivers/led.h` diff --git a/documentation/6.components/device-driver/mailbox/dm.md b/documentation/6.components/device-driver/mailbox/dm.md new file mode 100755 index 00000000000..546411e035c --- /dev/null +++ b/documentation/6.components/device-driver/mailbox/dm.md @@ -0,0 +1,168 @@ +@page page_device_mailbox_dm Mailbox device model (DM) + +# Mailbox under `RT_USING_DM` + +**`RT_USING_MBOX`** builds **`mailbox.c`** (controller registry, OFW **`mboxes`** resolution, **`rt_mbox_send`** / **`recv`**) and optionally **`mailbox-pic.c`** (**`rt-thread,pic-mailbox`**). The layer is a **Linux-style mailbox framework**: controllers expose channels; clients request by DT phandle and exchange **small payloads** (often one **`uint32_t`** or a pointer to shared memory). + +Client/controller API semantics: @ref page_device_mailbox. + +Sources: **`components/drivers/mailbox/mailbox.c`**, **`mailbox-pic.c`**, **`components/drivers/include/drivers/mailbox.h`**. + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_DM`** | Required | +| **`RT_USING_OFW`** | Required — **`mboxes`**, **`#mbox-cells`**, **`mbox-names`** | +| **`RT_USING_MBOX`** | Core **`mailbox.c`** | +| **`RT_MBOX_PIC`** | In-tree **`rt-thread,pic-mailbox`** (default **y** when MBOX on) | +| **`SOC_DM_MBOX_DIR`** | BSP adds SoC mailbox controllers | + +There is **no separate mailbox `rt_bus`** — providers register **`struct rt_mbox_controller`** and bind **`rt_ofw_data(provider_np) = ctrl`**. + +--- + +## Architecture + +``` + Mailbox controller driver (platform) + | + | rt_mbox_controller_register(ctrl) + | rt_dm_dev_bind_fwdata(dev, NULL, ctrl) + v + Provider OFW node (#mbox-cells, rt_ofw_data = ctrl) + | + | Consumer: mboxes = <&mbox 0>; mbox-names = "tx"; + v + rt_mbox_request_by_index / by_name(client) + | + | ofw_parse → channel index + | ops->request(chan) [optional] + | chan->client = client + v + Client: rt_mbox_send / rt_mbox_recv path + | + +-- tx_prepare → ops->send → [timer] → rt_mbox_send_done → tx_done + +-- ISR: read HW → rt_mbox_recv → rx_callback +``` + +| Role | Object | Responsibility | +| --- | --- | --- | +| **Controller** | **`struct rt_mbox_controller`** | **`num_chans`**, **`ops`**, per-chan timer (created by core) | +| **Channel** | **`struct rt_mbox_chan`** | Binds **`client`**, **`priv`**, send state | +| **Client** | **`struct rt_mbox_client`** | Lives on consumer **`rt_device`**; **`rx_callback`**, **`tx_*`** | + +--- + +## Controller registration + +```c +rt_err_t rt_mbox_controller_register(struct rt_mbox_controller *ctrl); +rt_err_t rt_mbox_controller_unregister(struct rt_mbox_controller *ctrl); +``` + +| Check | Detail | +| --- | --- | +| **`ctrl->dev`**, **`ops`**, **`num_chans > 0`** | Required or **`-RT_EINVAL`** | +| **`ops->send`**, **`ops->release`** | **De facto mandatory** — unregister always calls **`release`** per channel | +| **`ops->request`**, **`ops->peek`**, **`ops->ofw_parse`** | Optional | +| **Per-channel timer** | One-shot; used when **`rt_mbox_send(..., timeout_ms)`** is not **`RT_WAITING_FOREVER`** | + +**`ofw_parse`**: custom channel index from **`mboxes`** cells; default parser expects **one cell** = channel index. + +--- + +## Consumer device tree + +```dts +scmi: scmi { + compatible = "arm,scmi"; + mboxes = <&pic_mbox 0>; + mbox-names = "tx"; +}; + +remote: remoteproc@0 { + mboxes = <&vendor_mbox 2>, <&vendor_mbox 3>; + mbox-names = "tx", "rx"; +}; +``` + +| Property | Role | +| --- | --- | +| **`mboxes`** | Phandle list + **`#mbox-cells`** per controller | +| **`mbox-names`** | Optional; used with **`rt_mbox_request_by_name`** | +| **`#mbox-cells`** | On **provider** node (default **1** = channel id) | + +**`rt_mbox_request_by_index`** calls **`rt_platform_ofw_request`** on the provider if **`rt_ofw_data`** is empty. Returns **`struct rt_mbox_chan *`** or **error pointer** — test with **`rt_is_err_ptr`**. + +**`rt_mbox_request_by_name`**: returns **`NULL`** if the name is missing in **`mbox-names`** (distinct from error pointers). + +--- + +## In-tree: PIC mailbox (`mailbox-pic.c`) + +**Compatible**: **`rt-thread,pic-mailbox`** + +### Register map (per half) + +| Offset | Name | Role | +| --- | --- | --- | +| **`0x00`** | **`MAILBOX_IMASK`** | Channel interrupt mask | +| **`0x04`** | **`MAILBOX_ISTATE`** | Pending / doorbell state | +| **`0x08 + n*4`** | **`MAILBOX_MSG(n)`** | 32-bit message slot per channel | + +**`reg`** spans **local + peer** windows (each **`size/2`**). **`position = <0>`** (captain): iomap low half, peer = **`regs + size/2`**, initializes both **`IMASK`/`ISTATE`**. **`position = <1>`**: peer low, local high. + +| Property | Role | +| --- | --- | +| **`interrupts`** | Local mailbox IRQ | +| **`peer-interrupts`** | HW IRQ number on peer PIC to poke remote CPU | +| **`uid`** | Device name suffix **`pic-mbox{N}`** | +| **`#mbox-cells = <1>`** | Channel index **0..31** (**`num_chans = 32`**) | + +### Send / receive behavior + +- **`send`**: waits until peer **`ISTATE`** bit clear; writes **`*(uint32_t *)data`** to **`MAILBOX_MSG(index)`**; sets peer **`ISTATE`** bit; triggers **`rt_pic_irq_set_state_raw`** on **`peer_hwirq`**. +- **ISR**: reads pending **`ISTATE`**, loads **`MAILBOX_MSG`** from **peer** window, **`rt_mbox_recv(chan, &msg)`** per bit. +- **Does not call `rt_mbox_send_done`** — TX completion is **not** hardware-ACKed; use a **finite `timeout_ms`** in **`rt_mbox_send`** if **`tx_done`** matters, or treat send as fire-and-forget. + +--- + +## New controller driver checklist + +1. Enable **`RT_USING_MBOX`** (+ BSP Kconfig under **`SOC_DM_MBOX_DIR`**). +2. Allocate **`struct rt_mbox_controller`** + private state; set **`dev`**, **`num_chans`**, **`ops`**. +3. Implement **`send`** (required) and **`release`** (required). +4. Optional **`request`**: unmask channel / enable clock. +5. Optional **`peek`**: non-destructive pending check. +6. Optional **`ofw_parse`**: if **`#mbox-cells`** is not a single channel index. +7. **`rt_mbox_controller_register`** after HW init; store pointer in probe (**`user_data`** or **`rt_ofw_data`**). +8. RX ISR: read hardware, then **`rt_mbox_recv(chan, payload_ptr)`**. +9. If hardware has TX complete IRQ: call **`rt_mbox_send_done(chan, err)`**. +10. **`remove`**: detach IRQ, **`rt_mbox_controller_unregister`**. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **Missing `release`** | Unregister crashes — always provide **`ops->release`** | +| **PIC + `tx_done`** | PIC path never calls **`send_done`** — use timeout or do not rely on **`tx_done`** | +| **`data` lifetime** | Keep buffer valid until **`tx_done`** or synchronous send failure | +| **`tx_prepare` under spinlock** | No blocking calls | +| **Error pointer vs NULL** | **`by_index`** → err ptr; missing **`by_name`** → **`NULL`** | +| **32-bit payload on PIC** | **`send`** dereferences **`uint32_t`** — pass pointer to at least 4 bytes | + +--- + +## See also + +- @ref page_device_mailbox +- @ref page_device_rpmsg — often layered above mailbox + shared memory +- @ref page_device_scmi — typical doorbell consumer +- @ref page_device_pic +- @ref page_device_ofw +- `components/drivers/include/drivers/mailbox.h` diff --git a/documentation/6.components/device-driver/mailbox/mailbox.md b/documentation/6.components/device-driver/mailbox/mailbox.md new file mode 100755 index 00000000000..7148a2fd2ff --- /dev/null +++ b/documentation/6.components/device-driver/mailbox/mailbox.md @@ -0,0 +1,184 @@ +@page page_device_mailbox Mailbox subsystem + +# Mailbox subsystem (API) + +Header: **`components/drivers/include/drivers/mailbox.h`**. Core: **`components/drivers/mailbox/mailbox.c`**. + +The mailbox layer is a **lock-aware wrapper** around hardware **doorbell / message-slot** IPs: one side **sends** a small notification, the peer is woken by **IRQ** and handles **`rx_callback`**. Use for **AMP IPC**, **SCMI** doorbells, **remoteproc** handshakes, and **PIC**-backed mailboxes—not bulk DMA. + +Device-tree binding and **`rt-thread,pic-mailbox`**: @ref page_device_mailbox_dm. + +--- + +## When to use this API + +| Use mailbox when… | Prefer something else when… | +| --- | --- | +| You need a **hardware channel** with **IRQ to peer** (SoC mailbox IP, PIC mailbox). | **Thread-to-thread** only: semaphore, message queue, completion. | +| Firmware expects **doorbell + shared memory** (SCMI, AMP). | **Large payloads** — put data in SRAM, pass a **pointer** or single word. | +| DT exposes **`mboxes` / `#mbox-cells` / `mbox-names`**. | No controller: implement **`rt_mbox_controller_ops`** and register first. | +| Named **RPMsg** endpoints and bus matching are required. | @ref page_device_rpmsg | + +--- + +## Roles + +| Role | Responsibility | +| --- | --- | +| **Controller driver** | **`rt_mbox_controller_register`**: **`num_chans`**, **`ops`**. Implements **`send`**, **`release`**, optional **`peek`**, **`request`**, **`ofw_parse`**. Calls **`rt_mbox_send_done`** when HW TX completes (if applicable). | +| **Client driver** | **`struct rt_mbox_client`** on consumer **`rt_device`**: **`tx_prepare`**, **`tx_done`**, **`rx_callback`**. **`rt_mbox_request_by_index`** or **`by_name`**. | + +--- + +## Data structures + +```c +struct rt_mbox_controller { + struct rt_device *dev; + const struct rt_mbox_controller_ops *ops; + rt_size_t num_chans; + struct rt_mbox_chan *chans; +}; + +struct rt_mbox_controller_ops { + rt_err_t (*request)(struct rt_mbox_chan *); + void (*release)(struct rt_mbox_chan *); + rt_err_t (*send)(struct rt_mbox_chan *, const void *data); + rt_bool_t (*peek)(struct rt_mbox_chan *); + int (*ofw_parse)(struct rt_mbox_controller *, struct rt_ofw_cell_args *); +}; + +struct rt_mbox_client { + struct rt_device *dev; + void (*rx_callback)(struct rt_mbox_client *, void *data); + void (*tx_prepare)(struct rt_mbox_client *, const void *data); + void (*tx_done)(struct rt_mbox_client *, const void *data, rt_err_t err); +}; +``` + +--- + +## Requesting a channel + +```c +struct rt_mbox_chan *rt_mbox_request_by_index(struct rt_mbox_client *client, int index); +struct rt_mbox_chan *rt_mbox_request_by_name(struct rt_mbox_client *client, char *name); +rt_err_t rt_mbox_release(struct rt_mbox_chan *chan); +``` + +| API | Returns | +| --- | --- | +| **`request_by_index`** | Channel pointer or **error pointer** (`rt_is_err_ptr`) | +| **`request_by_name`** | Channel, **error pointer**, or **`NULL`** if name not in **`mbox-names`** | + +**Prerequisites**: valid **`client->dev->ofw_node`**; provider probed (**`rt_ofw_data`** = controller). Optional **`ops->request`** runs after channel selection; on failure core **`release`**s and returns error pointer. + +--- + +## Sending: `rt_mbox_send` + +```c +rt_err_t rt_mbox_send(struct rt_mbox_chan *chan, const void *data, rt_uint32_t timeout_ms); +void rt_mbox_send_done(struct rt_mbox_chan *chan, rt_err_t err); +``` + +**Flow** (under **`chan->lock`**): + +1. Optional **`client->tx_prepare(client, data)`** +2. **`ctrl->ops->send(chan, data)`** — return value is **only** the ops result +3. On success: store **`chan->data`**, clear **`complete`**; if **`timeout_ms != RT_WAITING_FOREVER`**, start one-shot timer +4. On send failure: **`complete = RT_TRUE`** immediately + +**`RT_EOK` from `rt_mbox_send`** means the controller **accepted** the submission, not that **`tx_done`** ran. + +**`rt_mbox_send_done`**: clears **`chan->data`**, calls **`tx_done`**, sets **`complete = RT_TRUE`**. Controller must invoke after HW TX finish (or on error). Timer calls it with **`-RT_ETIMEOUT`** if still incomplete. + +**Implications**: + +- Do not free **`data`** (or pointed buffer) until **`tx_done`** or synchronous send failure +- **`tx_prepare`** runs **with channel spinlock** — non-blocking only +- **`RT_WAITING_FOREVER`**: no timer; requires **`send_done`** from controller for **`tx_done`** (see @ref page_device_mailbox_dm for PIC caveat) + +--- + +## Receiving: `rt_mbox_recv` + +```c +rt_err_t rt_mbox_recv(struct rt_mbox_chan *chan, void *data); +``` + +Does **not** read hardware. Invokes **`client->rx_callback(client, data)`** if set. **Controller ISR** reads registers/FIFO, then passes payload pointer (e.g. local **`uint32_t`**) into **`rt_mbox_recv`**. + +--- + +## `rt_mbox_peek` + +```c +rt_bool_t rt_mbox_peek(struct rt_mbox_chan *chan); +``` + +Forwards to **`ops->peek`** if present; else **`RT_FALSE`**. + +--- + +## Controller unregister + +**`rt_mbox_controller_unregister`**: removes from global list, **`rt_dm_dev_unbind_fwdata`**, **`rt_mbox_release`** on every channel (reverse order), frees **`chans`**. **`release`** must be safe even if **`request`** never ran for a channel. + +--- + +## Example (client sketch) + +```c +static void my_rx(struct rt_mbox_client *cl, void *data) +{ + rt_uint32_t msg = *(rt_uint32_t *)data; + /* handle doorbell */ +} + +static void my_tx_done(struct rt_mbox_client *cl, const void *data, rt_err_t err) +{ + /* release buffer */ +} + +static struct rt_mbox_client mbox_cl = { + .dev = &my_pdev->parent, + .rx_callback = my_rx, + .tx_done = my_tx_done, +}; + +static int my_probe(struct rt_platform_device *pdev) +{ + struct rt_mbox_chan *chan; + + mbox_cl.dev = &pdev->parent; + chan = rt_mbox_request_by_name(&mbox_cl, "tx"); + if (!chan || rt_is_err_ptr(chan)) + return -RT_ERROR; + + rt_uint32_t doorbell = MY_MSG_ID; + return rt_mbox_send(chan, &doorbell, 100); /* 100 ms tx timeout */ +} +``` + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **No `send_done` in controller** | Use finite timeout or fire-and-forget (@ref page_device_mailbox_dm — PIC) | +| **`release` NULL** | Always implement **`ops->release`** | +| **Blocking in `tx_prepare`** | Deadlock risk under spinlock | +| **Confusing return types** | Distinguish **`NULL`** (name) vs **`rt_err_ptr`** (index) | + +--- + +## See also + +- @ref page_device_mailbox_dm +- @ref page_device_rpmsg +- @ref page_device_scmi +- @ref page_device_pic +- `components/drivers/mailbox/mailbox.c` +- `components/drivers/mailbox/mailbox-pic.c` diff --git a/documentation/6.components/device-driver/numa/numa.md b/documentation/6.components/device-driver/numa/numa.md new file mode 100755 index 00000000000..34d1abd8542 --- /dev/null +++ b/documentation/6.components/device-driver/numa/numa.md @@ -0,0 +1,178 @@ +@page page_device_numa NUMA helpers + +# NUMA (non-uniform memory access) + +**`components/drivers/core/numa.c`** provides lightweight helpers to map **CPUs**, **devices**, and **physical addresses** to NUMA nodes on multi-socket / multi-die platforms. The code is **not** a full NUMA allocator—it parses device-tree tables when enabled and exposes three query APIs in **`drivers/core/numa.h`**. + +Built with **`RT_USING_DM`** (always linked from **`core/SConscript`**). Runtime tables are populated only when **`RT_USING_OFW`** and boot **`numa=on`** are active. + +--- + +## Enabling NUMA + +| Requirement | Detail | +| --- | --- | +| **`RT_USING_DM`** | **`numa.c`** in core drivers | +| **`RT_USING_OFW`** | **`INIT_CORE_EXPORT(numa_ofw_init)`** parses DT | +| **Boot argument** | **`numa=on`** in **`chosen/bootargs`** (exact string after **`numa=`**) | + +If **`numa=`** is missing or not **`on`**, **`numa_enabled`** stays false and APIs behave as **single-node fallback** (see below). + +--- + +## Initialization (`numa_ofw_init`) + +``` + bootargs: numa=on + | + v + For each CPU node: read numa-node-id → cpu_numa_map[cpuid] + | + v + For each memory@ node with numa-node-id: + record [start, end) per reg entry on numa_memory_nodes list +``` + +| DT source | Stored as | +| --- | --- | +| **CPU** nodes | **`cpu_numa_map[i]`** = **`numa-node-id`** ( **`i`** = enumeration order, capped at **`RT_CPUS_NR`** ) | +| **`device_type = "memory"`** + **`numa-node-id`** | Linked **`struct numa_memory`** ranges (**`start`/`end`** physical) | + +There is **no** hot-plug update—tables are fixed at core init. + +--- + +## API + +```c +int rt_numa_cpu_id(int cpuid); +int rt_numa_device_id(struct rt_device *dev); +rt_err_t rt_numa_memory_affinity(rt_uint64_t phy_addr, rt_bitmap_t *out_affinity); +``` + +### `rt_numa_cpu_id` + +| Condition | Return | +| --- | --- | +| NUMA disabled | **`-RT_ENOSYS`** | +| **`cpuid >= RT_CPUS_NR`** | **`-RT_EINVAL`** | +| Otherwise | **`cpu_numa_map[cpuid]`** (node id from DT, or **`-RT_ENOSYS`** if never set) | + +### `rt_numa_device_id` + +| Condition | Return | +| --- | --- | +| NUMA disabled | **`(int)(uint32_t)-RT_ENOSYS`** (legacy cast in source) | +| **`numa-node-id` property present** on **`dev->ofw_node`** | Parsed **u32** node id | +| Property missing / read error | Prop-read error code as **int** | + +Use on **`rt_device`** objects that carry an OFW node (PCI, platform, etc.). + +### `rt_numa_memory_affinity` + +Given a **physical address**, fills **`out_affinity`** ( **`RT_BITMAP_LEN(RT_CPUS_NR)`** words) with CPUs whose **`rt_numa_cpu_id`** matches the memory region's node. + +| Condition | Return | +| --- | --- | +| **`out_affinity == NULL`** | **`-RT_EINVAL`** | +| NUMA disabled | Sets **CPU 0** only, **`RT_EOK`** | +| Address inside a recorded range | CPUs on same node set in bitmap, **`RT_EOK`** | +| No matching range | **`-RT_EEMPTY`** | + +Typical use: pick an IRQ affinity mask or thread CPU set for DMA buffers allocated at **`phy_addr`**. + +--- + +## Device tree examples + +### Bootargs + +```dts +/ { + chosen { + bootargs = "numa=on"; + }; +}; +``` + +### CPUs and memory + +```dts +cpu@0 { + device_type = "cpu"; + reg = <0>; + numa-node-id = <0>; +}; + +cpu@100 { + device_type = "cpu"; + reg = <0x100>; + numa-node-id = <1>; +}; + +memory@0 { + device_type = "memory"; + reg = <0x0 0x80000000>; + numa-node-id = <0>; +}; + +memory@80000000 { + device_type = "memory"; + reg = <0x80000000 0x80000000>; + numa-node-id = <1>; +}; +``` + +### Device node + +```dts +nvme@1,0 { + /* ... */ + numa-node-id = <0>; +}; +``` + +--- + +## Integration in-tree + +| Consumer | Usage | +| --- | --- | +| **NVMe** | **`INIT_SECONDARY_CPU_EXPORT(nvme_queue_affinify_fixup)`** sets per-queue IRQ affinity (@ref page_device_nvme_dm) | +| **Future DMA / drivers** | Call **`rt_numa_memory_affinity`** when pinning buffers or IRQs | + +No other in-tree callers are required for NUMA to be useful—BSPs can query node ids when placing shared memory for AMP. + +--- + +## When to use + +| Use **`rt_numa_*`** when… | Skip when… | +| --- | --- | +| BSP documents multiple nodes and **`numa=on`**. | Single RAM bank; **`rt_numa_cpu_id`** always maps to one node. | +| You pin **IRQ affinity** or **thread CPU** near PCIe/DMA memory. | All devices are local and symmetric. | +| Cross-node latency matters for storage/net. | Firmware hides NUMA (uniform UMA). | + +Treat node ids as **hints**—hardware may still allow remote access. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **Forgot `numa=on`** | All APIs behave as UMA (memory affinity → CPU 0 only). | +| **CPU map vs logical id** | **`cpu_numa_map`** indexed by **enumeration order**, not necessarily **`rt_hw_cpu_id()`** unless DT order matches—verify BSP. | +| **`rt_numa_device_id` without property** | Returns error from prop read—not **0**. | +| **Address not in table** | **`rt_numa_memory_affinity`** → **`-RT_EEMPTY`** — extend DT memory nodes or fall back. | +| **No hotplug** | Memory map changes are not tracked after boot. | + +--- + +## See also + +- @ref page_device_nvme_dm — IRQ affinity fixup +- @ref page_device_dma +- @ref page_device_ofw +- `components/drivers/include/drivers/core/numa.h` +- `components/drivers/core/numa.c` diff --git a/documentation/6.components/device-driver/nvme/dm.md b/documentation/6.components/device-driver/nvme/dm.md new file mode 100755 index 00000000000..ec3951b5249 --- /dev/null +++ b/documentation/6.components/device-driver/nvme/dm.md @@ -0,0 +1,147 @@ +@page page_device_nvme_dm NVMe device model (DM) + +# NVMe under `RT_USING_DM` + +**`RT_USING_NVME`** builds the protocol core (**`nvme.c`**) and optionally **`nvme-pci.c`** (**`RT_NVME_PCI`**). Controllers register with **`rt_nvme_controller_register`**; each namespace becomes a **`rt_blk_disk`** for the block layer. + +Controller API and queue ops: @ref page_device_nvme. + +Sources: **`components/drivers/nvme/nvme.c`**, **`nvme-pci.c`**, **`components/drivers/include/drivers/nvme.h`**. + +--- + +## Kconfig / build + +| Option | Depends on | Role | +| --- | --- | --- | +| **`RT_USING_NVME`** | **`RT_USING_DM`**, **`RT_USING_BLK`**, **`RT_USING_DMA`** | Core stack | +| **`RT_USING_NVME_IO_QUEUE`** | **`RT_USING_NVME`** | Number of I/O queues (2/4/8 by priority profile) | +| **`RT_NVME_PCI`** | **`RT_USING_NVME`**, **`RT_USING_PCI`** | PCI function driver (default **y**) | +| **`SOC_DM_NVME_DIR`** | BSP | Extra non-PCI controllers | + +**`RT_USING_NVME_QUEUE`** in **`nvme.h`** is **`1 + RT_USING_NVME_IO_QUEUE * RT_CPUS_NR`** (admin + per-CPU IO slots). + +--- + +## PCI transport (`nvme-pci.c`) + +### Matching + +```c +static const struct rt_pci_device_id pci_nvme_ids[] = { + { RT_PCI_DEVICE_ID(PCI_VENDOR_ID_REDHAT, 0x0010) }, + { RT_PCI_DEVICE_CLASS(PCIS_STORAGE_EXPRESS, ~0) }, +}; +``` + +Any **NVMe class** PCI function binds **`nvme-pci`** driver. + +### Probe flow + +``` + pci_nvme_probe(pdev) + | + | rt_pci_iomap(pdev, 0) → nvme->regs + | ops = quirk->ops ?: pci_nvme_std_ops ("PCI") + | MSI-X: rt_pci_msix_enable → nvme->irqs[i] + | else: single pdev->irq, rt_pci_irq_unmask + | rt_pci_set_master(pdev) + v + rt_nvme_controller_register(nvme) + | + v + pdev->parent.user_data = pci_nvme +``` + +### Remove / shutdown + +1. **`rt_nvme_controller_unregister`** +2. MSI-X disable or INTx mask +3. **`rt_pci_clear_master`**, **`rt_iounmap(regs)`**, free **`pci_nvme_controller`** + +### Quirks + +**`pdev->id->data`** may point to **`struct pci_nvme_quirk`** with alternate **`rt_nvme_ops`** (vendor MMIO). Default uses standard doorbell path in **`nvme.c`**. + +--- + +## IRQ and NUMA affinity + +| Mode | Behavior | +| --- | --- | +| **MSI-X** | Up to **`RT_USING_NVME_QUEUE`** vectors; linear index mapping | +| **INTx** | **`irqs_nr = 1`**, shared line — rely on **`complete_cmd`** to disambiguate if needed | + +**`INIT_SECONDARY_CPU_EXPORT(nvme_queue_affinify_fixup)`** (in **`nvme.c`**): + +- Runs when secondary CPUs start +- For each controller I/O queue index **`i ≡ cpuid (mod RT_CPUS_NR)`**, sets IRQ affinity to that CPU via **`rt_pic_irq_set_affinity`** +- Pairs with @ref page_device_numa when **`numa=on`** — keep queue IRQ near memory node + +Assign **`nvme->irqs[]`** before **`rt_nvme_controller_register`** — core wires ISR per queue during setup. + +--- + +## DMA + +Queue rings allocated with **`rt_dma_alloc(nvme->dev, ...)`** and **`nvme_queue_dma_flags()`** (coherent / device attributes per **`dev`**). + +Identify buffers use **`rt_malloc_align(..., nvme->page_size)`** after page size is negotiated from **`CAP.MPSMIN/MAX`**. + +--- + +## Block device exposure + +After Identify NS: + +```c +ndev->parent.ops = &nvme_blk_ops; +ndev->parent.parallel_io = RT_TRUE; +rt_dm_dev_set_name(&ndev->parent.parent, "%sn%u", nvme->name, nsid); +rt_hw_blk_disk_register(&ndev->parent); +``` + +Partitions use standard **`RT_BLK_PARTITION_MAX`** — see disk/partitions docs if enabled in BSP. + +--- + +## Non-PCI controllers + +1. Map **`regs`**, implement **`struct rt_nvme_ops`** ( **`setup_queue`/`submit_cmd`/`complete_cmd`** as needed). +2. Fill **`irqs[]`** for admin + IO queues. +3. Set **`nvme->dev`** to the **`rt_device`** used for DMA mapping. +4. **`rt_nvme_controller_register`**. +5. Add Kconfig under **`SOC_DM_NVME_DIR`** and platform/PCIe-alt probe. + +--- + +## Checklist (PCI BSP) + +1. Enable **`RT_USING_DM`**, **`RT_USING_PCI`**, **`RT_USING_BLK`**, **`RT_USING_DMA`**, **`RT_USING_NVME`**, **`RT_NVME_PCI`**. +2. ECAM/host bridge probes before NVMe function. +3. Ensure **`rt_pci_set_master`** path runs (done in **`pci_nvme_probe`**). +4. Prefer **MSI-X** in firmware/DT if available. +5. For multi-node servers: **`numa=on`** + CPU/memory **`numa-node-id`** in DT (@ref page_device_numa). +6. Verify namespace appears as **`nvme0n1`** (etc.) and partition mount if used. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **BAR not mapped** | **`rt_pci_iomap`** failure → probe abort | +| **MSI-X vector shortage** | Falls back to INTx — may need **`complete_cmd`** | +| **`CSTS == -1`** | Usually bad BAR/endianness — register rejects early | +| **Inverted `setup_queue` success** | @ref page_device_nvme — match **`nvme.c`** polarity | +| **Secondary CPU IRQ affinity** | Ensure **`nvme_queue_affinify_fixup`** runs (SMP BSP) | + +--- + +## See also + +- @ref page_device_nvme +- @ref page_device_pci +- @ref page_device_numa +- @ref page_device_dma +- `components/drivers/nvme/Kconfig` diff --git a/documentation/6.components/device-driver/nvme/nvme.md b/documentation/6.components/device-driver/nvme/nvme.md new file mode 100755 index 00000000000..a8b95cb913b --- /dev/null +++ b/documentation/6.components/device-driver/nvme/nvme.md @@ -0,0 +1,150 @@ +@page page_device_nvme NVMe subsystem + +# NVMe controller API + +Header: **`components/drivers/include/drivers/nvme.h`**. Core: **`components/drivers/nvme/nvme.c`**. + +RT-Thread implements an **NVMe host** stack: admin + I/O queues, Identify, namespace scan, and **`rt_hw_blk_disk_register`** per namespace. PCI binding: @ref page_device_nvme_dm. + +--- + +## Kconfig (summary) + +| Option | Role | +| --- | --- | +| **`RT_USING_NVME`** | Core **`nvme.c`** — needs **`RT_USING_DM`**, **`RT_USING_BLK`**, **`RT_USING_DMA`** | +| **`RT_USING_NVME_IO_QUEUE`** | I/O queue count (default scales with **`RT_THREAD_PRIORITY_*`**) | +| **`RT_NVME_PCI`** | **`nvme-pci.c`** — needs **`RT_USING_PCI`** | + +--- + +## Architecture + +``` + Transport (e.g. PCI): map BAR, setup IRQs/MSI-X + | + | fill struct rt_nvme_controller + ops + v + rt_nvme_controller_register(nvme) + | + | CAP/MQES, admin queue, IO queues (DMA) + | Identify Controller, scan namespaces + v + Per-NS: rt_hw_blk_disk_register → block device "nvme0n1" ... + | + v + blk layer: read/write/flush via nvme_submit_io_cmd (per-CPU queue) +``` + +--- + +## `struct rt_nvme_controller` (caller fills before register) + +| Member | Owner | Meaning | +| --- | --- | --- | +| **`dev`** | **Caller** | Used for **`rt_dma_alloc`/`free`** on queue buffers | +| **`regs`** | **Caller** | MMIO base (**`CAP`**, **`CC`**, doorbells) | +| **`ops`** | **Caller** | **Required**; at least **`name`** for logging | +| **`irqs_nr`**, **`irqs[]`** | **Caller** | One entry per queue (MSI-X preferred); see PCI glue | +| **`nvme_id`**, **`name`** | **Core** | **`rt_dm_ida`**, **`nvme%u`** | +| **`cap`**, **`queue_depth`**, **`doorbell_*`** | **Core** | From **`CAP`** register | +| **`max_transfer_shift`**, **`write_zeroes`**, **`sgl_mode`** | **Core** | From Identify Controller | +| **`queue[]`**, **`admin_queue`**, **`io_queues[]`** | **Core** | SQ/CQ DMA rings + completions | + +Do not pre-fill queue state—the core zeroes and allocates DMA in **`nvme_alloc_queue`**. + +--- + +## `struct rt_nvme_ops` + +```c +struct rt_nvme_ops { + const char *name; + rt_err_t (*setup_queue)(struct rt_nvme_queue *queue); + rt_err_t (*cleanup_queue)(struct rt_nvme_queue *queue); + rt_err_t (*submit_cmd)(struct rt_nvme_queue *queue, struct rt_nvme_command *cmd); + void (*complete_cmd)(struct rt_nvme_queue *queue, struct rt_nvme_command *cmd); +}; +``` + +| Callback | When | Role | +| --- | --- | --- | +| **`name`** | Logging | e.g. **`"PCI"`** | +| **`setup_queue`** | After SQ/CQ DMA alloc in **`nvme_alloc_queue`** | SoC-specific queue programming | +| **`cleanup_queue`** | **`nvme_free_queue`** | Undo setup | +| **`submit_cmd`** | After command copied to SQ slot, before doorbell | Custom doorbell / FIFO | +| **`complete_cmd`** | CQ ISR after phase check | Extra HW ack (shared IRQ, vendor IP) | + +**Return polarity (important):** in current **`nvme.c`**, **`setup_queue`** / **`cleanup_queue`** use **`if (!(err = ops->...))`** — a **non-zero** return is treated as **success**, **zero** as failure. Match this when porting quirks, or align **`nvme.c`** with upstream when fixing. + +**`submit_cmd`**: uses normal **`RT_EOK`** — non-zero aborts submit. + +--- + +## Registration API + +```c +rt_err_t rt_nvme_controller_register(struct rt_nvme_controller *nvme); +rt_err_t rt_nvme_controller_unregister(struct rt_nvme_controller *nvme); +``` + +### `rt_nvme_controller_register` sequence + +1. Validate **`nvme`**, **`ops`**; check **`CSTS`** not **`0xffffffff`** +2. **`rt_dm_ida_alloc`** → **`nvme%u`** +3. Read **`CAP`**, configure **admin queue** (disable → alloc → enable) +4. Create **I/O queues** (**`RT_USING_NVME_IO_QUEUE * RT_CPUS_NR`** max) +5. **Identify Controller** — MDTS, VWC, ONCS, SGL support +6. **`nvme_scan_device`** — for each namespace id: Identify NS, **`rt_hw_blk_disk_register`** +7. Link on global **`nvme_nodes`** list + +### `rt_nvme_controller_unregister` + +Removes namespaces, IO/admin queues, frees IDA, **shutdown + disable** controller. + +--- + +## Namespaces (`struct rt_nvme_device`) + +| Field | Role | +| --- | --- | +| Embeds **`struct rt_blk_disk parent`** | Block layer entry | +| **`nsid`** | Namespace ID (1-based in scan loop) | +| **`lba_shift`** | From active LBA format | +| **`id`** | Copy of **`struct rt_nvme_id_ns`** | + +Disk name pattern: **`nvme{N}n{nsid}`** (e.g. **`nvme0n1`**). + +--- + +## Command path (I/O) + +- **`nvme_submit_io_cmd`**: picks I/O queue **`rt_atomic_add(&ioqid[cpu], RT_CPUS_NR) % io_queue_max`** (per-CPU striping). +- **`nvme_submit_cmd`**: copies **`struct rt_nvme_command`** to SQ tail, rings doorbell (unless **`ops->submit_cmd`**), **`rt_completion_wait`** for CQ. +- Data path: **PRP** by default; **SGL** if Identify reports SGL support. + +Admin commands use **`admin_queue`** with a **60 s** completion timeout; I/O queues use **`RT_WAITING_FOREVER`**. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **`setup_queue` return code** | Inverted vs **`RT_EOK`** — see ops table above | +| **Page size** | Host **`ARCH_PAGE_SHIFT`** must be ≥ device **`MPSMIN`** or register fails | +| **IRQ count** | **`irqs_nr`** should cover admin + IO queues used; MSI-X preferred (@ref page_device_nvme_dm) | +| **Unregister order** | Mask IRQs in transport **`remove`** before **`rt_nvme_controller_unregister`** | +| **Admin queue blocking** | Do not hold locks that block admin path from I/O context | + +--- + +## See also + +- @ref page_device_nvme_dm +- @ref page_device_pci +- @ref page_device_numa +- @ref page_device_dma +- +- `components/drivers/nvme/nvme.c` +- `components/drivers/nvme/nvme-pci.c` diff --git a/documentation/6.components/device-driver/nvmem/dm.md b/documentation/6.components/device-driver/nvmem/dm.md new file mode 100755 index 00000000000..56f5040cfcd --- /dev/null +++ b/documentation/6.components/device-driver/nvmem/dm.md @@ -0,0 +1,114 @@ +@page page_device_nvmem_dm NVMEM device model (DM) + +# NVMEM under `RT_USING_DM` + +**`RT_USING_NVMEM`** builds **`nvmem.c`** only. **Provider** drivers register **`struct rt_nvmem_device`** and set **`rt_ofw_data(provider_np)`**. **Consumers** parse **`nvmem-cells`** phandles. + +API: @ref page_device_nvmem. Sources: **`nvmem.c`**, **`nvmem.h`**. + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_DM`** | Required | +| **`RT_USING_OFW`** | Required | +| **`RT_USING_PIN`** | Optional **`wp`** GPIO | +| **`RT_USING_NVMEM`** | Core; selects **`RT_USING_ADT_REF`** | +| **`SOC_DM_NVMEM_DIR`** | BSP OTP/eFuse/EEPROM drivers | + +No **`rt_bus`** — provider **`reg_read`/`reg_write`** + OFW lookup only. + +--- + +## Architecture + +``` + BSP provider (efuse/otp/eeprom) + | rt_nvmem_device_register, rt_ofw_data = ndev + v + Consumer: nvmem-cells = <&provider N>; + v + rt_nvmem_get_cell_by_name → rt_nvmem_cell_read/write + v + rt_nvmem_put_cell +``` + +--- + +## Provider DT + +```dts +efuse: efuse@110000 { + compatible = "vendor,soc-efuse"; + reg = <0x110000 0x1000>; + #address-cells = <1>; + #size-cells = <1>; + #nvmem-cell-cells = <1>; + read-only; + + mac@0 { reg = <0x00 0x06>; }; +}; +``` + +| Property | Role | +| --- | --- | +| **`#nvmem-cell-cells`** | Usually **1** — phandle arg = cell index | +| **`read-only`** | Forces read-only at register | +| **`wp`** | Write-protect GPIO (unless **`ignore_wp`**) | + +--- + +## Consumer DT + +```dts +gmac: ethernet@0 { + nvmem-cells = <&efuse 0>; + nvmem-cell-names = "mac-address"; +}; +``` + +**`ofw_nvmem_get_cell`** order: + +1. **`rt_ofw_data(cell_np)`** if cached +2. Match **`append_cell`** static list by index/id +3. Allocate dynamic cell from child **`reg`** (**`offset`**, **`bytes`**) + +Dynamic cells are **`free_able`** and removed on last **`put_cell`**. + +--- + +## Provider checklist + +1. Implement **`reg_read`**; **`reg_write`** only if programmable. +2. Set **`size`**, **`parent.ofw_node`**, callbacks. +3. **`rt_nvmem_device_register`**. +4. Optional **`append_cell`** for fixed index/id layouts. +5. **`remove`**: all **`put_cell`**, then **`unregister`** when ref is 1. +6. Add driver under **`SOC_DM_NVMEM_DIR`**. + +--- + +## Bit-packed cells + +**`bit_offset`** / **`nbits`** on **`struct rt_nvmem_cell`**: core masks after byte read; write merges into existing bytes. Prefer byte-aligned **`reg = `**; validate parsing in **`nvmem.c`** or use **`append_cell`**. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Provider not probed | **`rt_platform_ofw_request`** first | +| **`#nvmem-cell-cells` mismatch** | Match provider binding | +| Concurrent OTP | Serialize in **`reg_read`/`reg_write`** | +| **`unregister` EBUSY** | Release all cell refs | + +--- + +## See also + +- @ref page_device_nvmem +- @ref page_device_iio_dm +- @ref page_device_ofw \ No newline at end of file diff --git a/documentation/6.components/device-driver/nvmem/nvmem.md b/documentation/6.components/device-driver/nvmem/nvmem.md new file mode 100755 index 00000000000..872fbfc3d3b --- /dev/null +++ b/documentation/6.components/device-driver/nvmem/nvmem.md @@ -0,0 +1,111 @@ +@page page_device_nvmem NVMEM subsystem + +# Non-volatile memory (NVMEM) + +Header: **`components/drivers/include/drivers/nvmem.h`**. Core: **`components/drivers/nvmem/nvmem.c`**. + +NVMEM models **OTP, eFuse, EEPROM slices, PMIC trim, MAC/calibration blobs** as a **provider device** plus **cells** (named or indexed byte/bit ranges). Consumers resolve cells from device tree phandles—see @ref page_device_nvmem_dm. + +There is **no in-tree OTP driver** under **`components/drivers/nvmem/`**; SoC providers live under **`SOC_DM_NVMEM_DIR`** in the BSP. + +--- + +## When to use NVMEM + +| Use **`rt_nvmem_*`** when… | Use a dedicated driver when… | +| --- | --- | +| **Multiple consumers** need slices of one OTP/eFuse controller. | Only one driver reads a fixed offset once at boot. | +| DT uses **`nvmem-cells`** / **`nvmem-cell-names`** (Linux-style). | MAC/calibration is hard-coded in BSP C sources. | +| You want refcounted **`rt_nvmem_cell`** handles. | Direct MMIO in a single platform driver is enough. | + +--- + +## Data structures + +```c +struct rt_nvmem_device { + struct rt_device parent; + int cells_nr; + rt_list_t cell_nodes; + 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; + int word_size; + int stride; + rt_bool_t read_only; + rt_bool_t ignore_wp; + rt_base_t wp_pin; + rt_uint8_t wp_pin_active; + struct rt_ref ref; + struct rt_spinlock spinlock; + void *priv; +}; +``` + +| Field | Role | +| --- | --- | +| **`reg_read` / `reg_write`** | Byte access at **`offset`** | +| **`read_only`** | DT **`read-only`**, no **`reg_write`**, or preset flag | +| **`wp_pin`** | Optional **`wp`** GPIO for EEPROM | + +--- + +## Provider API + +```c +rt_err_t rt_nvmem_device_register(struct rt_nvmem_device *ndev); +rt_err_t rt_nvmem_device_unregister(struct rt_nvmem_device *ndev); +rt_err_t rt_nvmem_device_append_cell(struct rt_nvmem_device *ndev, struct rt_nvmem_cell *cell); +``` + +**`register`**: optional **`wp`** pin, **`read_only`** from DT / missing **`reg_write`**, **`rt_ofw_data(np)=ndev`**. + +**`unregister`**: only if **`rt_ref_read(&ndev->ref)==1`**. + +**`append_cell`**: static cells (**`free_able` false**) known at probe time. + +--- + +## Consumer API + +```c +struct rt_nvmem_cell *rt_nvmem_get_cell_by_index(struct rt_device *dev, int index); +struct rt_nvmem_cell *rt_nvmem_get_cell_by_name(struct rt_device *dev, const char *id); +void rt_nvmem_put_cell(struct rt_nvmem_cell *cell); +rt_ssize_t rt_nvmem_cell_read(struct rt_nvmem_cell *cell, void *buffer, rt_size_t len); +rt_ssize_t rt_nvmem_cell_write(struct rt_nvmem_cell *cell, void *buffer, rt_size_t len); +``` + +- **`len <= cell->bytes`** and **`<= nvmem->size`** +- Bit fields: **`bit_offset`**, **`nbits`** — read shifts/masks; write read-modify-write +- **`wp_pin`** deasserted during **`reg_write`** +- Typed helpers: **`rt_nvmem_cell_read_u8/u16/u32/u64`** + +--- + +## Example + +```c +cell = rt_nvmem_get_cell_by_name(&pdev->parent, "mac-address"); +if (!cell || rt_is_err_ptr(cell)) return -RT_ERROR; +rt_nvmem_cell_read(cell, mac, 6); +rt_nvmem_put_cell(cell); +``` + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Forgot **`put_cell`** | Blocks unregister | +| OTP write | Omit **`reg_write`** | +| No BSP provider | **`SOC_DM_NVMEM_DIR`** | + +--- + +## See also + +- @ref page_device_nvmem_dm +- @ref page_device_iio_dm +- `components/drivers/nvmem/Kconfig` \ No newline at end of file diff --git a/documentation/6.components/device-driver/ofw/dtc.md b/documentation/6.components/device-driver/ofw/dtc.md index 2cc3c1b34c7..bc8f701394d 100644 --- a/documentation/6.components/device-driver/ofw/dtc.md +++ b/documentation/6.components/device-driver/ofw/dtc.md @@ -2,6 +2,8 @@ # Introduction to the DTC +Runtime driver APIs that consume the flattened device tree (**`rt_ofw_*`**, **`rt_fdt_*`**, MMIO and IRQ parsing) are documented under @ref page_device_ofw and @ref page_device_ofw_boot. + Device Tree Compiler, dtc, takes as input a device-tree in a given format and outputs a device-tree in another format for booting kernels on embedded systems. Typically, the input format is "dts" (device-tree source), a human readable source format, and creates a "dtb" (device-tree binary), or binary format as output. @@ -10,7 +12,7 @@ Typically, the input format is "dts" (device-tree source), a human readable sour ## Generate DTS When you have a DTB or FDT file from firmware or another runtime system, you might want to convert it into a DTS file for easier reading. -You can do this in Python or your SConscript file. For example, assuming you have `dummpy.dtb`: +You can do this in Python or your SConscript file. For example, assuming you have `dummy.dtb`: ```python import os, sys @@ -21,18 +23,18 @@ sys.path.append(RTT_ROOT + '/tools') from building import * import dtc -dtc.dtb_to_dts(RTT_ROOT, "dummpy.dtb") +dtc.dtb_to_dts(RTT_ROOT, "dummy.dtb") ``` -This will generate a dummpy.dts in the current directory. If a file with the same name already exists, it will be replaced. +This will generate a dummy.dts in the current directory. If a file with the same name already exists, it will be replaced. To avoid overwriting, you can specify a different output name: ```python [...] -dtc.dtb_to_dts(RTT_ROOT, "dummpy.dtb", "dummpy-tmp.dts") +dtc.dtb_to_dts(RTT_ROOT, "dummy.dtb", "dummy-tmp.dts") # or -dtc.dtb_to_dts(RTT_ROOT, "dummpy.dtb", dts_name = "dummpy-tmp.dts") +dtc.dtb_to_dts(RTT_ROOT, "dummy.dtb", dts_name = "dummy-tmp.dts") ``` ## Generate DTB @@ -120,7 +122,7 @@ sys.path.append(RTT_ROOT + '/tools') from building import * import dtc -dtc.dts_to_dtb(RTT_ROOT, ["dummpy.dts"] +dtc.dts_to_dtb(RTT_ROOT, ["dummy.dts"]) ``` To append more include paths, for example, SoC DM headers: @@ -128,7 +130,7 @@ To append more include paths, for example, SoC DM headers: ```python [...] -dtc.dts_to_dtb(RTT_ROOT, ["dummpy.dts"], include_paths = ['dm/include', 'firmware']) +dtc.dts_to_dtb(RTT_ROOT, ["dummy.dts"], include_paths = ['dm/include', 'firmware']) ``` ### Multiple DTB @@ -244,10 +246,10 @@ Build all DTBs together: ```python [...] -dtc.dts_to_dtb(RTT_ROOT, ["dummpy-vendorA.dts", "dummpy-vendorB.dts", "dummpy-vendorC.dts"]) +dtc.dts_to_dtb(RTT_ROOT, ["dummy-vendorA.dts", "dummy-vendorB.dts", "dummy-vendorC.dts"]) ``` -This will produce `dummpy-vendorA.dtb`, `dummpy-vendorB.dtb`, and `dummpy-vendorC.dtb` +This will produce `dummy-vendorA.dtb`, `dummy-vendorB.dtb`, and `dummy-vendorC.dtb` ## Warnings @@ -257,7 +259,7 @@ To suppress specific warnings: ```python [...] -dtc.dts_to_dtb(RTT_ROOT, ["dummpy.dts"], ignore_warning = ["simple_bus_reg", "unit_address_vs_reg", "clocks_is_cell", "gpios_property"]) +dtc.dts_to_dtb(RTT_ROOT, ["dummy.dts"], ignore_warning = ["simple_bus_reg", "unit_address_vs_reg", "clocks_is_cell", "gpios_property"]) ``` Make sure your DTS is valid! @@ -269,6 +271,6 @@ DTC provides additional command-line options (see dtc --help). You can pass raw ```python [...] -dtc.dtb_to_dts(RTT_ROOT, "dummpy.dtb", options = "--quiet") -dtc.dts_to_dtb(RTT_ROOT, ["dummpy.dts"], options = "--quiet") +dtc.dtb_to_dts(RTT_ROOT, "dummy.dtb", options = "--quiet") +dtc.dts_to_dtb(RTT_ROOT, ["dummy.dts"], options = "--quiet") ``` diff --git a/documentation/6.components/device-driver/ofw/ofw.md b/documentation/6.components/device-driver/ofw/ofw.md new file mode 100644 index 00000000000..e09edbb06d1 --- /dev/null +++ b/documentation/6.components/device-driver/ofw/ofw.md @@ -0,0 +1,86 @@ +@page page_device_ofw Open Firmware (OFW) + +# Open Firmware (OFW) overview + +RT-Thread **OFW** parses the boot **DTB** into **`struct rt_ofw_node`**, then exposes property I/O, addressing, interrupts, and platform device enumeration. Implementation mirrors **`components/drivers/ofw/`** source layout. + +| Source / header | Documentation | +| --- | --- | +| **`base.c`**, **`ofw.c`** — `ofw.h` | @ref page_device_ofw_base | +| **`fdt.c`** — `ofw_fdt.h` | @ref page_device_ofw_boot | +| **`io.c`** — `ofw_io.h` | @ref page_device_ofw_io | +| **`irq.c`** — `ofw_irq.h` | @ref page_device_ofw_irq | +| **`raw.c`** — `ofw_raw.h` | @ref page_device_ofw_raw | +| **`core/platform_ofw.c`** | @ref page_device_ofw_platform | +| Build **DTS → DTB** | @ref page_device_dtc | + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_OFW`** | OFW + libfdt; requires **`RT_USING_DM`** | +| **`RT_USING_BUILTIN_FDT`** | Link **`RT_BUILTIN_FDT_PATH`** (default `rtthread.dtb`) | +| **`RT_FDT_EARLYCON_MSG_SIZE`** | Early console ring (KB, default 128) | +| **`RT_USING_OFW_BUS_RANGES_NUMBER`** | Cached **`ranges`** slots (4 or 8) | + +--- + +## Typical driver flow + +``` + Bootloader DTB + → rt_fdt_prefetch / scan_memory / unflatten (@ref page_device_ofw_boot) + → INIT_PLATFORM_EXPORT(platform_ofw_device_probe) (@ref page_device_ofw_platform) + → driver probe(np): reg/irq/phandles + rt_ofw_get_address + rt_dm_dev_iomap (@ref page_device_ofw_io) + rt_ofw_get_irq / rt_ofw_map_irq (@ref page_device_ofw_irq) + rt_ofw_parse_phandle_cells + rt_platform_ofw_request (@ref page_device_ofw_base) +``` + +--- + +## Phandle consumer pattern + +Most DM subsystems share the same parse sequence: + +1. **`rt_ofw_parse_phandle_cells(np, "clocks", "#clock-cells", index, &args)`** +2. **`provider_np = args.data`**; if **`!rt_ofw_data(provider_np)`** → **`rt_platform_ofw_request(provider_np)`** +3. Use subsystem API (**`rt_clk_get_by_index`**, **`rt_mbox_request_channel`**, …) + +| Property | `#*-cells` | See also | +| --- | --- | --- | +| **`clocks`** | **`#clock-cells`** | @ref page_device_clk | +| **`resets`** | **`#reset-cells`** | reset DM | +| **`mboxes`** | **`#mbox-cells`** | @ref page_device_mailbox_dm | +| **`io-channels`** | **`#io-channel-cells`** | @ref page_device_iio_dm | +| **`nvmem-cells`** | **`#nvmem-cell-cells`** | @ref page_device_nvmem_dm | + +**`rt_ofw_parse_object(np, obj_name, "#clock-cells")`** resolves composite objects in **`rt_ofw_data`** when one node exports multiple controllers. + +--- + +## Driver quick notes + +| Topic | Guidance | +| --- | --- | +| **`status`** | Use **`rt_ofw_node_is_available`** before attaching hardware | +| **`reg`** | Use **`rt_ofw_io_*`** / **`rt_ofw_get_address`** — never hard-code parent **`#address-cells`** | +| **IRQ** | Direct **`interrupts`** vs bridge **`interrupt-map`** — see @ref page_device_ofw_irq | +| **Refs** | Pair **`rt_ofw_node_get`** / **`rt_ofw_node_put`** when caching **`np`** | +| **`compatible`** | Most specific string first in DTS and **`rt_ofw_node_id`** table | + +--- + +## Normative references + +- [Device Tree Specification](https://devicetree-specification.readthedocs.io/) +- **`components/drivers/include/dt-bindings/`** — include from DTS via cpp + +## See also + +- `components/drivers/ofw/` — implementation +- @ref page_device_platform +- @ref page_device_bus +- @ref page_device_dm diff --git a/documentation/6.components/device-driver/ofw/ofw_base.md b/documentation/6.components/device-driver/ofw/ofw_base.md new file mode 100755 index 00000000000..552b0406b15 --- /dev/null +++ b/documentation/6.components/device-driver/ofw/ofw_base.md @@ -0,0 +1,134 @@ +@page page_device_ofw_base OFW nodes and properties + +# OFW core (`base.c`, `ofw.c`) + +Node tree, properties, phandles, matching, stubs, and **`rt_ofw_parse_object`**. Boot: @ref page_device_ofw_boot. IO: @ref page_device_ofw_io. IRQ: @ref page_device_ofw_irq. + +Headers: **`drivers/ofw.h`**. Sources: **`components/drivers/ofw/base.c`**, **`ofw.c`**. + +--- + +## Core types + +| Type | Role | +| --- | --- | +| **`struct rt_ofw_prop`** | Property list entry: **`name`**, **`length`**, **`value`** | +| **`struct rt_ofw_node`** | **`full_name`**, **`phandle`**, **`props`**, tree links, **`ref`**, **`flags`**, **`rt_data`**, optional **`dev`** | +| **`struct rt_ofw_cell_args`** | Parsed phandle + specifier (**`args[]`**, max **`RT_OFW_MAX_CELL_ARGS`**) | +| **`struct rt_ofw_node_id`** | **`compatible`** / **`name`** / **`type`** match table row | +| **`struct rt_ofw_stub`** | Early probe: **`ids`**, **`handler(np, id)`** | + +### Node flags (`RT_OFW_F_*`) + +| Flag | Meaning | +| --- | --- | +| **`RT_OFW_F_SYSTEM`** | Meta node (`/`, `/cpus`, `/chosen`, …) — platform walk skips | +| **`RT_OFW_F_READLY`** | Stub or driver already bound | +| **`RT_OFW_F_PLATFORM`** | **`rt_platform_device`** created | +| **`RT_OFW_F_OVERLAY`** | From applied DT overlay | + +APIs: **`rt_ofw_node_set_flag`**, **`rt_ofw_node_test_flag`**, **`rt_ofw_node_clear_flag`**. + +--- + +## Lifetime + +| API | Role | +| --- | --- | +| **`rt_ofw_node_get` / `rt_ofw_node_put`** | Reference count on **`np`** | +| **`rt_ofw_node_destroy`** | Tear down subtree when applicable | +| **`rt_ofw_data(np)`** | Driver private pointer in **`np->rt_data`** | + +--- + +## Finding nodes + +| API | Role | +| --- | --- | +| **`rt_ofw_find_node_by_path`** | Absolute path (`/soc/uart@1000`) | +| **`rt_ofw_find_node_by_compatible`** | DFS from **`from`** (**`RT_NULL`** = whole tree) | +| **`rt_ofw_find_node_by_ids` / `_r`** | Match **`rt_ofw_node_id`** table | +| **`rt_ofw_find_node_by_phandle`** | Resolve numeric phandle | +| **`rt_ofw_find_node_by_name`**, **`_by_prop`**, **`_by_tag`**, **`_by_type`** | Other keys | +| **`rt_ofw_get_parent`**, **`rt_ofw_get_next_*`** | Walk tree | +| **`rt_ofw_foreach_*` macros** | Prefer macros in **`ofw.h`** over manual sibling loops | + +**Availability**: **`rt_ofw_node_is_available`** (**`status`**), **`rt_ofw_machine_is_compatible`** (root). + +--- + +## Properties + +| API | Role | +| --- | --- | +| **`rt_ofw_get_prop`** | Find property by name | +| **`rt_ofw_prop_read_raw`** | Blob + length | +| **`rt_ofw_prop_read_u8/u16/u32/s32/u64/string/bool`** | Typed index 0 | +| **`rt_ofw_prop_read_*_index`**, **`*_array_index`** | Multi-element | +| **`rt_ofw_prop_count_of_u32`** (etc.) | Element count | +| **`rt_ofw_append_prop`** | Runtime append (rare) | + +--- + +## Phandles + +| API | Role | +| --- | --- | +| **`rt_ofw_parse_phandle`** | Single phandle property by index | +| **`rt_ofw_parse_phandle_cells`** | List + **`#*-cells`** → **`rt_ofw_cell_args`** | +| **`rt_ofw_count_phandle_cells`** | Entry count in list | +| **`rt_ofw_parse_object`** | Find named object in composite **`rt_ofw_data`** | + +**`ofw.c`** registers **`#clock-cells`**, **`#reset-cells`**, **`#power-domain-cells`** object layouts when those subsystems are enabled. + +--- + +## Matching and stubs + +| API / macro | Role | +| --- | --- | +| **`rt_ofw_node_match`**, **`rt_ofw_prop_match`** | Match **`compatible`** against **`rt_ofw_node_id[]`** | +| **`RT_OFW_STUB_EXPORT`** | Link stub into **`.rt_ofw_data.stub.*`** | +| **`rt_ofw_stub_probe_range`** | Run stubs on a node (class ranges via **`RT_OFW_STUB_RANGE_EXPORT`**) | + +Used by early serial, hwcache, and other non-platform bindings. + +--- + +## Aliases and CPU + +| API | Role | +| --- | --- | +| **`rt_ofw_get_alias_node`**, **`rt_ofw_get_alias_id`** | **`/aliases`** resolution | +| **`rt_ofw_get_cpu_node`**, **`rt_ofw_foreach_cpu_node`** | CPU nodes under **`/cpus`** | +| **`rt_ofw_get_cpu_id`**, **`rt_ofw_get_cpu_hwid`** | MPIDR / hardware ID | +| **`rt_ofw_map_id`** | Generic **`*-map`** / **`*-map-mask`** lookup | + +**`ofw_alias_scan`** runs during unflatten (**`base.c`**). + +--- + +## Console and debug + +| API | Role | +| --- | --- | +| **`rt_ofw_console_setup`** | Console from **`/chosen`** | +| **`rt_ofw_node_dump_dts`** | Print subtree as DTS-like text (**`RT_USING_CONSOLE`**) | + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Leaked **`np`** | Always **`put`** after **`get`** | +| Wrong phandle index | Check **`rt_ofw_count_phandle_cells`** and **`*-names`** | +| **`compatible` order** | Most specific first in driver **`ids[]`** | +| **`rt_ofw_data` before probe** | **`rt_platform_ofw_request`** on provider | + +## See also + +- @ref page_device_ofw +- @ref page_device_ofw_platform +- @ref page_device_ofw_io +- @ref page_device_ofw_irq diff --git a/documentation/6.components/device-driver/ofw/ofw_boot.md b/documentation/6.components/device-driver/ofw/ofw_boot.md new file mode 100755 index 00000000000..eba4f610dae --- /dev/null +++ b/documentation/6.components/device-driver/ofw/ofw_boot.md @@ -0,0 +1,94 @@ +@page page_device_ofw_boot OFW boot and FDT + +# FDT bring-up (`fdt.c`) + +Raw **DTB** → **memblock** → unflattened **`rt_ofw_node`** tree. After unflatten, use **`rt_ofw_*`** (@ref page_device_ofw_base), not **`rt_fdt_*`**, unless still touching the blob. + +Header: **`drivers/ofw_fdt.h`**. Source: **`components/drivers/ofw/fdt.c`**. + +--- + +## Boot sequence + +``` + rt_fdt_prefetch(fdt) + → rt_fdt_scan_root() + → rt_fdt_scan_memory() /* memblock */ + → rt_fdt_scan_initrd() /* optional */ + → rt_fdt_unflatten() /* rt_ofw_node tree */ + → INIT_PLATFORM_EXPORT(platform_ofw_device_probe) +``` + +Global nodes (**`ofw_internal.h`**): **`ofw_node_root`**, **`ofw_node_cpus`**, **`ofw_node_chosen`**, **`ofw_node_aliases`**, **`ofw_node_reserved_memory`**. + +--- + +## API (blob phase) + +| API | Role | +| --- | --- | +| **`rt_fdt_prefetch`** | Store **`_fdt`**, validate header, **`rt_fdt_scan_root`** | +| **`rt_fdt_scan_root`** | Root **`#address-cells` / `#size-cells`** | +| **`rt_fdt_scan_memory`** | **`device_type = "memory"`** → **`rt_memblock_add_memory`** | +| **`rt_fdt_scan_initrd`** | Reserve initrd range | +| **`rt_fdt_unflatten`** | Build in-memory tree; **`ofw_alias_scan`**; phandle hash | +| **`rt_fdt_unflatten_single`** | Unflatten with explicit **`fdt`** pointer | +| **`rt_fdt_device_is_available`** | **`status`** on blob node (pre-unflatten) | + +### Chosen / bootargs + +| API | Role | +| --- | --- | +| **`rt_fdt_scan_chosen_stdout`** | **`stdout-path`** | +| **`rt_fdt_bootargs_select`** | Parse **`bootargs`** token | +| **`rt_ofw_bootargs_select`** | Same on unflattened tree | + +### Early console + +| API / macro | Role | +| --- | --- | +| **`RT_FDT_EARLYCON_EXPORT`** | Register **`rt_fdt_earlycon_id`** in linker section | +| **`rt_fdt_earlycon_output`** | Print before UART driver probe | +| **`rt_fdt_earlycon_kick`** | Hand off to real console | + +### libfdt helpers + +| API | Role | +| --- | --- | +| **`rt_fdt_read_number`**, **`rt_fdt_next_cell`** | Cell decoding | +| **`rt_fdt_translate_address`** | Address on raw blob | + +--- + +## Memory and reserved regions + +| Source | Effect | +| --- | --- | +| DTB **`/memreserve`** | **`rt_memblock_reserve_memory`** | +| **`/reserved-memory`** nodes | Validated **`reg`** (+ **`ranges`**) | +| **`rt_fdt_scan_initrd`** | Initrd carve-out | + +Unflatten marks system nodes with **`RT_OFW_F_SYSTEM | RT_OFW_F_READLY`** so they are not platform devices. + +--- + +## Builtin DTB + +**`RT_USING_BUILTIN_FDT`** embeds **`RT_BUILTIN_FDT_PATH`**. Build flow: @ref page_device_dtc. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **`rt_fdt_*` after unflatten** | Prefer **`rt_ofw_*`** for properties | +| Missing **`/cpus`** | **`RT_ASSERT`** in unflatten — BSP must supply | +| Memory scan skipped | Call **`scan_memory`** before allocator use | + +## See also + +- @ref page_device_ofw +- @ref page_device_ofw_platform +- @ref page_device_ofw_raw +- @ref page_device_dtc diff --git a/documentation/6.components/device-driver/ofw/ofw_io.md b/documentation/6.components/device-driver/ofw/ofw_io.md new file mode 100755 index 00000000000..cd7639f1675 --- /dev/null +++ b/documentation/6.components/device-driver/ofw/ofw_io.md @@ -0,0 +1,78 @@ +@page page_device_ofw_io OFW addressing and MMIO + +# Addressing and I/O (`io.c`) + +Decode **`reg`**, follow **`ranges` / `dma-ranges`**, and **`ioremap`**. Header: **`drivers/ofw_io.h`**. + +--- + +## Address cells + +| API | Role | +| --- | --- | +| **`rt_ofw_bus_addr_cells` / `rt_ofw_bus_size_cells`** | Walk parents for bus **`#address-cells`** | +| **`rt_ofw_io_addr_cells` / `rt_ofw_io_size_cells`** | Parent cells used to decode this node's **`reg`** | + +**`rt_ofw_get_address`** applies **`rt_ofw_translate_address`** so returned CPU physical addresses include parent **`ranges`**. + +--- + +## `reg` decoding + +| API | Role | +| --- | --- | +| **`rt_ofw_get_address_count`** | Number of **`reg`** tuples | +| **`rt_ofw_get_address(np, index, &addr, &size)`** | One tuple (translated base + size) | +| **`rt_ofw_get_address_by_name`** | Match **`reg-names`** | +| **`rt_ofw_get_address_array`** | Batch decode | + +Example in **`probe`**: + +```c +rt_uint64_t base, size; +if (rt_ofw_get_address(np, 0, &base, &size) != RT_EOK) + return -RT_ERROR; +pdev->iomem = rt_dm_dev_iomap(np, 0); /* or rt_ofw_iomap(np, 0) */ +``` + +--- + +## Translation + +| API | Role | +| --- | --- | +| **`rt_ofw_translate_address(np, range_type, addr)`** | Follow **`ranges`** or **`dma-ranges`** (**`range_type`** = **`"ranges"`** / **`"dma-ranges"`**, or **`RT_NULL`**) | +| **`rt_ofw_reverse_address`** | Child → parent bus address | +| **`rt_ofw_translate_dma2cpu`** | DMA bus → CPU physical (inline) | +| **`rt_ofw_translate_cpu2dma`** | CPU → DMA bus (inline) | + +**`io.c`** caches up to **`RT_USING_OFW_BUS_RANGES_NUMBER`** **`ranges`** tables for performance. + +--- + +## MMIO map + +| API | Role | +| --- | --- | +| **`rt_ofw_iomap(np, index)`** | **`ioremap`** for **`reg`** index | +| **`rt_ofw_iomap_by_name`** | Named **`reg-names`** segment | + +Prefer **`rt_dm_dev_iomap`** in DM drivers when **`struct rt_device`** is available (@ref page_device_dm). + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Wrong **`#address-cells`** | Always use **`rt_ofw_io_*`**, not constants | +| **`reg` without `ranges` on bus** | Translation may be identity — verify DTS | +| Multiple **`reg`** | Index 0 is not always the only MMIO bank | +| **`reg-names` mismatch** | **`rt_ofw_get_address_by_name`** returns **`-RT_EEMPTY`** | + +## See also + +- @ref page_device_ofw +- @ref page_device_ofw_base +- @ref page_device_ofw_boot +- @ref page_device_platform diff --git a/documentation/6.components/device-driver/ofw/ofw_irq.md b/documentation/6.components/device-driver/ofw/ofw_irq.md new file mode 100755 index 00000000000..cbcc526a9da --- /dev/null +++ b/documentation/6.components/device-driver/ofw/ofw_irq.md @@ -0,0 +1,92 @@ +@page page_device_ofw_irq OFW interrupts + +# Interrupts (`irq.c`) + +Parse **`interrupts`**, **`interrupt-parent`**, and **`interrupt-map`** into logical IRQ numbers for @ref page_device_pic / **`rt_pic_*`**. + +Header: **`drivers/ofw_irq.h`**. Source: **`components/drivers/ofw/irq.c`**. + +--- + +## Two wiring styles + +| Style | DTS | Typical use | +| --- | --- | --- | +| **Direct** | **`interrupts`** + optional **`interrupt-parent`** | SoC device on GIC | +| **Mapped** | **`interrupt-map`** + **`interrupt-map-mask`** on bridge | PCI, AMBA behind nexus | + +--- + +## API + +| API | Role | +| --- | --- | +| **`rt_ofw_irq_cells(np)`** | **`#interrupt-cells`** of interrupt controller | +| **`rt_ofw_find_irq_parent`** | Walk **`interrupt-parent`**; returns parent **`np`** and cell count | +| **`rt_ofw_parse_irq_cells(np, index, &args)`** | Parse **`interrupts`** entry → **`rt_ofw_cell_args`** | +| **`rt_ofw_parse_irq_map(np, &args)`** | Match **`interrupt-map`** row for this device | +| **`rt_ofw_map_irq(&args)`** | Final logical IRQ number via PIC layer | +| **`rt_ofw_get_irq_count`** | Number of interrupt specifiers | +| **`rt_ofw_get_irq(np, index)`** | Convenience: parse + map in one call | +| **`rt_ofw_get_irq_by_name`** | **`interrupt-names`** lookup | + +--- + +## Recommended probe pattern + +```c +struct rt_ofw_cell_args irq_args; +int irq; + +if (rt_ofw_get_prop(np, "interrupt-map", RT_NULL)) +{ + if (rt_ofw_parse_irq_map(np, &irq_args) != RT_EOK) + return -RT_ERROR; +} +else +{ + if (rt_ofw_parse_irq_cells(np, 0, &irq_args) != RT_EOK) + return -RT_ERROR; +} + +irq = rt_ofw_map_irq(&irq_args); +if (irq < 0) + return irq; + +rt_pic_irq_enable(irq); /* subsystem-specific */ +``` + +For multiple IRQs, loop **`rt_ofw_get_irq(np, i)`** or use **`interrupt-names`**. + +--- + +## `interrupt-map` (bridge) + +Each map entry contains (per spec comment in **`irq.c`**): + +1. Child unit address (**`#address-cells`** of bridge) +2. Child interrupt specifier (**`#interrupt-cells`** of bridge) +3. **`interrupt-parent`** phandle +4. Parent unit address +5. Parent interrupt specifier + +**`interrupt-map-mask`** masks bits before compare. PCI host bridges are the common case — see also @ref page_device_pci_ofw. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Missing **`interrupt-parent`** | Must inherit from bus or use **`interrupt-map`** | +| Using **`rt_ofw_get_irq`** on PCI endpoint | Often need **`parse_irq_map`** first | +| Cell count mismatch | Verify parent **`#interrupt-cells`** | +| IRQ vs trigger type | Specifier may include flags — pass full **`irq_args`** to PIC driver | + +## See also + +- @ref page_device_ofw +- @ref page_device_ofw_base +- @ref page_device_ofw_io +- @ref page_device_pic +- @ref page_device_pci_ofw diff --git a/documentation/6.components/device-driver/ofw/ofw_platform.md b/documentation/6.components/device-driver/ofw/ofw_platform.md new file mode 100755 index 00000000000..f00ec612cff --- /dev/null +++ b/documentation/6.components/device-driver/ofw/ofw_platform.md @@ -0,0 +1,79 @@ +@page page_device_ofw_platform OFW platform enumeration + +# Platform bus enumeration (`platform_ofw.c`) + +Walk the unflattened tree and register **`struct rt_platform_device`** for driver **`probe`**. Core OFW APIs: @ref page_device_ofw_base. + +Source: **`components/drivers/core/platform_ofw.c`**. Platform driver API: @ref page_device_platform. + +--- + +## Probe order + +**`INIT_PLATFORM_EXPORT(platform_ofw_device_probe)`**: + +1. **`/clocks`** subtree (providers before consumers) +2. **`/`** root (depth-first) +3. **`/firmware`** +4. **`/chosen/simple-framebuffer`** (if present) + +--- + +## Child walk + +For each **available** child of **`parent_np`**: + +| Skip when | Reason | +| --- | --- | +| **`np->dev` set** | Already instantiated | +| **`RT_OFW_F_SYSTEM` or `RT_OFW_F_READLY`** | Meta or bound node | +| No **`compatible`** and no real name | Anonymous container | + +| **`compatible` in `platform_ofw_ids`** | Action | +| --- | --- | +| **`simple-bus`**, **`simple-mfd`**, **`isa`**, **`arm,amba-bus`** | Recurse **`platform_ofw_device_probe_once(np)`** first | +| Leaf | **`alloc_ofw_platform_device`** → **`rt_platform_device_register`** | + +**`ofw_device_rename`**: **`reg`**-based name (`%lx.%.*s`) when decodable, else hierarchical path. + +--- + +## `rt_platform_ofw_request` + +```c +rt_err_t rt_platform_ofw_request(struct rt_ofw_node *np); +``` + +| State | Behavior | +| --- | --- | +| No **`np->dev`** | Create pdev, register (runs **`probe`**) | +| **`dev` without driver** | **`rt_bus_reload_driver_device`** | +| Probed | **`RT_EOK`** | + +Call from consumers after **`rt_ofw_parse_phandle_cells`** when provider **`rt_ofw_data`** is NULL. + +--- + +## Other APIs + +| API | Role | +| --- | --- | +| **`rt_platform_ofw_device_probe_child`** | Register single child under non-root parent | +| **`rt_platform_ofw_free`** | Clear **`RT_OFW_F_PLATFORM`**, **`rt_ofw_node_put`**, free pdev | + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Clock provider late | Ensure **`/clocks`** nodes probe first (built-in order) | +| No **`rt_platform_ofw_request`** | Provider never probes — phandle parse succeeds but **`rt_ofw_data`** NULL | +| Bus not in **`platform_ofw_ids`** | Children still register as leaf platform devices if **`compatible`** present | + +## See also + +- @ref page_device_ofw +- @ref page_device_ofw_boot +- @ref page_device_platform +- @ref page_device_bus diff --git a/documentation/6.components/device-driver/ofw/ofw_raw.md b/documentation/6.components/device-driver/ofw/ofw_raw.md new file mode 100755 index 00000000000..a2f5ab3b3c0 --- /dev/null +++ b/documentation/6.components/device-driver/ofw/ofw_raw.md @@ -0,0 +1,50 @@ +@page page_device_ofw_raw OFW raw FDT helpers + +# Raw FDT editing (`raw.c`) + +Optional **in-place** libfdt helpers for firmware, loaders, or tools that modify the blob **before** **`rt_fdt_unflatten`**. Runtime drivers normally use @ref page_device_ofw_base after unflatten. + +Header: **`drivers/ofw_raw.h`**. Source: **`components/drivers/ofw/raw.c`**. + +--- + +## When to use + +| Use raw API | Use `rt_ofw_*` | +| --- | --- | +| Patch DTB in RAM pre-boot | Driver **`probe`** on live tree | +| Add **`/chosen`** properties from bootloader | Read **`reg` / `interrupts`** | +| Install initrd nodes | Platform enumeration | + +--- + +## API highlights + +| API / macro | Role | +| --- | --- | +| **`fdt_add_subnode_possible`** | Add child if space in blob | +| **`fdt_add_mem_rsv_possible`** | Add memory reserve entry | +| **`fdt_setprop_cstring`** | Set string property | +| **`fdt_setprop_cells`**, **`fdt_appendprop_cells`** | U32 cell arrays (endian-safe) | +| **`fdt_setprop_uxx`** | 8/16/32/64-bit property | +| **`fdt_getprop_u8` … `fdt_getprop_s32`** | Typed read on blob offset | +| **`fdt_io_addr_cells`**, **`fdt_io_size_cells`** | Cells on raw node offset | +| **`fdt_install_initrd`** | Add **`linux,initrd-*`** under **`chosen`** | + +**`FDT_SIZE_MAX`**, **`FDT_PADDING_SIZE`** — growth limits when expanding tree. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Blob too small | Reserve padding at build time; check return codes | +| Edit after unflatten | Tree and blob may diverge — rebuild or only edit pre-unflatten | +| Endianness | Use **`fdt_setprop_cells`**, not raw **`fdt_setprop`** for integers | + +## See also + +- @ref page_device_ofw_boot +- @ref page_device_dtc +- libfdt: **`components/drivers/ofw/libfdt/`** diff --git a/documentation/6.components/device-driver/pci/bridge.md b/documentation/6.components/device-driver/pci/bridge.md new file mode 100755 index 00000000000..6edb106fcbd --- /dev/null +++ b/documentation/6.components/device-driver/pci/bridge.md @@ -0,0 +1,74 @@ +@page page_device_pci_bridge PCI bridge function driver + +# PCI bridge function driver (`host-bridge.c`) + +**Not** the SoC host controller (@ref page_device_pci_host). This is the **`RT_PCI_DRIVER_EXPORT`** driver for **PCI bridge functions** (root ports, P2P bridges, certain virtual bridges). + +Source: **`components/drivers/pci/host-bridge.c`**. + +--- + +## Matched devices + +```c +static const struct rt_pci_device_id host_bridge_pci_ids[] = { + { RT_PCI_DEVICE_ID(PCI_VENDOR_ID_REDHAT, 0x0008) }, + { RT_PCI_DEVICE_CLASS(PCIS_BRIDGE_PCI_NORMAL, ~0) }, + { RT_PCI_DEVICE_CLASS(PCIS_BRIDGE_PCI_SUBTRACTIVE, ~0) }, + { RT_PCI_DEVICE_CLASS(((PCIS_SYSTEM_RCEC << 8) | 0x00), ~0) }, + { /* sentinel */ } +}; +``` + +Vendor-specific bridge drivers should register **narrower** **`rt_pci_device_id`** entries **before** this generic match. + +--- + +## Probe / remove + +| Callback | Behavior | +| --- | --- | +| **`host_bridge_probe`** | **`rt_pci_set_master(pdev)`**; optional PM register | +| **`host_bridge_remove`** | **`rt_pci_clear_master`** | +| **`host_bridge_shutdown`** | PM unregister | + +No BAR programming here — windows configured during enumeration (@ref page_device_pci_probe). + +--- + +## Power management (`RT_USING_PM`) + +**`host_bridge_pm_suspend` / `resume`**: **`rt_pci_enum_device`** on bus, **`rt_pci_enable_wake`** per function (@ref page_device_pci_pme). + +| Sleep | PCI state | +| --- | --- | +| Idle | D3hot | +| Light / Deep | D1 | +| Standby | D2 | +| Shutdown | D3cold | + +--- + +## Component map + +| Layer | Location | Role | +| --- | --- | --- | +| Host RC | **`pci/host/`**, **`ecam.c`** | Enumeration | +| Bridge function | **`host-bridge.c`** | Bus master + PM on bridge devfn | +| OFW INTx | **`pci/ofw.c`** | **`interrupt-map`** | + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Expect BAR setup here | Done in core PCI | +| Generic class match | Override with specific driver if needed | + +## See also + +- @ref page_device_pci +- @ref page_device_pci_host +- @ref page_device_pci_pme +- @ref page_device_pci_ofw diff --git a/documentation/6.components/device-driver/pci/endpoint.md b/documentation/6.components/device-driver/pci/endpoint.md new file mode 100755 index 00000000000..0c6d81363b1 --- /dev/null +++ b/documentation/6.components/device-driver/pci/endpoint.md @@ -0,0 +1,77 @@ +@page page_device_pci_endpoint PCI endpoint (EP) mode + +# PCI endpoint framework (`endpoint/`) + +Header: **`drivers/pci_endpoint.h`**. Core: **`endpoint/endpoint.c`**, **`endpoint/mem.c`**. + +SoC as **PCIe device** under a remote host — not root complex. Kconfig: **`RT_PCI_ENDPOINT`**. + +--- + +## Core objects + +| Type | Role | +| --- | --- | +| **`struct rt_pci_ep`** | Controller: **`max_functions`**, **`ops`**, **`epf`** list | +| **`struct rt_pci_epf`** | Function image (driver) | +| **`struct rt_pci_ep_header`** | Emulated config header | +| **`struct rt_pci_ep_bar`** | BAR size/flags for programming | + +**`rt_pci_ep_register` / `unregister`** — global controller list under lock. + +--- + +## Controller ops + +| API | `ops` | Purpose | +| --- | --- | --- | +| **`rt_pci_ep_write_header`** | **`write_header`** | Config header for **`func_no`** | +| **`rt_pci_ep_set_bar` / `clear_bar`** | **`set_bar`** | BAR programming | +| **`rt_pci_ep_map_addr` / `unmap_addr`** | **`map_addr`** | CPU ↔ PCI map | +| **`rt_pci_ep_set_msi` / `get_msi`** | MSI doorbell | +| **`rt_pci_ep_set_msix` / `get_msix`** | MSI-X table | +| **`rt_pci_ep_raise_irq`** | **`raise_irq`** | INTx/MSI/MSI-X to host | +| **`rt_pci_ep_start` / `stop`** | Link visibility | + +**`func_no < max_functions`** required; missing **`ops`** → **`-RT_ENOSYS`**. + +--- + +## BAR validation + +**`rt_pci_ep_set_bar`** rejects invalid 64-bit pairs, >4 GiB on 32-bit BAR, bad I/O flags before calling hardware. + +--- + +## Memory helpers (`mem.c`) + +**`rt_pci_ep_mem_init`**, **`rt_pci_ep_mem_array_init`** — outbound/inbound windows for host mapping. + +**`host/dw/pcie-dw_ep.c`** — typical **`rt_pci_ep_ops`** implementation. + +--- + +## Host vs endpoint + +| Mode | APIs | Doc | +| --- | --- | --- | +| Root complex | **`rt_pci_host_bridge_*`**, **`rt_pci_scan_*`** | @ref page_device_pci_host | +| Endpoint | **`rt_pci_ep_*`** | this page | + +Do not call host enumeration on EP controllers. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **`func_no` out of range** | **`-RT_EINVAL`** | +| IRQ before link **`start`** | Host may ignore | +| 64-bit BAR layout | Follow PCI pair rules | + +## See also + +- @ref page_device_pci +- @ref page_device_pci_host +- `components/drivers/pci/host/dw/pcie-dw_ep.c` diff --git a/documentation/6.components/device-driver/pci/msi.md b/documentation/6.components/device-driver/pci/msi.md new file mode 100755 index 00000000000..594de0e2812 --- /dev/null +++ b/documentation/6.components/device-driver/pci/msi.md @@ -0,0 +1,80 @@ +@page page_device_pci_msi PCI MSI and MSI-X + +# PCI MSI / MSI-X (`msi/`) + +Headers: **`drivers/pci_msi.h`**, **`pci_regs.h`**. + +| File | Role | +| --- | --- | +| **`msi/msi.c`** | Capability parse, mask/unmask, message write | +| **`msi/irq.c`** | **`rt_pci_msi_setup_irqs`**, PIC allocation | +| **`msi/device.c`** | Device enable helpers | + +Disabled when **`RT_PCI_MSI=n`** — stubs in **`pci.h`**. + +--- + +## Prerequisites + +- **`pdev->msi_pic`**: **`struct rt_pic`** with **`irq_alloc_msi`**, **`irq_free_msi`**, **`irq_compose_msi_msg`** +- **`pdev->msi_cap`** / **`pdev->msix_cap`** from **`rt_pci_msi_init`** / **`rt_pci_msix_init`** in setup + +--- + +## Setup flow (`irq.c`) + +**`rt_pci_msi_setup_irqs(pdev, nvec, type)`**: + +### MSI + +1. Read **`multi_msg_use`** (log2 vectors) from first **`rt_pci_msi_desc`** +2. **`irq_alloc_msi`** until **`nvec`** contiguous IRQs obtained +3. Per vector: **`irq_compose_msi_msg`**, **`rt_pci_msi_write_msg`** + +### MSI-X + +- Per table entry: separate **`irq_alloc_msi`** +- Table must be mapped (BAR assigned before enable) + +**`rt_pci_msi_cleanup_irqs`** frees allocations. + +--- + +## Driver API + +| API | Use | +| --- | --- | +| **`rt_pci_msi_enable(pdev)`** | Single vector | +| **`rt_pci_msi_enable_range(pdev, min, max)`** | Power-of-two count | +| **`rt_pci_msi_enable_range_affinity(...)`** | With CPU masks | +| **`rt_pci_msix_enable(pdev, entries, count)`** | MSI-X table indices | +| **`rt_pci_msi_disable` / `rt_pci_msix_disable`** | Tear down | +| **`rt_pci_alloc_vector`** | Unified legacy/MSI/MSI-X picker (@ref page_device_pci_irq) | + +--- + +## Masking (`msi.c`) + +- **MSI**: config **`mask`** register when **`cap.is_masking`** +- **MSI-X**: per-entry vector control in table + +**`rt_pci_msi_mask_irq` / `rt_pci_msi_unmask_irq`** — PIC integration. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Non-contiguous MSI vectors | Allocator retries; check return | +| MSI-X before BAR map | Enable only after **`rt_pci_setup_device`** | +| INTx still firing | Enable MSI only after masking INTx in core | +| Large MSI-X table | Default affinity storage capped — review for 2k+ entries | + +## See also + +- @ref page_device_pci +- @ref page_device_pci_irq +- @ref page_device_pci_ofw +- @ref page_device_pic +- `components/drivers/pci/msi/` diff --git a/documentation/6.components/device-driver/pci/ofw.md b/documentation/6.components/device-driver/pci/ofw.md new file mode 100755 index 00000000000..288ab2281ef --- /dev/null +++ b/documentation/6.components/device-driver/pci/ofw.md @@ -0,0 +1,90 @@ +@page page_device_pci_ofw PCI and device tree + +# PCI Open Firmware integration + +Implementation: **`components/drivers/pci/ofw.c`**. Requires **`RT_USING_OFW`**. + +Host controllers probe as **platform** devices; this file connects enumerated **`rt_pci_device`** instances to DT **`ranges`** and **INTx** routing. + +--- + +## INTx: `pci_ofw_irq_parse` + +Resolves **legacy PCI INTx**: + +1. Device node has **`interrupts`** → **`rt_ofw_parse_irq_cells`** +2. Else read **`INTPIN`** from config; **`pin == 0`** → **`-RT_ENOSYS`** +3. Walk bus tree: + - Device-local **`interrupt-map`** if present + - Else P2P bridge or **host bridge** node → **`rt_ofw_parse_irq_map`** with key: + +```text +map_addr[0] = (bus->number << 16) | (devfn << 8) +map_addr[3] = swizzled INTx pin /* rt_pci_irq_intx */ +``` + +Missing **`interrupt-map`** on root → **`-RT_EEMPTY`** (logged). + +--- + +## `rt_pci_ofw_irq_parse_and_map` + +```c +int rt_pci_ofw_irq_parse_and_map(struct rt_pci_device *pdev, + rt_uint8_t slot, rt_uint8_t pin); +``` + +Calls **`pci_ofw_irq_parse`**, then **`rt_ofw_map_irq`**. Sets **`pdev->intx_pic`** from **`rt_ofw_data(irq_args.data)`**. + +Host bridge may wire **`host_bridge->irq_map`** to this helper during init. + +--- + +## `ranges` parsing + +**`pci_ofw_parse_ranges`** / **`rt_pci_ofw_parse_ranges`**: + +- Decodes PCI address **`space_code`** (config / I/O / 32-bit mem / 64-bit mem) +- Fills **`host_bridge->bus_regions`**: CPU phys, PCI bus addr, size, prefetch flags +- Used before **`rt_pci_region_setup`** + +**`rt_pci_ofw_host_bridge_init`**, **`rt_pci_ofw_bus_init`**, **`rt_pci_ofw_device_init`** — per-node OFW hooks during probe. + +--- + +## DT artifacts + +| Property | Use | +| --- | --- | +| Host **`compatible`** | Select host driver (@ref page_device_pci_host) | +| **`reg`** | ECAM MMIO | +| **`ranges` / `dma-ranges`** | MEM/IO windows | +| **`interrupt-map` / `interrupt-map-mask`** | INTx → PIC | +| Endpoint **`reg`** (optional) | Direct **`interrupts`** on slot | + +BAR values still come from config space after enumeration. + +--- + +## MSI/MSI-X + +MSI message address/data from **PIC** (**`pdev->msi_pic`**), not **`interrupts`** cells. Ensure PIC/MSI parent in DT matches host setup (@ref page_device_pci_msi). + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Missing root **`interrupt-map`** | Use MSI or fix DTS | +| Wrong swizzling | Use **`rt_pci_irq_intx`** for map key | +| Empty **`ranges`** | BAR assignment fails | + +## See also + +- @ref page_device_pci +- @ref page_device_pci_host +- @ref page_device_pci_irq +- @ref page_device_pci_msi +- @ref page_device_ofw_irq +- `components/drivers/pci/ofw.c` diff --git a/documentation/6.components/device-driver/pci/pci.md b/documentation/6.components/device-driver/pci/pci.md new file mode 100755 index 00000000000..3285545a5cc --- /dev/null +++ b/documentation/6.components/device-driver/pci/pci.md @@ -0,0 +1,95 @@ +@page page_device_pci PCI / PCIe + +# PCI and PCIe overview + +**PCI/PCIe** support in RT-Thread covers config-space access, bus enumeration, BAR assignment, INTx/MSI/MSI-X, and function drivers. Implementation layout matches **`components/drivers/pci/`**. + +| Source / topic | Documentation | +| --- | --- | +| **`probe.c`** — scan, setup, drivers | @ref page_device_pci_probe | +| **`access.c`** — config accessors | @ref page_device_pci_access | +| **`ecam.c`**, **`host/`** — root complex | @ref page_device_pci_host | +| **`irq.c`** — INTx assign/mask | @ref page_device_pci_irq | +| **`pme.c`** — power management | @ref page_device_pci_pme | +| **`ofw.c`** — DT ranges/IRQ | @ref page_device_pci_ofw | +| **`msi/`** | @ref page_device_pci_msi | +| **`host-bridge.c`** — bridge function driver | @ref page_device_pci_bridge | +| **`endpoint/`** — EP mode | @ref page_device_pci_endpoint | + +Header: **`drivers/pci.h`**. Requires **`RT_USING_DM`** and **`RT_USING_PIC`**. + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_PCI`** | Core PCI stack | +| **`RT_PCI_MSI`** | MSI / MSI-X (default y) | +| **`RT_PCI_ENDPOINT`** | Endpoint framework | +| **`RT_PCI_ECAM`** | ECAM config access | +| **`RT_PCI_SYS_64BIT`** | 64-bit resource alloc | +| **`RT_PCI_CACHE_LINE_SIZE`** | Cache line bytes written to config | +| **`RT_PCI_LOCKLESS`** | Skip global config spinlock | + +Host-specific options: **`pci/host/Kconfig`**, **`pci/host/dw/Kconfig`**. + +--- + +## Topology + +``` + CPU + Host Bridge (platform driver) + → rt_pci_host_bridge + → root_bus (bus 0) + → scan: endpoints + PCI bridges + → sub-buses + → rt_pci_driver probe per function +``` + +| Object | Role | +| --- | --- | +| **`struct rt_pci_host_bridge`** | Domain, **`bus_regions`**, **`rt_pci_ops`**, optional OFW node | +| **`struct rt_pci_bus`** | Bus number, device list, **`ops`** chain | +| **`struct rt_pci_device`** | BDF, BARs **`resource[]`**, caps, **`irq`**, bound driver | +| **`struct rt_pci_driver`** | **`ids[]`**, **`probe`/`remove`** | + +**BDF macros**: **`RT_PCI_DEVFN`**, **`RT_PCI_DEVID`**, **`RT_PCI_SLOT`**, **`RT_PCI_FUNC`**. + +--- + +## Bring-up order + +Full call chain (host → scan → BAR → function driver): **@ref page_device_pci_probe** §「Full initialization flow」. + +1. Platform **host** driver probes — @ref page_device_pci_host +2. **`rt_pci_host_bridge_init`** — **`ranges`**, **`bus-range`** — @ref page_device_pci_ofw +3. **`rt_pci_host_bridge_probe`** — DFS enumeration, **`rt_pci_setup_device`**, **`rt_pci_device_register`** +4. Bus **`pci_probe`** → **`rt_pci_assign_irq`** → function **`pdrv->probe`** — IRQ: @ref page_device_pci_irq, MSI: @ref page_device_pci_msi + +--- + +## Function driver checklist + +1. Wait until **`rt_pci_setup_device`** completed (BARs in **`pdev->resource`**). +2. **`void *base = rt_pci_iomap(pdev, bar)`** before MMIO. +3. **`rt_pci_set_master`** only when DMA is ready. +4. IRQ: **`rt_pci_alloc_vector`** or **`rt_pci_msi_enable`** — not raw INTx unless legacy. +5. **`remove`**: **`rt_pci_free_vector`**, **`rt_pci_clear_master`**, disable INTx. + +--- + +## When to use PCI + +| PCI | Platform / AMBA | +| --- | --- | +| Device behind root complex with config space | Fixed SoC block with only **`reg`/`interrupts`** | + +--- + +## See also + +- @ref page_device_pic +- @ref page_device_ofw_irq +- @ref page_device_nvme +- `components/drivers/include/drivers/pci.h` diff --git a/documentation/6.components/device-driver/pci/pci_access.md b/documentation/6.components/device-driver/pci/pci_access.md new file mode 100755 index 00000000000..3aebdff1d9d --- /dev/null +++ b/documentation/6.components/device-driver/pci/pci_access.md @@ -0,0 +1,86 @@ +@page page_device_pci_access PCI config space access + +# Config space (`access.c`, `rt_pci_ops`) + +All enumeration and drivers reach hardware through **`struct rt_pci_ops`** on each **`rt_pci_bus`**. + +Sources: **`components/drivers/pci/access.c`**, **`ecam.c`** (typical **`ops`** implementation). + +--- + +## `struct rt_pci_ops` + +| Callback | Role | +| --- | --- | +| **`map(bus, devfn, reg)`** | Optional MMIO pointer to config dword (ECAM) | +| **`read(bus, devfn, reg, width, &value)`** | Config read | +| **`write(bus, devfn, reg, width, value)`** | Config write | +| **`add` / `remove`** | Bus hook (optional) | + +Host bridge sets **`host_bridge->ops`**; child buses inherit or use **`child_ops`**. + +--- + +## Locked accessors (`access.c`) + +| API | Role | +| --- | --- | +| **`rt_pci_bus_read_config_u8/u16/u32`** | Bus-level read | +| **`rt_pci_bus_write_config_u8/u16/u32`** | Bus-level write | +| **`rt_pci_bus_read_config_uxx`** | Width 1/2/4; uses **`map`** if implemented | +| **`rt_pci_read_config_* (pdev, …)`** | Inline wrappers using **`pdev->devfn`** | + +Global **`rt_pci_lock`** serializes config unless **`RT_PCI_LOCKLESS=y`**. + +On read error, typed helpers may return **`~0`** — check **`rt_err_t`** from **`bus->ops->read`** when critical. + +--- + +## ECAM fast path + +**`pci_ecam_map`** computes: + +```text +win + (bus_offset | devfn_offset | reg_offset) +``` + +**`rt_pci_bus_read_config_uxx`** reads via **`HWREG8/16/32`** when **`map`** is non-NULL. + +See @ref page_device_pci_host for **`pci_generic_ecam_ops`** and DesignWare **`bus_shift`** variants. + +--- + +## Config space size + +**`pdev->cfg_size`** set in **`rt_pci_setup_device`**: + +- Standard 256 bytes, or extended 4 KiB if extended capability present. + +Use **`rt_pci_find_capability`** only within valid size. + +--- + +## MMIO from BAR (not config) + +| API | Role | +| --- | --- | +| **`rt_pci_iomap(pdev, bar_idx)`** | **`rt_ioremap(resource[bar].base, size)`** | + +BAR CPU addresses come from enumeration — do not confuse with ECAM config window. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Config during IRQ | Lock held by **`access.c`** — avoid long callbacks under lock | +| **`map` returns NULL** | Fall back to **`read`/`write`** ops only | +| Wrong **`devfn`** on secondary bus | Always use **`pdev->devfn`** on device's bus | +| Byte enables on IO | Use correct width API for register layout | + +## See also + +- @ref page_device_pci_probe +- @ref page_device_pci_host +- @ref page_device_pci diff --git a/documentation/6.components/device-driver/pci/pci_host.md b/documentation/6.components/device-driver/pci/pci_host.md new file mode 100755 index 00000000000..120be5444b6 --- /dev/null +++ b/documentation/6.components/device-driver/pci/pci_host.md @@ -0,0 +1,84 @@ +@page page_device_pci_host PCI host controllers + +# Host controllers (`ecam.c`, `host/`) + +SoC **root complex** drivers: map config space, parse DT **`ranges`**, call **`rt_pci_host_bridge_probe`**. Distinct from the PCI **bridge function** driver (@ref page_device_pci_bridge). + +--- + +## ECAM (`ecam.c`, `ecam.h`) + +| API | Role | +| --- | --- | +| **`pci_ecam_create(host_bridge, ops)`** | Attach **`pci_generic_ecam_ops`** (or variant) to host | +| **`pci_ecam_map(bus, devfn, where)`** | Config MMIO address | +| **`pci_generic_ecam_ops`** | Standard **`bus_shift`** ECAM | + +**`struct pci_ecam_config_window`**: **`win`** base, **`bus_range[2]`**, **`bus_shift`**, **`ops`**. + +### DesignWare / slot-0 quirk + +**`pci_dw_ecam_bus_ops`**: on root bus, **`devfn` slot > 0** returns **`NULL`** from **`map`** (only device 0 on bus 0) — used by **`snps,dw-pcie-ecam`**, Marvell, Socionext compatibles. + +--- + +## Generic platform host (`host/pci-host-generic.c`) + +**`RT_PLATFORM_DRIVER_EXPORT(gen_pci_driver)`** + +| `compatible` | ECAM ops | +| --- | --- | +| **`pci-host-ecam-generic`** | **`pci_generic_ecam_ops`** | +| **`pci-host-cam-generic`** | CAM layout, **`bus_shift = 16`** | +| **`snps,dw-pcie-ecam`**, **`marvell,armada8k-pcie-ecam`**, … | **`pci_dw_ecam_bus_ops`** | + +**`pci_host_common_probe`** (`host/pci-host-common.c`): + +1. **`rt_pci_host_bridge_alloc`** +2. **`rt_dm_dev_iomap(dev, 0)`** — ECAM **`reg`** +3. **`rt_pci_host_bridge_init`** — OFW ranges +4. **`pci_ecam_create`**, **`conf_win->win = base`** +5. **`rt_pci_host_bridge_probe`** + +--- + +## DesignWare host (`host/dw/`) + +| File | Role | +| --- | --- | +| **`pcie-dw_host.c`** | RC mode: DBI, link, **`rt_pci_host_bridge_probe`** | +| **`pcie-dw_ep.c`** | EP mode — @ref page_device_pci_endpoint | +| **`pcie-dw_platfrom.c`** | Platform glue | + +Enable via **`pci/host/dw/Kconfig`**. DT binding typically **`snps,dw-pcie`** plus ECAM child node. + +--- + +## Host bridge callbacks (IRQ without full OFW) + +**`struct rt_pci_host_bridge`** optional: + +| Field | Role | +| --- | --- | +| **`irq_slot(pdev, &pin)`** | ACPI/legacy slot for INTx | +| **`irq_map(pdev, slot, pin)`** | Return Linux IRQ number | + +If NULL, OFW path or no INTx (@ref page_device_pci_irq, @ref page_device_pci_ofw). + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| ECAM **`reg` not iomapped** | **`pci_host_common_probe`** fails with **`-RT_EIO`** | +| Empty **`ranges`** | **`rt_pci_ofw_parse_ranges`** fails — no MEM/IO for BARs | +| Wrong **`compatible`** | Pick ops matching hardware CAM vs ECAM | +| Confusing with **`host-bridge.c`** | That file is PCI **function** driver for bridge devices | + +## See also + +- @ref page_device_pci_probe +- @ref page_device_pci_ofw +- @ref page_device_pci_access +- @ref page_device_platform diff --git a/documentation/6.components/device-driver/pci/pci_irq.md b/documentation/6.components/device-driver/pci/pci_irq.md new file mode 100755 index 00000000000..8ed6b6d1a16 --- /dev/null +++ b/documentation/6.components/device-driver/pci/pci_irq.md @@ -0,0 +1,81 @@ +@page page_device_pci_irq PCI INTx interrupts + +# Legacy INTx (`irq.c` + core) + +**INTx** (legacy PCI pin A–D) assignment and masking. MSI/MSI-X: @ref page_device_pci_msi. DT routing: @ref page_device_pci_ofw. + +Sources: **`components/drivers/pci/irq.c`**, INTx helpers in **`pci.c`**. + +--- + +## Assignment (`rt_pci_assign_irq`) + +During setup, if host provides **`irq_map`**: + +1. Read **`PCIR_INTPIN`** from config +2. Optional **`irq_slot`** swizzle on bridges +3. **`irq_map(pdev, slot, pin)`** → **`pdev->irq`** +4. Write **`PCIR_INTLINE`** in config + +Without **`irq_map`**, logs debug and leaves IRQ 0 — use OFW or MSI. + +### OFW path + +**`rt_pci_ofw_irq_parse_and_map`** sets **`pdev->intx_pic`** and IRQ via **`rt_ofw_map_irq`** — preferred on DT platforms (@ref page_device_pci_ofw). + +--- + +## Swizzling + +| API | Role | +| --- | --- | +| **`rt_pci_irq_intx(pdev, pin)`** | Swizzle INTPIN across P2P bridges | +| **`rt_pci_irq_slot(pdev, &pin)`** | Slot number for **`interrupt-map`** key | + +Used when building **`interrupt-map`** address cells on root bridge. + +--- + +## Mask / unmask + +| API | Role | +| --- | --- | +| **`rt_pci_intx(pdev, enable)`** | Enable/disable INTx in command register | +| **`rt_pci_check_and_mask_intx`** | Mask if supported | +| **`rt_pci_check_and_unmask_intx`** | Unmask | +| **`rt_pci_irq_mask` / `rt_pci_irq_unmask`** | Device-level helpers | + +**`pdev->broken_intx_masking`**: set when hardware cannot mask INTx reliably. + +--- + +## Unified vector API (`pci.h`, `RT_PCI_MSI`) + +**`rt_pci_alloc_vector(pdev, min, max, flags, affinities)`**: + +| Flag | Meaning | +| --- | --- | +| **`RT_PCI_IRQ_F_LEGACY`** | Allow INTx | +| **`RT_PCI_IRQ_F_MSI`** | Allow MSI | +| **`RT_PCI_IRQ_F_MSIX`** | Allow MSI-X | +| **`RT_PCI_IRQ_F_AFFINITY`** | CPU affinity | + +Picks best available type; **`rt_pci_free_vector`** on teardown. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| MSI + INTx both active | Core often masks INTx when MSI enabled — use one path | +| **`interrupt-map` missing** | **`-RT_EEMPTY`** from OFW — enable MSI or fix DTS | +| Wrong pin after bridge | Use **`rt_pci_irq_intx`**, not raw config pin on child | +| **`irq == 0`** | May mean unassigned — do not register handler | + +## See also + +- @ref page_device_pci_msi +- @ref page_device_pci_ofw +- @ref page_device_ofw_irq +- @ref page_device_pic diff --git a/documentation/6.components/device-driver/pci/pci_pme.md b/documentation/6.components/device-driver/pci/pci_pme.md new file mode 100755 index 00000000000..173f31bae36 --- /dev/null +++ b/documentation/6.components/device-driver/pci/pci_pme.md @@ -0,0 +1,51 @@ +@page page_device_pci_pme PCI power management + +# PME and D-states (`pme.c`) + +PCI Power Management capability: D0/D1/D2/D3hot/D3cold and PME wake. + +Source: **`components/drivers/pci/pme.c`**. Init from **`rt_pci_setup_device`** via **`rt_pci_pme_init`**. + +--- + +## API + +| API | Role | +| --- | --- | +| **`rt_pci_pme_init(pdev)`** | Find **`PCIY_PMG`** cap, fill **`pme_support`** bitmask | +| **`rt_pci_pme_active(pdev, enable)`** | Enable/disable PME# generation | +| **`rt_pci_enable_wake(pdev, state, enable)`** | Program wake for **D1/D2/D3hot/D3cold** | +| **`rt_pci_pme_capable(pdev, state)`** | Test **`pme_support`** bit | + +**`enum rt_pci_power`**: **`RT_PCI_D0`** … **`RT_PCI_D3COLD`**, **`RT_PCI_PME_MAX`**. + +--- + +## Bridge driver integration + +**`host-bridge.c`** PM ops (**`RT_USING_PM`**) walk the bus with **`rt_pci_enum_device`** and call **`rt_pci_enable_wake`** per device on system sleep (@ref page_device_pci_bridge). + +| Sleep mode | PCI target | +| --- | --- | +| Idle | D3hot | +| Light / Deep | D1 | +| Standby | D2 | +| Shutdown | D3cold | + +Coordinate with **`power-domain`**, **`clk`**, **`reset`** from DM — PCI D-state alone may not gate SoC power. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| PME spec > 3 | **`rt_pci_pme_init`** logs error and aborts | +| Wake without **`pme_cap`** | **`rt_pci_pme_capable`** returns false | +| D3cold without platform support | May need PMIC/SCPI outside PCI layer | + +## See also + +- @ref page_device_pci_probe +- @ref page_device_pci_bridge +- @ref page_device_pci diff --git a/documentation/6.components/device-driver/pci/pci_probe.md b/documentation/6.components/device-driver/pci/pci_probe.md new file mode 100755 index 00000000000..665b0160876 --- /dev/null +++ b/documentation/6.components/device-driver/pci/pci_probe.md @@ -0,0 +1,256 @@ +@page page_device_pci_probe PCI enumeration and drivers + +# Enumeration and device setup (`probe.c`, `pci.c`) + +End-to-end **host bring-up → bus scan → BAR assignment → driver probe**. Sources: **`probe.c`** (scan/host), **`pci.c`** (regions, register, bus), **`ofw.c`** (DT windows). + +--- + +## Full initialization flow + +### Phase A — Host controller (platform driver) + +Typical ECAM host (**`pci_host_common_probe`** in `host/pci-host-common.c`): + +``` + platform probe (e.g. pci-host-ecam-generic) + | + v + rt_pci_host_bridge_alloc() + | + v + rt_dm_dev_iomap(dev, 0) /* ECAM config window */ + | + v + rt_pci_host_bridge_init() + +-- rt_pci_ofw_host_bridge_init() [RT_USING_OFW] + | bus-range, pci-domain + | rt_pci_ofw_parse_ranges() -> bus_regions[], dma_regions[] + | rt_pci_region_setup() /* allocator pools ready */ + | irq_slot = rt_pci_irq_slot + | irq_map = rt_pci_ofw_irq_parse_and_map + | + v + pci_ecam_create(host, ops) /* host_bridge->ops = ECAM read/write */ + conf_win->win = ECAM base + | + v + rt_pci_host_bridge_probe() /* starts enumeration — Phase B */ +``` + +DesignWare and other hosts follow the same pattern after link/DBI setup (@ref page_device_pci_host). + +### Phase B — Root bus and enumeration + +**`rt_pci_host_bridge_probe`** → **`rt_pci_scan_root_bus_bridge`**: + +``` + rt_pci_host_bridge_register(host) + | pci_alloc_bus(NULL) -> root_bus + | root_bus->number = bus_range[0] + | root_bus->ops = host_bridge->ops + | + v + rt_pci_scan_child_bus(root_bus) + | + v + rt_pci_scan_child_buses(bus, buses=0) + | + +-- for slot 0..30 (step 8 devfn): + | rt_pci_scan_slot(bus, devfn) + | for func 0..7 (ARI / multifunction): + | rt_pci_scan_single_device() /* Phase C */ + | + +-- for each bridge on bus (rt_pci_foreach_bridge): + pci_scan_bridge_extend() + pcie_fixup_link() [if PCIe cap] + pci_alloc_bus(parent) -> child bus + pci_child_bus_init(bus_no, bridge pdev) + program PCIR_PRIBUS_1 (primary/secondary/subordinate) + rt_pci_scan_child_buses(child, ...) /* recurse */ + update subordinate bus number +``` + +**Depth-first**: all devices on bus N are scanned, then each bridge opens bus N+1 and recurses. + +### Phase C — Single function (`rt_pci_scan_single_device`) + +``` + read config PCIR_VENDOR / PCIR_DEVICE + | (0xffff / 0x0000 -> empty slot, skip) + v + rt_pci_alloc_device(bus) /* link into bus->devices_nodes */ + | + v + rt_pci_setup_device(pdev) /* Phase D — must succeed */ + | + v + pci_procfs_attach(pdev) [optional debug] + | + v + rt_pci_device_register(pdev) + -> rt_bus_add_device(pci_bus, &pdev->parent) + -> pci bus match + pci_probe() /* Phase E */ +``` + +If **`rt_pci_setup_device`** fails, the **`rt_pci_device`** is freed and the slot is skipped. + +### Phase D — Per-device setup (`rt_pci_setup_device`) + +Order inside **`probe.c`** / **`pci.c`**: + +| Step | Action | +| --- | --- | +| 1 | **`rt_pci_ofw_device_init`** — bind OFW node if any | +| 2 | Read **class**, **hdr_type**, clear **STATUS** errors | +| 3 | **`pci_read_irq`** — read **INTPIN** / **INTLINE** from config only (not mapped yet) | +| 4 | **`rt_pci_device_alloc_resource`** — size BARs, **`rt_pci_region_alloc`**, write BAR registers, enable bridge windows | +| 5 | **`pci_init_capabilities`** — **`rt_pci_pme_init`**, **`rt_pci_msi_init`/`msix_init`** (disabled), PCIe cap, **cfg_size**, ARI | + +Device name set to **`domain:bus:slot.func`**. + +### Phase E — Bus driver probe (`pci.c`) + +When **`rt_pci_device_register`** adds the device to the PCI bus: + +``` + pci_match(pdrv, pdev) /* vendor/device or class table */ + | + v + pci_probe(dev) + +-- rt_pci_assign_irq(pdev) /* host irq_map or OFW INTx */ + +-- rt_pci_enable_wake(pdev, D0) + +-- pdrv->probe(pdev) /* NVMe, NIC, ... */ +``` + +**Important**: function driver **`probe`** runs **after** BARs are assigned and **after** **`rt_pci_assign_irq`**. Use **`rt_pci_iomap`** / **`rt_pci_alloc_vector`** here, not during scan. + +### IRQ timing summary + +| When | What | +| --- | --- | +| **`rt_pci_setup_device`** | **`pci_read_irq`** — raw config pin/line | +| **`pci_probe` (bus)** | **`rt_pci_assign_irq`** — **`irq_map`** / OFW → **`pdev->irq`**, **`intx_pic`** | +| Function **`pdrv->probe`** | MSI/MSI-X enable, **`rt_pic_*`** register | + +--- + +## Flow diagram + +```mermaid +flowchart TD + A[Platform host probe] --> B[host_bridge_alloc + iomap ECAM] + B --> C[host_bridge_init / OFW ranges] + C --> D[pci_ecam_create] + D --> E[host_bridge_probe] + E --> F[host_bridge_register root bus] + F --> G[scan_child_bus DFS] + G --> H[scan_slot / scan_single_device] + H --> I[setup_device BAR + caps] + I --> J[device_register] + J --> K[bus pci_probe assign_irq] + K --> L[function driver probe] + G --> M[scan bridge -> child bus] + M --> G +``` + +--- + +## Host bridge lifecycle (API) + +| API | Role | +| --- | --- | +| **`rt_pci_host_bridge_alloc(priv_size)`** | Allocate host bridge + private tail | +| **`rt_pci_host_bridge_init`** | OFW: **`rt_pci_ofw_host_bridge_init`** | +| **`rt_pci_host_bridge_probe`** | **`rt_pci_scan_root_bus_bridge`** | +| **`rt_pci_host_bridge_register`** | Create root **`rt_pci_bus`** | +| **`rt_pci_host_bridge_remove`** | Enum remove all devices | + +--- + +## Scan API + +| API | Role | +| --- | --- | +| **`rt_pci_scan_child_bus(bus)`** | Entry: scan one bus + bridges | +| **`rt_pci_scan_child_buses`** | Slots 0–30, then bridge extend | +| **`rt_pci_scan_slot`** | All functions on one devfn (MF/ARI) | +| **`rt_pci_scan_single_device`** | One function if vendor valid | +| **`rt_pci_scan_bridge`** | Bridge secondary bus setup | +| **`rt_pci_alloc_device`** | Allocate **`rt_pci_device`**, link to bus | + +Bridges: **`pdev->subbus`** after **`pci_scan_bridge_extend`**. Endpoints: **`subbus == NULL`**. + +--- + +## `rt_pci_setup_device` detail + +See **Phase D** above. Invalid header/class → error, device discarded. + +--- + +## Resources (`pci.c`) + +| API | Role | +| --- | --- | +| **`rt_pci_region_setup`** | After OFW **`ranges`**: log pools, **`bus_start` ≥ 0x1000** | +| **`rt_pci_region_alloc`** | Carve BAR/bus address from **`bus_regions`** | +| **`rt_pci_device_alloc_resource`** | Probe BAR sizes, assign, write config | +| **`rt_pci_find_bar`** | Lookup assigned BAR by flags/index | + +--- + +## Driver model + +```c +static const struct rt_pci_device_id my_ids[] = { + { RT_PCI_DEVICE_ID(0x1234, 0xabcd) }, + { RT_PCI_DEVICE_CLASS(0x01080200, 0xffffff00) }, + { /* sentinel */ }, +}; + +static struct rt_pci_driver my_driver = { + .name = "my_pci", + .ids = my_ids, + .probe = my_probe, + .remove = my_remove, +}; +RT_PCI_DRIVER_EXPORT(my_driver); +``` + +| API | Role | +| --- | --- | +| **`rt_pci_match_ids`** | Match during **`pci_match`** | +| **`rt_pci_driver_register`** | Register on PCI bus (export macro at init) | +| **`rt_pci_device_register`** | After scan — triggers bus **`probe`** | +| **`rt_pci_enum_device`** | Walk tree callback | + +--- + +## Helpers + +| API | Role | +| --- | --- | +| **`rt_pci_domain`**, **`rt_pci_find_host_bridge`** | Domain / host lookup | +| **`rt_pci_is_bridge`**, **`rt_pci_is_pcie`** | Type checks | +| **`rt_pci_find_capability`** | Capability walk | + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| MMIO in scan before **`device_register`** | Wait for function **`probe`** | +| BARs empty | Fix DT **`ranges`** + **`rt_pci_region_setup`** | +| **`irq == 0`** after probe | Check **`interrupt-map`** / use MSI | +| Bridge bus exhausted | **`bus-range`** too small in DTS | +| Scan order vs driver | **`RT_PCI_DRIVER_EXPORT`** drivers already registered before host probe | + +## See also + +- @ref page_device_pci +- @ref page_device_pci_host +- @ref page_device_pci_access +- @ref page_device_pci_ofw +- @ref page_device_pci_irq diff --git a/documentation/6.components/device-driver/phye/phye.md b/documentation/6.components/device-driver/phye/phye.md new file mode 100755 index 00000000000..4ef310c275a --- /dev/null +++ b/documentation/6.components/device-driver/phye/phye.md @@ -0,0 +1,89 @@ +@page page_device_phye Generic PHY (Phye) + +# PHY abstraction (phye) overview + +**Phye** is the DM framework for **external PHY** blocks: SerDes/USB/PCIe/SATA/MIPI PHY that sit beside the MAC/host controller and are shared via device tree **`phys`** phandles. + +| Topic | Page | +| --- | --- | +| Core API, refcount, modes | @ref page_device_phye_core | +| Device tree **`phys` / `#phy-cells`** | @ref page_device_phye_ofw | +| Implement a PHY provider | @ref page_device_phye_provider | +| Host / MAC consumer pattern | @ref page_device_phye_consumer | +| Built-in **`usb-nop-xceiv`** | @ref page_device_phye_generic_usb | + +Header: **`drivers/phye.h`**. Core: **`components/drivers/phye/phye.c`**. + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_PHYE`** | Framework (**`RT_USING_DM`**) | +| **`RT_PHYE_GENERIC_USB`** | **`usb-nop-xceiv`** platform driver | +| **`SOC_DM_PHYE_DIR`** | BSP adds SoC-specific PHY drivers via **`osource`** | + +Use phye only when the PHY is **not** fully embedded in the controller IP. + +--- + +## Architecture + +``` + PHY provider (platform driver) + rt_phye_register() → rt_ofw_data(phy_node) = rt_phye * + ^ + | phys + #phy-cells [+ phy-names] + | + Consumer (USB/PCIe/SATA/AHCI/… host driver) + rt_phye_get_by_index/name → init → power_on → set_mode + … link training / MAC start … + power_off → exit → rt_phye_put +``` + +Providers implement **`struct rt_phye_ops`**; the core handles **reference counting** on **`init`** / **`power_on`**. + +--- + +## When to use phye + +| Use **`rt_phye_*`** | Skip phye | +| --- | --- | +| DT **`phys`** on host; PHY has own **`compatible`** node | PHY registers only touched inside one driver | +| Same PHY IP shared (mux) or tuning table reused | No OFW and fixed **`board.c`** init | + +--- + +## Power-up order (typical) + +Follow SoC TRM; common sequence: + +1. Regulator / power domain +2. Reference clock (**`rt_clk_prepare_enable`**) +3. Deassert reset +4. **`rt_phye_init`** → **`rt_phye_power_on`** +5. **`rt_phye_set_mode`** (lane rate, USB HS/SS, PCIe RC/EP, …) +6. Host link training / MAC enable + +Teardown in reverse; see @ref page_device_phye_consumer. + +--- + +## In-tree consumers (examples) + +| Consumer | PHY lookup | Source | +| --- | --- | --- | +| DesignWare AHCI | **`rt_phye_get_by_name(dev, "sata-phy")`** | **`ata/ahci-dw.c`** | +| USB host/device | **`phys`** → generic or SoC PHY | BSP + @ref page_device_phye_generic_usb | + +PCIe host drivers may call **`RT_PHYE_MODE_PCIE`** before training when BSP supplies a PHY driver under **`SOC_DM_PHYE_DIR`**. + +--- + +## See also + +- @ref page_device_ofw_base — phandle pattern +- @ref page_device_pci_host +- @ref page_device_clk +- `components/drivers/include/drivers/phye.h` diff --git a/documentation/6.components/device-driver/phye/phye_consumer.md b/documentation/6.components/device-driver/phye/phye_consumer.md new file mode 100755 index 00000000000..1bba169ca8f --- /dev/null +++ b/documentation/6.components/device-driver/phye/phye_consumer.md @@ -0,0 +1,122 @@ +@page page_device_phye_consumer Phye consumer integration + +# Host / MAC consumer pattern + +How link-layer drivers acquire and sequence PHY bring-up **before** training or DMA. + +--- + +## Recommended sequence + +```c +struct rt_phye *phy; + +phy = rt_phye_get_by_name(dev, "sata-phy"); +if (rt_is_err_or_null(phy)) + return rt_ptr_err(phy) ? rt_ptr_err(phy) : -RT_ERROR; + +err = rt_phye_init(phy); +if (err) + goto put; + +err = rt_phye_power_on(phy); +if (err) + goto exit; + +err = rt_phye_set_mode_simple(phy, RT_PHYE_MODE_SATA); +if (err) + goto power_off; + +/* optional: rt_phye_reset(phy) */ + +err = host_enable_and_train(); /* MAC-specific */ +if (err) + goto power_off; + +/* store phy in driver private */ + +power_off: + rt_phye_power_off(phy); +exit: + rt_phye_exit(phy); +put: + rt_phye_put(phy); +return err; +``` + +On success, keep PHY powered until **`remove`** — do not **`put`** until teardown. + +--- + +## DesignWare AHCI example (`ata/ahci-dw.c`) + +| Step | Call | +| --- | --- | +| Get | **`rt_phye_get_by_name(dev, "sata-phy")`** | +| Clocks | **`rt_clk_array_prepare_enable`** (before PHY) | +| PHY | **`rt_phye_init`** → **`rt_phye_power_on`** | +| Host | **`rt_ahci_host_register`** | +| Fail path | **`power_off`** → **`exit`** → disable clocks → **`rt_phye_put`** | +| Remove | **`exit`** + **`power_off`** (note order in source) | + +DTS: host node needs **`phys`** / **`phy-names = "sata-phy"`** pointing at SATA PHY provider. + +--- + +## PCIe host (typical) + +Before **`rt_pci_host_bridge_probe`** or link training: + +```c +phy = rt_phye_get_by_index(dev, 0); +rt_phye_init(phy); +rt_phye_power_on(phy); +rt_phye_set_mode(phy, RT_PHYE_MODE_PCIE, RT_PHYE_MODE_PCIE_RC); +/* then DWC / generic PCIe host probe */ +``` + +Exact ordering vs **`clk`/`reset`** is SoC-specific — @ref page_device_pci_host. + +--- + +## USB + +Often uses **`usb-nop-xceiv`** (@ref page_device_phye_generic_usb) or SoC USB PHY: + +```c +phy = rt_phye_get_by_index(dev, 0); +rt_phye_power_on(phy); +rt_phye_set_mode_simple(phy, RT_PHYE_MODE_USB_HOST_SS); +``` + +Generic USB driver may only implement **`power_on`/`power_off`/`reset`** — mode set optional. + +--- + +## Error handling + +| Return | Meaning | +| --- | --- | +| **`rt_err_ptr(-RT_EINVAL)`** | Bad args | +| **`rt_err_ptr` from `ofw_parse`** | DT cells wrong | +| **`NULL`** | No **`phys`** / provider missing | + +Always **`rt_phye_put`** for every successful **`get`**. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Train before **`power_on`** | Link fails — follow TRM order | +| **`set_mode` while link up** | Down link first | +| Leak **`put`** | Pair with **`get`** on all paths | +| Double **`power_off`** in remove | Match **`probe`** success path | + +## See also + +- @ref page_device_phye +- @ref page_device_phye_ofw +- @ref page_device_pci_probe +- @ref page_device_ata diff --git a/documentation/6.components/device-driver/phye/phye_core.md b/documentation/6.components/device-driver/phye/phye_core.md new file mode 100755 index 00000000000..13b5477bfe8 --- /dev/null +++ b/documentation/6.components/device-driver/phye/phye_core.md @@ -0,0 +1,91 @@ +@page page_device_phye_core Phye core API + +# Core framework (`phye.c`) + +Registration, reference-counted **`init`/`power_on`**, and **`set_mode`**. Source: **`components/drivers/phye/phye.c`**. + +--- + +## Objects + +| Type | Role | +| --- | --- | +| **`struct rt_phye`** | **`dev`**, **`ops`**, **`init_count`**, **`power_count`**, **`lock`** | +| **`struct rt_phye_ops`** | Provider callbacks (all optional except as needed) | + +**`rt_phye_register`** requires **`phye->dev`** and **`phye->ops`**, calls **`rt_dm_dev_bind_fwdata(dev, NULL, phye)`** so **`rt_ofw_data(phy_np)`** resolves the provider. + +**`rt_phye_unregister`** fails with **`-RT_EBUSY`** if **`dev->ref_count != 0`**. + +--- + +## Reference counting + +| API | Behavior | +| --- | --- | +| **`rt_phye_init` / `rt_phye_exit`** | **`ops->init`** on first **`init`**; **`ops->exit`** when **`init_count`** returns to 0 | +| **`rt_phye_power_on` / `rt_phye_power_off`** | Same pattern for **`power_on`/`power_off`** | +| **`rt_phye_reset`** | No refcount — call when driver needs pulse | +| **`rt_phye_set_mode`** | No refcount — configure mode each time (serialized by lock) | + +**`NULL` phye** pointer: APIs return **`RT_EOK`** (no-op) — useful for optional PHY in consumer code. + +--- + +## `rt_phye_ops` + +| Callback | Typical use | +| --- | --- | +| **`init`** | One-time calibration, load OTP/NVMEM | +| **`exit`** | Reverse **`init`** | +| **`reset`** | GPIO or soft reset pulse | +| **`power_on`** | Enable regulators/clocks, exit reset | +| **`power_off`** | Gate power/clocks | +| **`set_mode`** | USB speed, PCIe RC/EP, SATA, MIPI, … | +| **`ofw_parse`** | Decode **`#phy-cells`** after phandle resolve | + +--- + +## Modes (`enum rt_phye_mode`) + +| Range | Examples | +| --- | --- | +| **`< RT_PHYE_MODE_MAX`** | **`RT_PHYE_MODE_USB_HOST_HS`**, **`RT_PHYE_MODE_PCIE`**, **`RT_PHYE_MODE_SATA`**, **`RT_PHYE_MODE_MIPI_DPHY`**, … | +| **`>= RT_PHYE_MODE_MAX`** (submode only) | **`RT_PHYE_MODE_PCIE_RC`**, **`RT_PHYE_MODE_PCIE_EP`**, **`RT_PHYE_MODE_PCIE_BIFURCATION`** | + +**PCIe example** (base mode + submode): + +```c +rt_phye_set_mode(phy, RT_PHYE_MODE_PCIE, RT_PHYE_MODE_PCIE_RC); +``` + +**Simple mode** (no submode): + +```c +rt_phye_set_mode_simple(phy, RT_PHYE_MODE_SATA); +/* same as set_mode(..., RT_PHYE_MODE_INVALID) */ +``` + +**`rt_phye_set_mode`** returns **`-RT_EINVAL`** if **`mode >= RT_PHYE_MODE_MAX`** or submode is invalid. + +--- + +## Acquire / release (consumer) + +| API | Role | +| --- | --- | +| **`rt_phye_get_by_index(dev, index)`** | Parse **`phys`** on **`dev->ofw_node`** | +| **`rt_phye_get_by_name(dev, id)`** | **`phy-names`** → index | +| **`rt_phye_put(phye)`** | Decrement **`dev->ref_count`** | + +Returns **`rt_err_ptr(...)`** on failure — use **`rt_is_err_or_null(phye)`** before use. + +**`get`** bumps **`ref_count`** under **`phye->lock`**. + +--- + +## See also + +- @ref page_device_phye_ofw +- @ref page_device_phye_provider +- @ref page_device_phye_consumer diff --git a/documentation/6.components/device-driver/phye/phye_generic_usb.md b/documentation/6.components/device-driver/phye/phye_generic_usb.md new file mode 100755 index 00000000000..54bc5ae7d89 --- /dev/null +++ b/documentation/6.components/device-driver/phye/phye_generic_usb.md @@ -0,0 +1,82 @@ +@page page_device_phye_generic_usb Generic USB PHY + +# Generic USB PHY (`phye-generic-usb.c`) + +Minimal PHY provider for **`usb-nop-xceiv`** — reset, clock, and regulators without complex **`set_mode`**. + +Kconfig: **`RT_PHYE_GENERIC_USB`** (requires **`RT_USING_PHYE`**). + +--- + +## Device tree + +```dts +usb_phy: usb-phy { + compatible = "usb-nop-xceiv"; + clocks = <&usb_clk>; + clock-frequency = <24000000>; + vcc-supply = <&usb_vcc>; + vbus-supply = <&usb_vbus>; /* optional */ + reset-gpios = <&gpio PHY_RST GPIO_ACTIVE_LOW>; +}; +``` + +Consumer (e.g. DWC3): + +```dts +usb3: usb@... { + phys = <&usb_phy>; + phy-names = "usb"; +}; +``` + +--- + +## Operations + +| `rt_phye_ops` | Behavior | +| --- | --- | +| **`power_on`** | **`vcc`** enable → **`clk_prepare_enable`** → **`reset`** pulse (15 ms assert, 20 ms deassert) | +| **`power_off`** | **`vcc`** disable, clock off | +| **`reset`** | GPIO reset only | + +No **`init`**, **`exit`**, or **`set_mode`** — USB mode assumed fixed after power-on. + +--- + +## Probe flow + +1. **`reset`** GPIO from **`reset-gpios`** +2. **`main_clk`** via **`rt_clk_get_by_name`**; optional **`clock-frequency`** +3. **`vcc`**, **`vbus`** regulators — **`vbus`** enabled at probe if present +4. **`rt_phye_register`** + +**`INIT_PLATFORM_EXPORT(generic_usb_phy_drv_register)`** — registers platform driver at init. + +--- + +## Consumer usage + +```c +phy = rt_phye_get_by_name(dev, "usb"); +rt_phye_power_on(phy); /* often sufficient for nop-xceiv */ +/* start USB controller */ +``` + +Use **`rt_phye_init`** only if provider adds **`init`** later. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Wrong clock rate | Set **`clock-frequency`** in DTS | +| **`vbus` always on** | Probe enables **`vbus`** — OK for host; device role may differ | +| Expect **`set_mode` for SS** | Add SoC PHY driver or extend ops | + +## See also + +- @ref page_device_phye_provider +- @ref page_device_phye_consumer +- @ref page_device_phye diff --git a/documentation/6.components/device-driver/phye/phye_ofw.md b/documentation/6.components/device-driver/phye/phye_ofw.md new file mode 100755 index 00000000000..8309460f875 --- /dev/null +++ b/documentation/6.components/device-driver/phye/phye_ofw.md @@ -0,0 +1,75 @@ +@page page_device_phye_ofw Phye and device tree + +# Device tree binding (`phye.c`) + +Consumers reference PHY providers with standard graph properties. Requires **`RT_USING_OFW`**. + +--- + +## Properties (consumer node) + +| Property | Role | +| --- | --- | +| **`phys`** | Phandle list to PHY provider node(s) | +| **`#phy-cells`** | On **provider** — specifier length per entry | +| **`phy-names`** | Optional string ID per entry (for **`rt_phye_get_by_name`**) | + +Example (SATA host): + +```dts +ahci: sata@... { + phys = <&sata_phy 0>; + phy-names = "sata-phy"; +}; +``` + +--- + +## Resolution flow (`ofw_phye_get_by_index`) + +``` + rt_ofw_parse_phandle_cells(np, "phys", "#phy-cells", index, &args) + | + | args.data = provider rt_ofw_node * + v + rt_platform_ofw_request(provider_np) /* probe provider if needed */ + | + v + phye = rt_ofw_data(provider_np) + | + v + phye->ops->ofw_parse(phye, &args) /* optional */ +``` + +If **`ofw_parse`** fails, **`get_by_index`** returns **`rt_err_ptr(err)`**. + +Provider platform driver must **`rt_phye_register`** in **`probe`** after **`rt_dm_dev_bind_fwdata`**. + +--- + +## Provider node + +| Element | Role | +| --- | --- | +| **`compatible`** | Selects PHY driver (SoC or generic) | +| **`#phy-cells`** | Usually `0` or mode/lane index | +| **`clocks` / `resets` / regulators** | Handled in provider **`probe`**, not by core | + +Cell semantics are **driver-specific** — document in BSP binding and implement in **`ofw_parse`**. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Provider not probed | **`rt_platform_ofw_request`** before **`rt_ofw_data`** (core does this) | +| **`ofw_parse` omitted** | **`#phy-cells` ignored** — wrong mode if cells non-zero | +| Index / name mismatch | **`phy-names`** must align with **`phys`** order | +| Missing **`phys`** | **`get_by_index`** returns NULL — fail host **`probe`** | + +## See also + +- @ref page_device_phye_core +- @ref page_device_ofw_base +- @ref page_device_phye_provider diff --git a/documentation/6.components/device-driver/phye/phye_provider.md b/documentation/6.components/device-driver/phye/phye_provider.md new file mode 100755 index 00000000000..d8c80afaf01 --- /dev/null +++ b/documentation/6.components/device-driver/phye/phye_provider.md @@ -0,0 +1,100 @@ +@page page_device_phye_provider Phye provider driver + +# Implementing a PHY provider + +Platform (or other) driver that owns PHY registers and exports **`struct rt_phye`**. + +--- + +## Minimal template + +```c +struct my_phy { + struct rt_phye phye; + void __iomem *regs; + struct rt_clk *clk; + /* mode, calibration, ... */ +}; + +static rt_err_t my_phy_power_on(struct rt_phye *phye) +{ + struct my_phy *p = rt_container_of(phye, struct my_phy, phye); + return rt_clk_prepare_enable(p->clk); +} + +static rt_err_t my_phy_set_mode(struct rt_phye *phye, + enum rt_phye_mode mode, int submode) +{ + /* program PHY for mode / submode */ + return RT_EOK; +} + +static const struct rt_phye_ops my_phy_ops = { + .power_on = my_phy_power_on, + .power_off = my_phy_power_off, + .set_mode = my_phy_set_mode, + .ofw_parse = my_phy_ofw_parse, +}; + +static rt_err_t my_phy_probe(struct rt_platform_device *pdev) +{ + struct my_phy *p = rt_calloc(1, sizeof(*p)); + p->phye.dev = &pdev->parent; + p->phye.ops = &my_phy_ops; + return rt_phye_register(&p->phye); +} +``` + +--- + +## Probe checklist + +1. **`rt_dm_dev_iomap`** / clocks / resets / regulators per TRM +2. Fill **`rt_phye_ops`** — only implement needed ops +3. **`phye->dev = &pdev->parent`** +4. **`rt_phye_register(&phye)`** — binds **`rt_ofw_data`** for consumers +5. Do **not** call **`set_mode`** for a specific host here — consumers select mode + +--- + +## `ofw_parse` + +```c +static rt_err_t my_phy_ofw_parse(struct rt_phye *phye, + struct rt_ofw_cell_args *args) +{ + /* args->args[0..args_count-1] from #phy-cells */ + /* store in container struct for set_mode */ + return RT_EOK; +} +``` + +Return error if cells are invalid — consumer **`get`** fails and host **`probe`** aborts. + +--- + +## Unregister + +**`remove`**: **`rt_phye_unregister`** then free resources. Fails if consumers still hold references (**`-RT_EBUSY`**). + +--- + +## BSP placement + +SoC-specific drivers often live under **`SOC_DM_PHYE_DIR`** (Kconfig **`osource`**). Enable in menuconfig alongside **`RT_USING_PHYE`**. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Forgot **`rt_phye_register`** | **`rt_ofw_data`** NULL — consumer sees missing PHY | +| Mode in **`probe`** | Host may need different mode — use **`set_mode`** from consumer | +| Shared PHY | Refcount in core helps; exclusive mux still needs driver policy | + +## See also + +- @ref page_device_phye_core +- @ref page_device_phye_ofw +- @ref page_device_phye_generic_usb diff --git a/documentation/6.components/device-driver/pic/pic.md b/documentation/6.components/device-driver/pic/pic.md new file mode 100755 index 00000000000..1de3a9afe11 --- /dev/null +++ b/documentation/6.components/device-driver/pic/pic.md @@ -0,0 +1,92 @@ +@page page_device_pic PIC overview + +# Programmable Interrupt Controller (PIC) + +The **PIC** layer maps device-tree interrupts to **global virtual IRQ numbers**, dispatches ISRs, supports **cascaded** controllers, and integrates arch-specific controllers (in-tree reference: `pic-gicv2.c` / `pic-gicv3.c`) and MSI extensions. + +| Topic | Page | +| --- | --- | +| Virtual IRQ, `pirq`, global table | @ref page_device_pic_irq_domain | +| Framework API (`pic.c`) | @ref page_device_pic_core | +| Cascade & routing | @ref page_device_pic_cascade | +| Device tree / `rt_ofw_map_irq` | @ref page_device_pic_ofw | +| MSI / MSI-X (PCI vectors) | @ref page_device_pic_msi | +| Rockchip / Raspberry Pi examples | @ref page_device_pic_examples | + +Header: **`drivers/pic.h`**. Core: **`components/drivers/pic/pic.c`**. + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_PIC`** | PIC framework (**`RT_USING_DM`**) | +| **`MAX_HANDLERS`** | Size of global virtual IRQ table (default 256) | +| **`RT_PIC_ARM_GIC`** | GICv1/v2 + OFW | +| **`RT_PIC_ARM_GIC_V2M`** | GIC MSI frame (PCI MSI) | +| **`RT_PIC_ARM_GIC_V3`** | GICv3 | +| **`RT_PIC_ARM_GIC_V3_ITS`** | GICv3 ITS + LPI | +| **`RT_USING_PIC_STATISTICS`** | ISR timing stats | +| **`SOC_DM_PIC_DIR`** | BSP-specific PIC drivers | + +--- + +## Boot sequence (typical AArch64 DM) + +From **`libcpu/aarch64/common/setup.c`**: + +``` + rt_fdt_unflatten() + | + v + rt_pic_init() /* scan DT interrupt-controller nodes */ + | ofw_pic_init → RT_PIC_OFW_DECLARE stubs (e.g. GIC) + | rt_pic_linear_irq, rt_pic_add_traps + v + rt_pic_irq_init() /* per-PIC ops->irq_init (CPU IF enable) */ + | + v + rt_components_board_init() /* platform drivers, devices */ +``` + +Root **PIC** (e.g. SoC interrupt controller) must initialize here before devices call **`rt_dm_dev_get_irq`**. + +--- + +## Three IRQ numbers (do not confuse) + +| Name | Field | Used by | +| --- | --- | --- | +| **hwirq** | **`pirq->hwirq`** | This PIC hardware line (GIC SPI 32 = hwirq 32) | +| **Virtual / logical IRQ** | **`pirq->irq`** | **`rt_pic_attach_irq`**, **`rt_hw_interrupt_*`**, **`rt_dm_dev_get_irq`** | +| **irq_index** | `0 … pic->irq_nr-1` | Index into **`pic->pirqs[]`** for that controller | + +**`rt_pic_config_irq(pic, irq_index, hwirq)`** assigns **`irq = pic->irq_start + irq_index`** and stores hwirq. + +--- + +## End-to-end (device driver view) + +``` + DTS: device interrupts = <&gic GIC_SPI 77 IRQ_TYPE_LEVEL_HIGH> + | + v + rt_dm_dev_get_irq(dev, 0) + → rt_ofw_get_irq → rt_ofw_map_irq + → gic irq_parse (hwirq=77+32) + irq_map (virtual irq) + | + v + rt_pic_attach_irq(irq, handler, …) /* or rt_hw_interrupt_install */ + rt_pic_irq_unmask(irq) +``` + +Secondary PIC (PMIC, PCIe INTx): see @ref page_device_pic_cascade. + +--- + +## See also + +- @ref page_device_ofw_irq +- @ref page_device_pci_irq +- @ref page_device_pci_msi diff --git a/documentation/6.components/device-driver/pic/pic_cascade.md b/documentation/6.components/device-driver/pic/pic_cascade.md new file mode 100755 index 00000000000..58c3f5f29ad --- /dev/null +++ b/documentation/6.components/device-driver/pic/pic_cascade.md @@ -0,0 +1,131 @@ +@page page_device_pic_cascade PIC cascade and routing + +# Cascaded interrupt controllers + +When a **secondary PIC** (PMIC, GPIO aggregator, PCIe INTx block, GICv2m MSI frame) sits **below** a **parent** line on another PIC. + +--- + +## Concepts + +``` + Device hwirq → Child PIC → parent virtual IRQ → Root PIC (GIC) → CPU + | | | + irq_parse irq_map cascade link +``` + +| Link | Set by | +| --- | --- | +| **Child `pirq->hwirq`** | Child **`irq_parse`** | +| **Child virtual `irq`** | Child **`irq_map`** → **`rt_pic_config_irq`** | +| **Parent line** | **`rt_pic_cascade(child_pirq, parent_virtual_irq)`** | + +--- + +## `rt_pic_cascade` / `rt_pic_uncascade` + +```c +rt_err_t rt_pic_cascade(struct rt_pic_irq *pirq, int parent_irq); +``` + +| Step | Effect | +| --- | --- | +| **`pirq->parent`** | Parent **`pirq`** from **`irq2pirq(parent_irq)`** | +| Copy | **`priority`**, **`affinity`** from parent | +| **`RT_PIC_F_IRQ_ROUTING`** | Insert **`pirq`** on **`parent->children_nodes`** | + +**`rt_pic_uncascade`** clears parent link and removes from children list. + +**`parent_irq`** must be a **virtual IRQ** on the **parent PIC** (often from parent **`irq_map`**). + +--- + +## Dispatch modes + +### A — Hardware routing (`RT_PIC_F_IRQ_ROUTING`) + +Parent **`rt_pic_handle_isr`** walks **`children_nodes`**, ack/ISR/eoi each child. + +Used when parent trap fires for a **shared** line and children are registered in software tree. + +### B — Dedicated parent ISR (no routing flag) + +Parent IRQ has its own handler that **polls** child status and calls **`rt_pic_handle_isr`** per child. + +**Rockchip PCIe INTx** (`pcie-dw-rockchip.c`): + +- GIC **`legacy`** IRQ → **`rockchip_pcie_legacy_isr`** +- Reads **`PCIE_CLIENT_INTR_STATUS_LEGACY`**, per pin **`rt_pic_find_irq(&intx_pic, pin)`** → **`rt_pic_handle_isr`** + +Child **`intx_pic`** has **`irq_parse`/`irq_map`** for PCI **`interrupt-map`**; cascade to GIC is via **DT** mapping the legacy line, not **`rt_pic_cascade`**. + +### C — MSI / MSI-X child PIC + +MSI provider allocates a line on the **child** PIC, then wires it to the **parent**: + +```c +parent_irq = parent_pic->ops->irq_map(parent_pic, parent_hwirq, mode); +irq = rt_pic_config_irq(msi_pic, index, hwirq); +rt_pic_cascade(pirq, parent_irq); +``` + +PCI sees the **child** virtual IRQ from **`irq_alloc_msi`**. Hardware delivery is parent-specific — see @ref page_device_pic_msi. + +--- + +## Parent helpers + +After cascade, child drivers may call: + +```c +rt_pic_irq_parent_mask(pirq); +rt_pic_irq_parent_unmask(pirq); +rt_pic_irq_parent_eoi(pirq); +/* … */ +``` + +--- + +## Rockchip RK8xx PMIC IRQ chip + +**`bsp/rockchip/dm/mfd/rk8xx.c`** — reference cascade: + +1. PMIC **`rk8xx->irq`** (GPIO/IRQ from SoC) → **`rk8xx_pmic_isr`** wakes thread +2. Child **`rk8xx_irqchip`**: **`rt_pic_linear_irq`**, **`irq_map`** does: + ```c + irq = rt_pic_config_irq(pic, hwirq, hwirq); + rt_pic_cascade(pirq, rk8xx_ic->rk8xx->irq); /* PMIC pin → PMIC sub-IRQ */ + ``` +3. **`irq_parse`**: one cell = sub-hwirq index +4. Thread scans status registers → **`rt_pic_handle_isr`** per pending bit + +DT: **`interrupts`** on RK8xx children point at **`interrupt-controller`** sub-node. + +--- + +## Design checklist + +| Step | Action | +| --- | --- | +| 1 | Parent PIC up (**`rt_pic_init`**) | +| 2 | Child **`rt_pic_linear_irq`** | +| 3 | Child **`irq_map`** → virtual IRQ | +| 4 | **`rt_pic_cascade(child_pirq, parent_irq)`** | +| 5 | **`rt_ofw_data(child_ic_np) = child_pic`** | +| 6 | Parent ISR or **`RT_PIC_F_IRQ_ROUTING`** | + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Cascade before parent mapped | **`irq_map`** parent first | +| Wrong **`parent_irq`** | Must be virtual IRQ on parent PIC | +| Level IRQ EOI order | Child clear source before parent **EOI** | +| Missing routing + no demux ISR | Children never run | + +## See also + +- @ref page_device_pic_examples +- @ref page_device_pic_msi diff --git a/documentation/6.components/device-driver/pic/pic_core.md b/documentation/6.components/device-driver/pic/pic_core.md new file mode 100755 index 00000000000..6f6b7957f99 --- /dev/null +++ b/documentation/6.components/device-driver/pic/pic_core.md @@ -0,0 +1,107 @@ +@page page_device_pic_core PIC core framework + +# Core framework (`pic.c`, `pic_rthw.c`) + +Registration, ISR attach, trap dispatch, and global IRQ control wrappers. + +--- + +## `struct rt_pic` / `struct rt_pic_ops` + +| `rt_pic_ops` | Role | +| --- | --- | +| **`irq_init` / `irq_finit`** | Controller-wide setup (called from **`rt_pic_irq_init`**) | +| **`irq_enable` / `irq_disable`** | Optional gate | +| **`irq_mask` / `irq_unmask`** | Mask line at hardware | +| **`irq_ack` / `irq_eoi`** | Acknowledge / end of interrupt | +| **`irq_map`** | **Allocate virtual IRQ** from hwirq + mode (required for OFW) | +| **`irq_parse`** | Decode **`#interrupt-cells`** into **`out_pirq`** (required for OFW) | +| **`irq_set_priority` / `irq_set_affinity` / `irq_set_triger_mode`** | Configuration | +| **`irq_send_ipi`** | SMP IPI | +| MSI: **`irq_alloc_msi`**, **`irq_compose_msi_msg`**, … | @ref page_device_pic_msi | +| **`flags`** | **`RT_PIC_F_IRQ_ROUTING`** — parent dispatches children in **`rt_pic_handle_isr`** | + +--- + +## Trap path (root PIC) + +Root PIC (CPU-facing) registers a **trap handler**: + +```c +rt_pic_add_traps(my_root_handler, pic); +``` + +CPU exception → **`rt_pic_do_traps()`** → first handler returning **`RT_TRUE`**: + +1. Read hwirq from hardware (e.g. GIC IAR in `pic-gicv2.c`) +2. Map to **`pirq`** (IPI vs SPI) +3. **`irq_ack`** → **`rt_pic_handle_isr(pirq)`** → **`irq_eoi`** + +Child PICs without traps are reached via **cascade** or **dedicated parent ISR** (@ref page_device_pic_cascade). + +--- + +## `rt_pic_handle_isr` + +1. If **`children_nodes`** non-empty: for each child, **`irq_ack`**, recursive **`rt_pic_handle_isr(child)`**, **`irq_eoi`** +2. Call primary **`pirq->isr.action.handler(irq, param)`** +3. Call chained ISRs on **`pirq->isr.list`** + +Used by root trap and by software demux (e.g. Rockchip PCIe legacy ISR). + +--- + +## Attach / detach + +```c +rt_err_t rt_pic_attach_irq(int irq, rt_isr_handler_t handler, void *uid, + const char *name, int flags); +rt_err_t rt_pic_detach_irq(int irq, void *uid); +``` + +- **`irq`**: **virtual** global number +- First handler uses embedded **`pirq->isr`**; additional handlers malloc **`struct rt_pic_isr`** nodes +- **`rt_hw_interrupt_install`** in **`pic_rthw.c`** wraps **`rt_pic_attach_irq`** + +--- + +## Global IRQ API + +Thin wrappers: **`irq2pirq(irq)`** → **`pirq->pic->ops->…`** + +```c +void rt_pic_irq_mask(int irq); +void rt_pic_irq_unmask(int irq); +void rt_pic_irq_eoi(int irq); +/* … priority, affinity, trigger mode, IPI … */ +``` + +**`rt_hw_interrupt_mask` / `umask`** in arch code call these. + +--- + +## Init refcount (`phye`-style pattern in phye.c — here init/power on PIC is per-op at device level) + +For **`rt_phye`**-like refcount see phye; PIC uses **per-`pirq` ISR** and **init_count** only inside individual drivers. + +PIC core **`rt_phye_init`** N/A — use **`irq_map`** once per DT line. + +--- + +## `rt_pic_register` pattern (provider) + +There is no single **`rt_pic_register`** — providers: + +1. Embed **`struct rt_pic`** in driver private data +2. Set **`ops`**, **`priv_data`** +3. **`rt_pic_linear_irq`** +4. **`rt_ofw_data(np) = &pic`** (or **`rt_dm_dev_bind_fwdata`**) +5. Optional **`rt_pic_user_extends`**, **`rt_pic_add_traps`** + +--- + +## See also + +- @ref page_device_pic_irq_domain +- @ref page_device_pic_cascade +- @ref page_device_pic_ofw diff --git a/documentation/6.components/device-driver/pic/pic_examples.md b/documentation/6.components/device-driver/pic/pic_examples.md new file mode 100755 index 00000000000..6ab3c78ec2f --- /dev/null +++ b/documentation/6.components/device-driver/pic/pic_examples.md @@ -0,0 +1,96 @@ +@page page_device_pic_examples PIC platform examples + +# Rockchip & Raspberry Pi DM patterns + +Concrete BSP references (paths under **`rt-thread/bsp/`**). + +--- + +## Raspberry Pi 4 — GIC root (`dm/dts/bcm2711-rpi-4-b.dts`) + +```dts +gicv2: interrupt-controller@40041000 { + compatible = "arm,gic-400"; + interrupt-controller; + #interrupt-cells = <3>; + reg = <0x40041000 0x1000>, <0x40042000 0x2000>; +}; +``` + +- Probed by in-tree **`pic-gicv2.c`** during **`rt_pic_init`** +- All devices: **`interrupt-parent = <&gicv2>`** + **`GIC_SPI n type`** +- Drivers: **`rt_dm_dev_get_irq`** → virtual IRQ → **`rt_pic_attach_irq`** + +PCIe **`interrupt-map`** on host points at **`&gicv2 GIC_SPI 143…`**. + +--- + +## Raspberry Pi — BCM2835 mailbox (`dm/mailbox/mailbox-bcm2835.c`) + +```c +bcm_mbox->irq = rt_dm_dev_get_irq(dev, 0); +rt_hw_interrupt_install(bcm_mbox->irq, bcm2835_mbox_isr, …); +rt_hw_interrupt_umask(bcm_mbox->irq); +``` + +Single interrupt line from GIC — no cascade. Shows standard DM consumer pattern. + +--- + +## Rockchip — RK8xx PMIC IRQ (`dm/mfd/rk8xx.c`) + +| Piece | Detail | +| --- | --- | +| **Root** | PMIC **`rk8xx->irq`** from SoC GPIO | +| **Child PIC** | **`rk8xx_irqchip`**, **`rt_pic_linear_irq`** | +| **Cascade** | **`rt_pic_cascade(pirq, rk8xx->irq)`** in **`rk8xx_irq_map`** | +| **Demux** | Thread reads **`status_base`** regs → **`rt_pic_handle_isr`** | +| **DT** | **`interrupts = <&rk8xx_intc 0>`** on sub-devices | + +Template for **GPIO/PMIC expander** IRQ chips. + +--- + +## Rockchip — PCIe DW INTx (`dm/pci/pcie-dw-rockchip.c`) + +| Piece | Detail | +| --- | --- | +| **Child node** | **`legacy-interrupt-controller`** (OFW child) | +| **Child PIC** | **`rockchip_intx_ops`**, 4 lines (**`RT_PCI_INTX_PIN_MAX`**) | +| **`rt_ofw_data(intx_np) = &intx_pic`** | PCI **`interrupt-map`** targets this | +| **Parent ISR** | **`rockchip_pcie_legacy_isr`** on **`legacy`** GIC IRQ | +| **No `RT_PIC_F_IRQ_ROUTING`** | Software poll of legacy status register | + +PCI function drivers use **`rt_pci_ofw_irq_parse_and_map`** → maps through **`intx_pic`** → GIC. + +--- + +## Rockchip DM consumers (simple) + +Many drivers only use allocated virtual IRQ: + +```c +i2c->irq = rt_dm_dev_get_irq(dev, 0); +rt_pic_attach_irq(i2c->irq, handler, i2c, name, flags); +rt_pic_irq_unmask(i2c->irq); +``` + +Examples: **`i2c-rk3x.c`**, **`spi-rockchip.c`**, **`mailbox-rockchip.c`**. + +--- + +## Choosing a pattern + +| Hardware | Pattern | Example | +| --- | --- | --- | +| Single GIC line per device | **`get_irq` + attach** | UART, I2C | +| Aggregated status register | Child PIC + parent ISR poll | RK PCIe INTx | +| Many lines behind one pin | Child PIC + **cascade** | RK8xx PMIC | +| Message-signalled | MSI PIC + PCI | See @ref page_device_pic_msi | + +--- + +## See also + +- @ref page_device_pic_cascade +- @ref page_device_pci_ofw diff --git a/documentation/6.components/device-driver/pic/pic_irq_domain.md b/documentation/6.components/device-driver/pic/pic_irq_domain.md new file mode 100755 index 00000000000..8f13dfa3237 --- /dev/null +++ b/documentation/6.components/device-driver/pic/pic_irq_domain.md @@ -0,0 +1,110 @@ +@page page_device_pic_irq_domain PIC virtual IRQ domain + +# Virtual IRQ allocation (`pic.c`) + +How RT-Thread turns many interrupt controllers into one **global IRQ space** drivers can use. + +--- + +## Global table `_pirq_hash[]` + +- Size: **`MAX_HANDLERS`** (Kconfig, default 256) +- Each slot is a **`struct rt_pic_irq`** with **`irq`**, **`hwirq`**, **`pic`**, ISR list, optional **`parent`** + +Allocation pointer **`_pirq_hash_idx`**: + +| Region | Indices | Purpose | +| --- | --- | --- | +| **IPI** | `0 … RT_MAX_IPI-1` | SMP inter-processor interrupts | +| **PIC domains** | `_pirq_hash_idx` upward | Each **`rt_pic_linear_irq`** takes a contiguous block | + +**`rt_pic_config_ipi`** uses fixed low indices. Peripheral IRQs come from **`rt_pic_config_irq`**. + +--- + +## Per-controller window + +**`rt_pic_linear_irq(pic, irq_nr)`** (under global lock): + +```c +pic->irq_start = _pirq_hash_idx; /* first virtual IRQ for this PIC */ +pic->irq_nr = irq_nr; +pic->pirqs = &_pirq_hash[pic->irq_start]; +_pirq_hash_idx += irq_nr; +``` + +Example (GICv2 with 988 SPIs): + +- **`irq_nr = max_irq + 1 - 32`** (SGI/PPI handled separately) +- Virtual IRQs might be **`[RT_MAX_IPI, RT_MAX_IPI + irq_nr)`** +- **`irq_index = hwirq - 32`** for SPI hwirq 32 → index 0 + +--- + +## Mapping API + +```c +int rt_pic_config_irq(struct rt_pic *pic, int irq_index, int hwirq); +int rt_pic_config_ipi(struct rt_pic *pic, int ipi_index, int hwirq); +``` + +| Input | Meaning | +| --- | --- | +| **`irq_index`** | Offset within this PIC's window | +| **`hwirq`** | Hardware ID in that controller | +| **Return value** | **Global virtual IRQ** = **`pic->irq_start + irq_index`** | + +Lookup: + +| API | Returns | +| --- | --- | +| **`rt_pic_find_irq(pic, irq_index)`** | **`&pic->pirqs[irq_index]`** | +| **`rt_pic_find_pirq(pic, virtual_irq)`** | **`pirq`** if **`virtual_irq`** in range | +| **`irq2pirq(irq)`** | Global lookup for **`rt_pic_irq_*`** wrappers | + +--- + +## `struct rt_pic_irq` fields + +| Field | Role | +| --- | --- | +| **`irq`** | Virtual IRQ (global); **`-1`** = slot unused | +| **`hwirq`** | Controller-specific line | +| **`mode`** | Edge/level (`RT_IRQ_MODE_*`) | +| **`pic`** | Owning **`struct rt_pic *`** | +| **`parent`** | Cascade parent **`pirq`** | +| **`children_nodes`** | Routed children if **`RT_PIC_F_IRQ_ROUTING`** | +| **`isr`** | Handler chain | + +--- + +## IPI vs device IRQ + +| | IPI | Device IRQ | +| --- | --- | --- | +| Config | **`rt_pic_config_ipi`** | **`rt_pic_config_irq`** or **`irq_map`** | +| Affinity | All CPUs set in **`config_ipi`** | Per **`irq_set_affinity`** | +| Dispatch | GIC trap reads hwirq < 16 → **`rt_pic_find_ipi`** | hwirq ≥ 32 → **`rt_pic_find_irq`** | + +--- + +## Debug + +With MSH: **`list_irq`** / **`list_irq all`** ( **`pic.c`** ) dumps virtual IRQ, PIC name, hwirq, mode, handlers. + +**`rt_pic_linear_irq`** comment: intended for production PIC registration; **`rt_pic_cancel_irq`** only for failed init teardown. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **`MAX_HANDLERS` too small** | Sum all PIC **`irq_nr`** + IPI + headroom | +| Use hwirq as **`rt_pic_attach_irq` argument** | Always use **virtual `irq`** from **`rt_dm_dev_get_irq`** / **`irq_map`** | +| Re-**`config_irq`** on busy slot | **`config_pirq`** asserts empty ISR lists if PIC changes | + +## See also + +- @ref page_device_pic_core +- @ref page_device_pic_ofw diff --git a/documentation/6.components/device-driver/pic/pic_msi.md b/documentation/6.components/device-driver/pic/pic_msi.md new file mode 100755 index 00000000000..9f621798675 --- /dev/null +++ b/documentation/6.components/device-driver/pic/pic_msi.md @@ -0,0 +1,109 @@ +@page page_device_pic_msi PIC and PCI MSI + +# MSI / MSI-X at the PIC layer + +PCI **message-signalled interrupts** need an interrupt controller that can **allocate a vector**, **build the MSI message** (address + data), and **deliver** it into the same virtual IRQ space as wired IRQs. + +PCI enable path: @ref page_device_pci_msi. Wiring to PIC: this page. + +--- + +## What the MSI-capable PIC must do + +| Responsibility | Typical `rt_pic_ops` | +| --- | --- | +| Hand out a **virtual IRQ** per MSI/MSI-X vector | **`irq_alloc_msi`** / **`irq_free_msi`** | +| Tell PCI what to write to the device | **`irq_compose_msi_msg`**, **`irq_write_msi_msg`** | +| Mask / unmask at hardware | Often **`irq_mask`/`irq_unmask`** + **`rt_pci_msi_*`** helpers | +| Reach the CPU interrupt architecture | Usually **cascade** to a parent PIC line (@ref page_device_pic_cascade) or a **dedicated status ISR** that demuxes bits | + +The PIC driver does **not** replace PCI config-space MSI capability setup — that stays in **`pci/msi/`**. The PIC side owns **vector backing** and **message programming**. + +--- + +## Call flow (PCI → PIC) + +``` + pci/msi: rt_pci_msi_setup_irqs(pdev, nvec, type) + | + v + pdev->msi_pic->ops->irq_alloc_msi(pic, msi_desc) + | reserve hw line / table entry + | rt_pic_config_irq → virtual irq + | optional rt_pic_cascade(child_pirq, parent_irq) + | + v + irq_compose_msi_msg(pirq, &msg) → rt_pci_msi_write_msg + | + v + Driver: rt_pic_attach_irq(virtual_irq, handler, …) +``` + +**`struct rt_pci_device::msi_pic`** must point at the **`struct rt_pic`** that implements these ops (from DT **`msi-parent`**, **`msi-map`**, or board setup). + +--- + +## Implementing `irq_alloc_msi` + +Typical steps inside the provider: + +1. Pick a free **hwirq** / table index (bitmap or hardware allocator). +2. **`rt_pic_config_irq(pic, index, hwirq)`** — obtain **global virtual IRQ** for **`rt_pic_attach_irq`**. +3. If this PIC is not CPU-root: **`parent_irq = parent_pic->ops->irq_map(...)`** then **`rt_pic_cascade(pirq, parent_irq)`** so the parent line fires when MSI is triggered. +4. Store **`msi_desc`** on **`pirq`** if the PCI layer needs it for mask/unmask. +5. Return **virtual IRQ** (not hwirq) to **`pci/msi/irq.c`**. + +**`irq_free_msi`**: **`rt_pic_uncascade`**, release index, clear bitmap. + +--- + +## `irq_compose_msi_msg` + +Fill **`struct rt_pci_msi_msg`**: + +| Field | Meaning | +| --- | --- | +| **`address_lo` / `address_hi`** | Doorbell or MSI frame write target | +| **`data`** | Vector data written by device | + +SoC-specific: address may be a dedicated MSI window or a parent SPI doorbell — implement per TRM, not in the generic framework. + +--- + +## MSI vs MSI-X + +| | MSI | MSI-X | +| --- | --- | --- | +| Vectors | Often power-of-two block, may need **contiguous** virtual IRQs | Per-table-entry allocation common | +| Table | Config space | Often **BAR-mapped** table — map BAR before unmask | +| PIC | Same ops; PCI layer passes count / type | Same ops; one **`irq_alloc_msi`** per entry typical | + +--- + +## DT (optional) + +Platforms may describe: + +- **`msi-parent`** — phandle to MSI-capable interrupt controller +- **`msi-map` / `msi-map-mask`** — map PCI requester ID to controller + specifier + +Parsing is SoC-specific; result must be **`pdev->msi_pic`** before **`rt_pci_msi_enable`**. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **`RT_PCI_MSI` disabled** | Ops stubbed; PCI MSI APIs no-op | +| **`msi_pic` NULL** | Set in host/PCI probe before enable | +| Return **hwirq** to PCI | Return **virtual IRQ** from **`irq_alloc_msi`** | +| No parent link | MSI never reaches CPU — cascade or demux ISR | +| MSI-X table unmapped | Enable only after BAR iomap in PCI setup | + +## See also + +- @ref page_device_pci_msi +- @ref page_device_pic_cascade +- @ref page_device_pic_irq_domain +- @ref page_device_pic_core diff --git a/documentation/6.components/device-driver/pic/pic_ofw.md b/documentation/6.components/device-driver/pic/pic_ofw.md new file mode 100755 index 00000000000..558302666b5 --- /dev/null +++ b/documentation/6.components/device-driver/pic/pic_ofw.md @@ -0,0 +1,127 @@ +@page page_device_pic_ofw PIC and device tree + +# Device tree integration + +How **`interrupts`** properties become virtual IRQs via **`irq_parse`** + **`irq_map`**. + +Sources: **`pic.c`** (`ofw_pic_init`), **`ofw/irq.c`** (`rt_ofw_map_irq`). + +--- + +## Boot: discovering controllers + +**`rt_pic_init()`** → **`ofw_pic_init()`**: + +```c +rt_ofw_foreach_node_by_prop(ic_np, "interrupt-controller") + rt_ofw_stub_probe_range(ic_np, &_pic_ofw_start, &_pic_ofw_end); +``` + +**`RT_PIC_OFW_DECLARE(name, ids, handler)`** expands to **`RT_OFW_STUB_EXPORT(..., pic, handler)`** — same mechanism as other OFW stubs, linker section **`.rt_ofw_data.stub.*`**. + +Handler (e.g. **`gicv2_ofw_init`**) must: + +1. Map MMIO +2. **`rt_pic_linear_irq`** +3. **`rt_pic_add_traps`** (root only) +4. **`rt_ofw_data(np) = &pic`** + +--- + +## Device IRQ resolution + +**`rt_dm_dev_get_irq(dev, index)`** → **`ofw_api_call(get_irq)`** → **`rt_ofw_get_irq`** → **`rt_ofw_map_irq`**. + +### `rt_ofw_map_irq` (core logic) + +```c +pic = rt_pic_dynamic_cast(rt_ofw_data(ic_np)); +pic->ops->irq_parse(pic, irq_args, &pirq); /* RT_EOK == 0 */ +irq = pic->ops->irq_map(pic, pirq.hwirq, pirq.mode); +``` + +| Step | Responsibility | +| --- | --- | +| **`rt_ofw_parse_irq_cells`** | Fill **`rt_ofw_cell_args`** from **`interrupts`** | +| **`irq_parse`** | Set **`out_pirq->hwirq`**, **`mode`** (GIC: type + number) | +| **`irq_map`** | **`rt_pic_config_irq`**, return **virtual IRQ** | + +**`rt_pic_dynamic_cast`**: finds **`struct rt_pic`** embedded after **`rt_device`** or **`rt_object`** (name **`"PIC"`**). + +--- + +## Example: GIC-style `#interrupt-cells` (3 cells) + +From **`pic-gicv2.c`** / **`pic-gicv3.c`**: + +| Cell 0 | Cell 1 | Cell 2 | +| --- | --- | --- | +| **0** = SPI, **1** = PPI, … | Interrupt number | Flags → **`RT_IRQ_MODE_*`** | + +SPI DT number **0** → **hwirq 32** (`args[1] + 32`). + +DTS example (Raspberry Pi 4, **`bcm2711-rpi-4-b.dts`**): + +```dts +interrupts = ; +``` + +--- + +## Secondary `interrupt-controller` + +Child node must expose **`interrupt-controller`** + **`#interrupt-cells`** and set **`rt_ofw_data`** to its **`struct rt_pic`**. + +Consumers use **`interrupt-parent = <&child_ic>`** or inherited parent. + +**PCI `interrupt-map`**: resolves to parent GIC cells via @ref page_device_pci_ofw — still ends in **`rt_ofw_map_irq`**. + +--- + +## Provider requirements for OFW + +| `ops` | Required for DT devices | +| --- | --- | +| **`irq_parse`** | Yes | +| **`irq_map`** | Yes | +| **`irq_mask` / `irq_unmask`** | Strongly recommended | + +Without **`irq_map`**, **`rt_ofw_map_irq`** logs error and fails. + +--- + +## `RT_PIC_OFW_DECLARE` template + +```c +static rt_err_t my_ic_ofw_init(struct rt_ofw_node *np, const struct rt_ofw_node_id *id) +{ + /* map MMIO, fill my_pic.ops */ + rt_pic_linear_irq(&my_pic, NUM_LINES); + rt_ofw_data(np) = &my_pic; + return RT_EOK; +} + +static const struct rt_ofw_node_id my_ic_ids[] = { + { .compatible = "vendor,my-ic" }, + { /* sentinel */ }, +}; +RT_PIC_OFW_DECLARE(my_ic, my_ic_ids, my_ic_ofw_init); +``` + +Non-root: no **`rt_pic_add_traps`**; use parent ISR or cascade. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **`rt_ofw_data` NULL** | Provider not probed — **`rt_platform_ofw_request`** before cast | +| **`irq_parse` returns error** | **`rt_ofw_map_irq`** fails — check cell count | +| Wrong **`interrupt-parent`** | Walk **`rt_ofw_find_irq_parent`** in OFW IRQ code | +| Mix hwirq / virtual in driver | Only use **`rt_dm_dev_get_irq`** return value | + +## See also + +- @ref page_device_pic_irq_domain +- @ref page_device_ofw_irq diff --git a/documentation/6.components/device-driver/pin/dm.md b/documentation/6.components/device-driver/pin/dm.md new file mode 100755 index 00000000000..844e013afe4 --- /dev/null +++ b/documentation/6.components/device-driver/pin/dm.md @@ -0,0 +1,182 @@ +@page page_device_pin_dm GPIO device model (DM) + +# GPIO / PIN under `RT_USING_DM` + +**`RT_USING_PIN`** with **`RT_USING_DM`** adds a **global GPIO namespace**: each platform GPIO controller registers a contiguous **virtual pin number** range. Application code still uses **`rt_pin_mode` / `rt_pin_write` / `rt_pin_read`** (@ref page_device_pin); drivers use **`rt_pin_get_named_pin`** and optional **GPIO IRQ domains**. + +Sources: + +| File | Role | +| --- | --- | +| **`dev_pin_dm.c`** | Global pin table, **`pin_api_init`**, **`pin_pic_init`**, DM **`rt_pin_ops`** shim | +| **`dev_pin_ofw.c`** | **`rt_ofw_get_named_pin`** — `gpios` / `gpio` properties | +| **`dev_pin.c`** | Legacy single **`gpio`** device when no DM controllers | +| **`pin-pl061.c`** | Reference platform driver (`arm,pl061`) | + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_PIN`** | GPIO framework (default **y**) | +| **`RT_USING_DM`** | Required for DM path described here | +| **`RT_PIN_PL061`** | In-tree PL061 driver | +| **`SOC_DM_PIN_DIR`** | BSP adds SoC GPIO drivers via **`osource`** | + +--- + +## Architecture + +``` + DT: gpio-controller node (#gpio-cells, interrupt-controller optional) + | + | platform probe + v + struct rt_device_pin + rt_pin_ops (per controller) + | + +-- pin_api_init(gpio, pin_nr) → global pin_start..pin_start+nr-1 + +-- pin_pic_init(gpio, irq) → optional per-line PIC domain + +-- rt_dm_dev_bind_fwdata → rt_ofw_data(np) = gpio + | + v + First controller also: rt_device_pin_register("gpio", pin_api_dm_ops) + | + v + Consumers: + rt_pin_get_named_pin(dev, "foo", index, &mode, &val) + rt_pin_mode(pin, mode); rt_pin_write(pin, val); +``` + +| Concept | Meaning | +| --- | --- | +| **Local pin** | Index `0 … pin_nr-1` passed to **`rt_pin_ops`** | +| **Virtual pin** | **`pin_start + local`** — value for **`rt_pin_*`** and **`rt_pin_get_named_pin`** | +| **`pin_device_find`** | Maps virtual pin → owning **`struct rt_device_pin`** | + +Controllers are linked on **`pin_nodes`**; **`pin_total_nr`** only grows (ranges are not recycled). + +--- + +## Platform driver checklist + +1. **`struct rt_device_pin`** (or embed as first member of private struct). +2. Implement **`struct rt_pin_ops`**: at minimum **`pin_mode`**, **`pin_write`**, **`pin_read`**. +3. **`probe`**: iomap, clock, **`rt_dm_dev_bind_fwdata(dev, NULL, &gpio->parent)`**. +4. **`pin_api_init(&gpio->parent, gpio_count)`** — assigns **`pin_start`**. +5. If GPIO has a shared IRQ line: + - **`irq = rt_dm_dev_get_irq(dev, 0)`** + - **`pin_pic_init(&gpio->parent, irq)`** + - Hardware ISR: loop pending bits → **`pin_pic_handle_isr(&gpio->parent, local_pin)`** + - Install **`rt_hw_interrupt_install(irq, …)`** + **`umask`** +6. Optional **`pin_parse`** for non-default **`#gpio-cells`** (flags from **`dt-bindings/pin/pin.h`**). + +Reference: **`pin-pl061.c`** (`pl061_probe`). + +--- + +## `struct rt_pin_ops` (DM-relevant) + +| Callback | Role | +| --- | --- | +| **`pin_mode`** | Input / output / pull / open-drain | +| **`pin_write` / `pin_read`** | Level | +| **`pin_attach_irq`** | Direct per-pin ISR (optional) | +| **`pin_detach_irq`** | Remove handler | +| **`pin_irq_enable`** | Enable/disable line interrupt | +| **`pin_irq_mode`** | Edge/level when using legacy IRQ path | +| **`pin_parse`** | Decode **`#gpio-cells`** → local pin + **`flags`** | +| **`pin_get`** | Name → virtual pin (optional) | +| **`pin_debounce`** | Debounce time | + +If **`pin_attach_irq`** is **NULL**, **`dev_pin_dm.c`** stores handlers in **`legacy_isr[]`** and relies on **`pin_irq_mode`** + **`pin_pic_handle_isr`**. + +--- + +## Device tree: `gpios` properties + +**`rt_ofw_get_named_pin(np, propname, index, out_mode, out_value)`** tries: + +- **`{propname}-gpios`**, then **`{propname}-gpio`** +- If **`propname`** is **`NULL`**, tries **`gpios`** / **`gpio`** + +Flow: + +1. **`rt_ofw_parse_phandle_cells(..., "#gpio-cells", …)`** +2. **`rt_platform_ofw_request`** on GPIO provider if needed +3. **`pin_parse`** or default: **`args[0]`** = local pin +4. Convert **`flags`** → **`PIN_MODE_*`**, active level +5. Return **`local + pin_dev->pin_start`** (virtual pin) + +Wrapper on device: + +```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); +rt_ssize_t rt_pin_get_named_pin_count(struct rt_device *dev, const char *propname); +``` + +Typical **`probe`**: + +```c +pin = rt_pin_get_named_pin(dev, "reset", 0, &mode, &active); +if (pin < 0) + return pin; +rt_pin_mode(pin, mode); +rt_pin_write(pin, active); /* or inactive per DT flags */ +``` + +Examples in-tree: **`phye-generic-usb.c`** (`reset`), **`gpio-restart.c`**, **`led-gpio.c`** (child node), **`backlight-gpio.c`**. + +--- + +## GPIO IRQ + PIC + +**`struct rt_pin_irqchip`** is embedded in **`struct rt_device_pin`** (after **`parent`**). + +| API | Role | +| --- | --- | +| **`pin_pic_init(gpio, irq)`** | **`rt_pic_linear_irq`**, **`legacy_isr`** array, **`pin_dm_ops`** | +| **`pin_pic_handle_isr(gpio, local_pin)`** | Dispatch **PIC** path and/or **legacy_isr** callback | + +Per-line DT interrupt (2 cells: pin + flags) uses **`pin_dm_ops.irq_parse`** / **`irq_map`**: + +- Maps hwirq → virtual IRQ +- **`rt_pic_cascade(pirq, gpio->irqchip.irq)`** — GPIO block IRQ cascades to controller's **`rt_dm_dev_get_irq`** + +For **`rt_pin_attach_irq(virtual_pin, …)`** without DT interrupt on that line, legacy path + **`pin_irq_enable`** on hardware still applies. + +--- + +## Application / driver usage + +DM systems should prefer: + +```c +/* Not GET_PIN(port, n) from old BSP tables */ +pin = rt_pin_get_named_pin(&pdev->parent, "led", 0, &mode, NULL); +rt_pin_mode(pin, mode); +``` + +Legacy **`GET_PIN`** / **`drv_gpio.c`** pin tables apply to non-DM BSP GPIO only. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Skip **`pin_api_init`** | **`rt_ofw_get_named_pin`** adds wrong **`pin_start`** | +| **`pin_parse` missing** | Only **`args[0]`** used; wrong if `#gpio-cells` > 1 with flags | +| IRQ without **`pin_pic_init`** | Shared IRQ controllers need **`pin_pic_handle_isr`** | +| **`pin_attach_irq` vs legacy** | If **`pin_attach_irq` NULL**, must enable via **`pin_irq_enable`** after attach | +| Provider not probed | Ensure **`rt_platform_ofw_request`** or platform driver registered | + +--- + +## See also + +- @ref page_device_pin — **`rt_pin_*`** application API +- @ref page_device_pic_cascade — GPIO PIC cascade +- `components/drivers/include/drivers/dev_pin.h` +- `components/drivers/pin/dev_pin_dm.c`, `dev_pin_ofw.c` diff --git a/documentation/6.components/device-driver/pin/pin.md b/documentation/6.components/device-driver/pin/pin.md index c7c03eaa122..5eb59ce02a0 100644 --- a/documentation/6.components/device-driver/pin/pin.md +++ b/documentation/6.components/device-driver/pin/pin.md @@ -1,7 +1,11 @@ @page page_device_pin PIN Device +@subpage page_device_pin_dm + # Introduction of Pin +With **`RT_USING_DM`**, GPIO controllers use a global virtual pin namespace and device-tree **`gpios`** properties — see @ref page_device_pin_dm. This page documents the **`rt_pin_*`** application API (BSP **`GET_PIN`** macros apply to legacy non-DM setups). + The pins on the chip are generally divided into four categories: power supply, clock, control, and I/O. The I/O pins are further divided into General Purpose Input Output (GPIO) and function-multiplexed I/O (such as SPI/I2C/UART, etc.) pins, referring to their usage mode. Most MCU pins have more than one function. Their internal structure is different and their supported functionality are different. The actual function of the pin can be switched through different configurations. The main features of the General Purpose Input Output (GPIO) port are as follows: diff --git a/documentation/6.components/device-driver/pinctrl/pinctrl.md b/documentation/6.components/device-driver/pinctrl/pinctrl.md new file mode 100644 index 00000000000..2e5f9c129c3 --- /dev/null +++ b/documentation/6.components/device-driver/pinctrl/pinctrl.md @@ -0,0 +1,177 @@ +@page page_device_pinctrl Pin control (pinctrl) + +# Pinctrl subsystem + +Pinctrl applies **device-tree pin configurations** (mux, pull, drive strength, and related pad settings) before or during driver **`probe`**. + +- Core: **`components/drivers/pinctrl/pinctrl.c`** +- API / **`PIN_CONFIG_*`**: **`components/drivers/include/drivers/dev_pin.h`** + +**Kconfig**: **`RT_USING_PINCTRL`** (requires **`RT_USING_DM`** and **`RT_USING_PIN`**). BSP may add more providers via **`SOC_DM_PINCTRL_DIR`**. + +--- + +## End-to-end flow + +### 1. Device tree (consumer) + +The **consumer** node (UART, I2C, SPI, …) references **config subnodes** under a pinctrl controller: + +| Property | Role | +| --- | --- | +| **`pinctrl-0`**, **`pinctrl-1`**, … | Phandles to config subnodes (one **state** per index) | +| **`pinctrl-names`** | Names for each index: `"default"`, `"sleep"`, `"active"`, … | + +```dts +uart0: serial@40000000 { + compatible = "vendor,uart"; + reg = <0x40000000 0x1000>; + pinctrl-names = "default", "sleep"; + pinctrl-0 = <&uart0_pins>; + pinctrl-1 = <&uart0_sleep_pins>; +}; + +pinctrl: pinctrl@20000000 { + compatible = "vendor,pinctrl"; + reg = <0x20000000 0x1000>; + + uart0_pins: uart0-pins { + /* properties interpreted by the provider driver */ + }; +}; +``` + +**`pinctrl-0`** may contain **several** phandles (e.g. separate SCL/SDA nodes). They are applied **in order**; the first error stops the rest. + +Phandles must target a **config child** of the controller, not the controller root nor the consumer itself (see comment in **`ofw_pin_ctrl_confs_apply()`**). + +### 2. Core apply (`pinctrl.c`) + +For **`rt_pin_ctrl_confs_apply(dev, index)`** (or after name lookup): + +``` +foreach phandle in consumer's "pinctrl-": + conf_np = node from phandle + walk parents until node has "compatible" → provider_np + if !rt_ofw_data(provider_np): + rt_platform_ofw_request(provider_np) + pinctrl = rt_ofw_data(provider_np) /* struct rt_device_pin * */ + pinctrl->ops->pin_ctrl_confs_apply(&pinctrl->parent, conf_np) +``` + +### 3. Provider + +The **provider** is a platform driver for the pinctrl controller. At **`probe`** it typically: + +1. Maps **`reg`**, enables clock/reset. +2. Sets **`struct rt_pin_ops.pin_ctrl_confs_apply`** to parse **`fw_conf_np`** and program hardware. +3. Sets **`rt_ofw_data(controller_ofw_node) = &struct rt_device_pin.parent`**. + +Optional: **`pin_ctrl_gpio_request`** — pad setup when the GPIO layer claims a line (**`pin_gpio_request`** in **`dev_pin_dm.c`**). + +### 4. Platform bus (automatic) + +**`platform_probe()`** runs **before** the driver **`probe`**: + +```c +#ifdef RT_USING_PINCTRL + if (rt_pin_ctrl_confs_apply_by_name(dev, RT_NULL)) /* "default" */ + { + rt_pin_ctrl_confs_apply(dev, 0); /* fallback pinctrl-0 */ + } +#endif +``` + +So most drivers only need correct DT; they do **not** need to call pinctrl in **`probe`** unless switching states at runtime. + +Full platform order: @ref page_device_platform. + +--- + +## Usage + +### Consumer driver (typical) + +**Boot-time mux** — no code; define **`pinctrl-names`** + **`pinctrl-0`** (and extra states if needed). + +**Runtime state change** (sleep, high-speed, etc.): + +```c +/* By name */ +rt_pin_ctrl_confs_apply_by_name(&pdev->parent, "active"); + +/* By index */ +rt_ssize_t idx = rt_pin_ctrl_confs_lookup(&pdev->parent, "high-speed"); +if (idx >= 0) + rt_pin_ctrl_confs_apply(&pdev->parent, (int)idx); +``` + +**`name == NULL`** in **`rt_pin_ctrl_confs_apply_by_name`** means **`"default"`**. + +Some drivers (**`regulator-fixed`**, **`led-gpio`**, …) call apply again before using GPIO-related pads so DT and hardware stay aligned. + +### Public API + +| API | Role | +| --- | --- | +| **`rt_pin_ctrl_confs_lookup(dev, name)`** | Index in **`pinctrl-names`**; **`-RT_EEMPTY`** if missing | +| **`rt_pin_ctrl_confs_apply(dev, index)`** | Apply all phandles in **`pinctrl-`** | +| **`rt_pin_ctrl_confs_apply_by_name(dev, name)`** | Lookup + apply | + +Without **`RT_USING_OFW`**: **`-RT_ENOSYS`**. + +| Return | Meaning | +| --- | --- | +| **`RT_EOK`** | State applied | +| **`-RT_EEMPTY`** | No property / no name | +| **`-RT_ERROR`** | Provider missing or no **`pin_ctrl_confs_apply`** | +| **`-RT_EIO`** | Bad phandle | + +### Provider driver (BSP) + +1. Embed **`struct rt_device_pin`** (usually first member of private struct). +2. Implement **`pin_ctrl_confs_apply(device, fw_conf_np)`** — read provider-specific properties on **`fw_conf_np`** and write pad registers (or firmware, etc.). +3. **`rt_ofw_data(dev->ofw_node) = &pin_dev->parent`** at end of probe. +4. Document your binding (property names and cell layout) in the BSP; the core does not parse SoC-specific tuples. + +**`PIN_CONFIG_*`** in **`dev_pin.h`** are generic semantic types; providers map DT properties to hardware as needed. + +--- + +## DT layout example (multi-phandle) + +From **`pinctrl.c`** comments — one consumer, two phandles, two config nodes: + +```dts +i2c@4700 { + pinctrl-names = "default"; + pinctrl-0 = <&i2c_pin_scl, &i2c_pin_sda>; +}; + +&pinctrl { + i2c_pin_scl { /* provider-specific */ }; + i2c_pin_sda { /* provider-specific */ }; +}; +``` + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Provider not probed | Register platform driver; core calls **`rt_platform_ofw_request`** | +| Wrong phandle | Must point to **config subnode** under controller | +| Name / index mismatch | **`rt_pin_ctrl_confs_lookup`** string must match **`pinctrl-names`** | +| Partial apply | First failing phandle aborts the list | +| No **`rt_ofw_data`** | Provider **`probe`** must set it on controller node | +| Runtime PM | Add **`pinctrl-1`** + **`"sleep"`**; **`apply_by_name`** on suspend/resume | + +--- + +## See also + +- @ref page_device_platform — probe order +- @ref page_device_ofw — phandles +- `components/drivers/pinctrl/pinctrl.c` +- `components/drivers/core/platform.c` diff --git a/documentation/6.components/device-driver/platform/platform.md b/documentation/6.components/device-driver/platform/platform.md new file mode 100755 index 00000000000..d89c08eb120 --- /dev/null +++ b/documentation/6.components/device-driver/platform/platform.md @@ -0,0 +1,400 @@ +@page page_device_platform Platform bus + +# Platform bus + +Most **MMIO / on-SoC** peripherals under **`RT_USING_DM`** bind through the **platform bus**: a **`rt_platform_driver`** matches a **`rt_platform_device`**, then **`probe`** runs after the bus has applied common setup (pinctrl, clocks, power domain, IOMMU). + +- Header: **`components/drivers/include/drivers/platform.h`** +- Bus logic: **`components/drivers/core/platform.c`** +- OFW enumeration: **`components/drivers/core/platform_ofw.c`** + +--- + +## Data structures + +```c +struct rt_platform_device { + struct rt_device parent; /* parent.ofw_node, parent.bus, parent.drv */ + int dev_id; + const char *name; /* non-OFW matching only */ + const struct rt_ofw_node_id *id; /* filled by match; use id->data */ + void *priv; /* optional driver private pointer */ +}; + +struct rt_platform_driver { + struct rt_driver parent; + const char *name; + const struct rt_ofw_node_id *ids; /* compatible table, sentinel last */ + 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); +}; +``` + +In **`probe`**, almost all DM helpers take **`struct rt_device *dev = &pdev->parent`**. + +--- + +## Boot and init order + +Init levels (linker sort key, see **`rtdef.h`**): + +| Level | Typical use | +| --- | --- | +| **`INIT_CORE_EXPORT`** (`"1.0"`) | **`platform_bus_init`** — **`rt_bus_register(&platform_bus)`** | +| **`INIT_SUBSYS_EXPORT`** (`"1.1"`) | Early drivers: manual **`rt_platform_driver_register()`** (e.g. **`pin-pl061`**, **`pinctrl-single`**) | +| **`INIT_PLATFORM_EXPORT`** (`"1.2"`) | **`platform_ofw_device_probe`** — walk DT, **`rt_platform_device_register`** | +| **`INIT_DEVICE_EXPORT`** (`"3"`) | **`RT_PLATFORM_DRIVER_EXPORT`** — registers driver, then probes **all** devices already on the bus | + +``` +INIT_CORE → platform bus exists +INIT_SUBSYS → some drivers registered (optional) +INIT_PLATFORM → DT nodes → platform devices on bus → match + probe if driver already present +INIT_DEVICE → RT_PLATFORM_DRIVER_EXPORT → register driver → probe every unmatched device +``` + +**Takeaway**: DT devices appear at **`INIT_PLATFORM`**. Drivers using **`RT_PLATFORM_DRIVER_EXPORT`** register later and then bind via **`rt_bus_add_driver` → rt_bus_for_each_dev`**. Drivers that must run before the bulk DT scan use **`INIT_SUBSYS_EXPORT`** + manual **`rt_platform_driver_register`**. + +--- + +## Registering a driver (important) + +### Method A — `RT_PLATFORM_DRIVER_EXPORT` (most common) + +```c +static const struct rt_ofw_node_id my_ofw_ids[] = { + { .compatible = "vendor,my-device" }, + { /* sentinel */ } +}; + +static rt_err_t my_probe(struct rt_platform_device *pdev) +{ + struct rt_device *dev = &pdev->parent; + /* iomap, clk, init hardware, register char/block device */ + return RT_EOK; +} + +static rt_err_t my_remove(struct rt_platform_device *pdev) +{ + /* reverse probe: unregister, free */ + return RT_EOK; +} + +static struct rt_platform_driver my_driver = { + .name = "my-device", + .ids = my_ofw_ids, + .probe = my_probe, + .remove = my_remove, +}; + +RT_PLATFORM_DRIVER_EXPORT(my_driver); +``` + +Macro expansion (**`driver.h`**): + +```c +#define RT_PLATFORM_DRIVER_EXPORT(driver) RT_DRIVER_EXPORT(driver, platform, BUILIN) + +/* → static init calls rt_platform_driver_register(&driver) at INIT_DEVICE_EXPORT */ +``` + +Requirements: + +- **`name`**: driver name string — used for **name-based match** (see below); keep unique. +- **`ids`**: optional — **`compatible`** table for DT match; omit or set **`RT_NULL`** when using **name-only** binding. +- **`probe`**: return **`RT_EOK`** on success, **negative `rt_err_t`** on failure. +- **`remove`**: optional but recommended if **`probe`** allocates resources. + +### Matching: `ids` (compatible) vs `name` + +**`platform_match`** tries **both** (in order): + +| Step | Condition | Match when | +| --- | --- | --- | +| 1 | **`dev->ofw_node`** set and **`pdrv->ids`** non-NULL | **`compatible`** matches an entry in **`ids`** → **`pdev->id`** points at that entry | +| 2 | **`pdev->name`** and **`pdrv->name`** both set | Strings equal (same pointer or **`rt_strcmp` == 0**) | + +Implications: + +- **DT devices** (from **`INIT_PLATFORM`**): normally matched by **`ids`** / **`compatible`**. This is what most drivers use. +- **No `ids`**: set **`.ids = RT_NULL`** (or omit the field) and match by **`name`** — typical when the **device** is created in board code with **`rt_platform_device_alloc("same-string")`** and **`pdev->name`** equals **`pdrv->name`**. +- **Both** can be filled: compatible is tried first; if it fails, **name** is still tried (useful only if **`pdev->name`** is a non-empty string). + +Name-only driver + device: + +```c +static rt_err_t legacy_probe(struct rt_platform_device *pdev) { ... return RT_EOK; } + +static struct rt_platform_driver legacy_driver = { + .name = "board-uart", /* must equal pdev->name */ + .ids = RT_NULL, /* no compatible table */ + .probe = legacy_probe, +}; + +RT_PLATFORM_DRIVER_EXPORT(legacy_driver); + +/* In board init, after bus is up: */ +void board_uart_init(void) +{ + struct rt_platform_device *pdev = rt_platform_device_alloc("board-uart"); + pdev->name = "board-uart"; + rt_platform_device_register(pdev); +} +``` + +### Method B — manual `rt_platform_driver_register` + +Used when the driver must register **before** **`INIT_PLATFORM`** (dependency ordering) or the file predates the export macro: + +```c +static int my_drv_register(void) +{ + return rt_platform_driver_register(&my_driver); +} +INIT_SUBSYS_EXPORT(my_drv_register); +``` + +Same **`struct rt_platform_driver`** layout as method A. + +### What `rt_platform_driver_register` does + +1. Sets **`pdrv->parent.bus = &platform_bus`** +2. Copies **`pdrv->name`** into **`pdrv->parent.parent.name`** +3. **`rt_driver_register`** → **`rt_bus_add_driver`** +4. **`rt_bus_for_each_dev`** — for each device without **`dev->drv`**, **`platform_match`**; on match, **`platform_probe`** + +Registering the driver **never** creates devices; it only enables binding. + +--- + +## How devices get on the bus + +### Automatic — device tree (`INIT_PLATFORM`) + +**`platform_ofw_device_probe`** walks the tree (root, **`/clocks`**, **`/firmware`**, chosen framebuffer, …). For each **available** child with **`compatible`** (or a valid node name): + +1. **`alloc_ofw_platform_device(np)`** — **`rt_platform_device_alloc`**, **`pdev->parent.ofw_node = np`**, **`np->dev = &pdev->parent`** +2. **`rt_platform_device_register(pdev)`** → **`rt_bus_add_device`** → try all drivers + +**Bus nodes** (`simple-bus`, `simple-mfd`, `arm,amba-bus`, …): the walker **recurses into children first** without instantiating a device for the bus node itself (unless something else requests it). + +Device naming: **`ofw_device_rename`** — e.g. **`fe300000.serial:uart0`** from **`reg`** + node name + alias. + +### On demand — `rt_platform_ofw_request(np)` + +Called when another driver needs a **provider** (clock, gpio, pinctrl, regulator, phye, …) before or without global probe: + +| `np->dev` | Action | +| --- | --- | +| **NULL** | Allocate platform device, register on bus → match + probe | +| Set, **`dev->drv` NULL** | **`rt_bus_reload_driver_device`** — retry match | +| Set, **`dev->drv` set** | **`RT_EOK`** (already probed) | + +### Manual — non-OFW / board code + +```c +struct rt_platform_device *pdev = rt_platform_device_alloc("my-uart"); +pdev->name = "my-uart"; /* must match pdrv->name for name-based match */ +rt_platform_device_register(pdev); +``` + +Rare on DM + DT boards; used for legacy or test bring-up. + +### Child node only — `rt_platform_ofw_device_probe_child(np)` + +Registers **one** node as a platform device when it is not under **`/`** and not already **`RT_OFW_F_PLATFORM`**. Used by umbrella drivers that expose children explicitly. + +--- + +## Match rules (`platform_match`) + +See **Matching: `ids` vs `name`** above. Summary: + +1. **OFW / DT**: **`rt_ofw_node_match(np, pdrv->ids)`** when **`ids`** is set — **`pdev->id`** → use **`pdev->id->data`** for SoC variants. +2. **Name**: **`pdev->name`** vs **`pdrv->name`** — works without **`ids`**; primary path for **manually registered** devices. + +DT enumeration sets **`pdev->name`** from **`rt_platform_device_alloc("")`** (empty); binding those nodes still relies on **`compatible`** unless board code assigns a non-empty **`pdev->name`** that matches the driver. + +Only **one** driver binds per device. No match → device stays on **`dev_list`** until a driver registers later. + +--- + +## `platform_probe` wrapper (before your `probe`) + +The bus runs **before** **`pdrv->probe(pdev)`**: + +| Step | Function | Notes | +| --- | --- | --- | +| 1 | **`rt_pin_ctrl_confs_apply_by_name(dev, NULL)`** / **`pinctrl-0`** | @ref page_device_pinctrl | +| 2 | **`rt_ofw_clk_set_defaults(dev->ofw_node)`** | Assigned clocks | +| 3 | **`rt_dm_power_domain_attach(dev, RT_TRUE)`** | **`-RT_EEMPTY`** OK | +| 4 | **`rt_iommu_attach(dev)`** | **`-RT_EEMPTY`** OK if no IOMMU | +| 5 | **`pdrv->probe(pdev)`** | Your driver | + +On **`probe` failure**: IOMMU detach, power domain detach, **`dev->drv` cleared**. + +On **success**: **`RT_OFW_F_READLY`** set on the OFW node. + +Your **`probe`** should: parse resources (**`rt_dm_dev_iomap`**, **`rt_dm_dev_get_irq`**, **`rt_clk_get_by_name`**, …) → enable hardware → register the functional **`rt_device`** (UART, pin, LED, …). Return negative **`rt_err_t`** on hard errors and free what you already allocated. + +--- + +## Typical `probe` / `remove` pattern + +```c +static rt_err_t my_probe(struct rt_platform_device *pdev) +{ + rt_err_t err; + struct rt_device *dev = &pdev->parent; + struct my_dev *priv = rt_calloc(1, sizeof(*priv)); + + if (!priv) + return -RT_ENOMEM; + + priv->regs = rt_dm_dev_iomap(dev, 0); + if (!priv->regs) { + err = -RT_EIO; + goto err_free; + } + + priv->clk = rt_clk_get_by_index(dev, 0); + if (rt_is_err(priv->clk)) { + err = rt_ptr_err(priv->clk); + goto err_unmap; + } + + if ((err = rt_clk_prepare_enable(priv->clk))) + goto err_clk_put; + + /* register rt_device, set rt_ofw_data for providers */ + rt_dm_dev_bind_fwdata(dev, RT_NULL, priv); + + return RT_EOK; + +err_clk_put: + rt_clk_put(priv->clk); +err_unmap: + rt_iounmap(priv->regs); +err_free: + rt_free(priv); + return err; +} + +static rt_err_t my_remove(struct rt_platform_device *pdev) +{ + struct my_dev *priv = rt_dm_dev_get_fwdata(&pdev->parent); + /* unregister child device, disable clk, iounmap, rt_free(priv) */ + return RT_EOK; +} +``` + +**`remove` / `shutdown`**: bus calls **`pdrv->remove`**, then **`rt_iommu_detach`**, **`rt_dm_power_domain_detach`**, **`rt_platform_ofw_free(pdev)`** (drops OFW node ref, **`rt_free(pdev)`**). + +--- + +## `pdev` fields in practice + +| Field | Use | +| --- | --- | +| **`&pdev->parent`** | Pass to **`rt_dm_dev_*`**, **`rt_pin_get_named_pin`**, **`rt_ofw_*`** | +| **`pdev->id`** | After match: SoC variant via **`pdev->id->data`** (see **`pinctrl-single`** **`pcs_ofw_ids`**) | +| **`pdev->priv`** | Optional; core does not allocate it — prefer embedding **`rt_platform_device`** in your private struct or **`rt_dm_dev_bind_fwdata`** | +| **`pdev->parent.ofw_node`** | DT node for this instance | + +Common pattern: embed **`struct rt_platform_device`** as **not** used — instead embed nothing and use **`struct rt_device *dev = &pdev->parent`** only; provider drivers embed **`struct rt_device_pin parent`** etc. + +--- + +## Examples + +### A — DT + `ids` (typical) + +Device tree: + +```dts +mydev: device@40010000 { + compatible = "vendor,my-device"; + reg = <0x40010000 0x100>; + interrupts = ; +}; +``` + +Driver: + +```c +static rt_err_t my_probe(struct rt_platform_device *pdev) { ... } + +static const struct rt_ofw_node_id my_ids[] = { + { .compatible = "vendor,my-device" }, + { }, +}; + +static struct rt_platform_driver my_driver = { + .name = "vendor-my-device", + .ids = my_ids, /* match by compatible */ + .probe = my_probe, +}; +RT_PLATFORM_DRIVER_EXPORT(my_driver); +``` + +**`INIT_PLATFORM`** creates the device from DT; **`INIT_DEVICE`** registers the driver and **`probe`** runs. No **`rt_platform_device_register`** in board code. + +### B — name only, no `ids` + +No **`compatible`** binding — driver and device share the same **`name`** string: + +```c +static struct rt_platform_driver board_uart_driver = { + .name = "uart0", + .ids = RT_NULL, + .probe = board_uart_probe, +}; +RT_PLATFORM_DRIVER_EXPORT(board_uart_driver); + +/* board.c */ + struct rt_platform_device *pdev = rt_platform_device_alloc("uart0"); + pdev->name = "uart0"; + rt_platform_device_register(pdev); +``` + +Use for legacy BSP devices not described in DT, or tests. On DM + full DT boards, prefer **example A**. + +--- + +## When to use platform vs other buses + +| Use **platform** | Use another bus | +| --- | --- | +| SoC DT node with **`compatible`**, **`reg`**, **`interrupts`** | **PCI** endpoint → **`rt_pci_driver`** | +| GPIO LED, UART, GPIO controller, syscon, firmware node | **I2C** sensor → **`rt_i2c_driver`** + client | +| Provider requested via **`rt_platform_ofw_request`** | **SPI** slave → **`rt_spi_driver`** | + +Platform is the default for **on-chip** devices described in the board DTS. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **`compatible` typo** | No match on DT path; device never probes — check logs | +| **Name mismatch** | **`pdev->name`** must equal **`pdrv->name`**; empty **`pdev->name`** does not match | +| **Expect name match on DT node** | DT devices need **`ids`** unless you set **`pdev->name`** explicitly | +| Driver after device is OK | **`RT_PLATFORM_DRIVER_EXPORT`** relies on **`rt_bus_add_driver`** re-walk | +| Driver too late for early deps | Use **`INIT_SUBSYS_EXPORT`** or split provider driver earlier | +| **`probe` success but no driver** | Returning **`RT_EOK`** without work — still marks node **`READLY`** | +| Missing **`remove`** | OFW node freed by bus; leaks if you allocated IRQ/DMA without teardown | +| Provider not probed | **`rt_platform_ofw_request(np)`** from consumer before using phandle | +| **`pdev->priv` unset** | Do not assume bus sets it; use **`rt_calloc`** / **`bind_fwdata`** | +| Console not ready in early **`probe`** | **`INIT_DEVICE`** is late; use **`LOG_D`** or defer prints | +| Re-probe | **`rt_bus_reload_driver_device`** when driver registers after deferred provider | + +--- + +## See also + +- @ref page_device_pinctrl — applied in **`platform_probe`** before driver **`probe`** +- @ref page_device_ofw — DT nodes, phandles +- @ref page_device_dm — **`rt_dm_dev_iomap`**, clocks, IRQ +- `documentation/6.components/device-driver/core/bus.md` — generic **`rt_bus`** / **`RT_DRIVER_EXPORT`** +- `components/drivers/core/platform.c`, `platform_ofw.c` diff --git a/documentation/6.components/device-driver/power/board_reset.md b/documentation/6.components/device-driver/power/board_reset.md new file mode 100755 index 00000000000..af3ea718942 --- /dev/null +++ b/documentation/6.components/device-driver/power/board_reset.md @@ -0,0 +1,143 @@ +@page page_device_power_board_reset Board poweroff and restart + +# Board-level shutdown and restart + +**`components/drivers/power/reset/`** — drivers that hook **system-wide** power off or CPU reset, not per-IP reset lines (@ref page_device_reset). + +**Kconfig**: **`RT_USING_POWER_RESET`**, plus **`RT_POWER_RESET_GPIO_*`** or **`RT_POWER_RESET_SYSCON_*`**. + +API header: **`components/drivers/include/drivers/core/power.h`** — details in @ref page_device_dm_power. + +--- + +## Registration model + +These drivers are **platform drivers** whose **`probe`** registers a **callback** with the DM power core. They do **not** register a char device for normal I/O. + +``` +RT_PLATFORM_DRIVER_EXPORT(gpio_poweroff_driver) + | + v +gpio_poweroff_probe(pdev) + | + +-- parse GPIO (rt_pin_get_named_pin) or syscon reg + +-- rt_dm_power_off_handler(dev, MODE, PRIORITY, callback) + | + v +On rt_hw_cpu_shutdown() / rt_hw_cpu_reset(): + walk handlers by priority → callback → rt_dm_machine_shutdown/reset +``` + +### `rt_dm_power_off_handler` + +```c +rt_err_t rt_dm_power_off_handler(struct rt_device *dev, int mode, int priority, + rt_err_t (*callback)(struct rt_device *)); +``` + +| `mode` | When invoked | +| --- | --- | +| **`RT_DM_POWER_OFF_MODE_SHUTDOWN`** | System power off (**`rt_hw_cpu_shutdown`**) | +| **`RT_DM_POWER_OFF_MODE_RESET`** | Warm reset path (**`rt_hw_cpu_reset`**) | + +| `priority` (low → high) | Typical use | +| --- | --- | +| **`RT_DM_POWER_OFF_PRIO_PLATFORM`** | SoC flush | +| **`RT_DM_POWER_OFF_PRIO_LOW` / `DEFAULT` / `HIGH`** | Drivers, filesystem sync | +| **`RT_DM_POWER_OFF_PRIO_FIRMWARE`** | Last handoff | + +Callbacks run in order; non-**`RT_EOK`** is logged but later handlers still run. + +**`dev`**: passed to callback as **`dev->user_data`** context holder (GPIO drivers store private struct in **`user_data`**). + +--- + +## In-tree drivers + +| Driver | Compatible / binding | Registration | +| --- | --- | --- | +| **`gpio-poweroff`** | **`gpio-poweroff`** | **`RT_DM_POWER_OFF_MODE_SHUTDOWN`**, **`rt_pin_get_named_pin`** | +| **`gpio-restart`** | **`gpio-restart`** | **`RT_DM_POWER_OFF_MODE_RESET`** | +| **`syscon-poweroff`** | syscon reg | MMIO write in callback | +| **`syscon-reboot`** | syscon reg | reset trigger | +| **`syscon-reboot-mode`** | NVMEM + syscon | **`rt_dm_reboot_mode_register`** | + +All use **`RT_PLATFORM_DRIVER_EXPORT`** except some syscon drivers use **`INIT_SUBSYS_EXPORT` + `rt_platform_driver_register`** (same binding, different init level — see @ref page_device_platform). + +### GPIO poweroff example (flow) + +```c +static rt_err_t gpio_poweroff_do_poweroff(struct rt_device *dev) +{ + struct gpio_poweroff *gp = dev->user_data; + /* assert GPIO sequence, mdelay per DT timeout-ms */ + return RT_EOK; +} + +static rt_err_t gpio_poweroff_probe(struct rt_platform_device *pdev) +{ + ... + dev->user_data = gp; + return rt_dm_power_off_handler(dev, RT_DM_POWER_OFF_MODE_SHUTDOWN, + RT_DM_POWER_OFF_PRIO_DEFAULT, gpio_poweroff_do_poweroff); +} +RT_PLATFORM_DRIVER_EXPORT(gpio_poweroff_driver); +``` + +DT: + +```dts +poweroff { + compatible = "gpio-poweroff"; + gpios = <&gpio0 99 GPIO_ACTIVE_HIGH>; + timeout-ms = <3000>; + active-delay-ms = <100>; + inactive-delay-ms = <100>; +}; +``` + +--- + +## Reboot mode + +```c +rt_dm_reboot_mode_register(dev, reboot_mode_callback); +rt_hw_cpu_reset_mode("recovery"); /* sets cmd + rt_hw_cpu_reset() */ +``` + +**`syscon-reboot-mode`** persists mode to NVMEM/syscon so the next boot loader can read it. See **`reboot-mode.h`**, @ref page_device_dm_power. + +--- + +## Machine hooks + +BSP sets after handlers: + +```c +rt_dm_machine_shutdown = board_shutdown; /* PMIC off */ +rt_dm_machine_reset = board_reset; /* PSCI / watchdog */ +``` + +Without these, callbacks may run but SoC never powers off. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Wrong GPIO polarity | Match **`gpios`** flags and **`active_value`** from **`rt_pin_get_named_pin`** | +| **`RT_USING_PINCTRL` off** for gpio-poweroff | Kconfig depends on pinctrl for pad mux | +| Confuse with **`rt_reset_control`** | IP reset lines ≠ system poweroff | +| Handler sleeps in IRQ context | GPIO driver uses **`rt_thread_mdelay`** — ensure thread context | +| No **`rt_dm_machine_shutdown`** | Set BSP hook | + +--- + +## See also + +- @ref page_device_dm_power — handler lists, priorities +- @ref page_device_power — overview +- @ref page_device_platform +- @ref page_device_reset — reset **controller** API (different) +- `components/drivers/power/reset/` diff --git a/documentation/6.components/device-driver/power/charger.md b/documentation/6.components/device-driver/power/charger.md new file mode 100755 index 00000000000..96a75f575b5 --- /dev/null +++ b/documentation/6.components/device-driver/power/charger.md @@ -0,0 +1,70 @@ +@page page_device_power_charger Charger (gpio-charger) + +# Charger as `rt_power_supply` + +Chargers are **not** a separate class. A charger is a **`struct rt_power_supply`** with USB/mains **`type`** and properties such as **`ONLINE`**, **`STATUS`**, **`CONSTANT_CHARGE_CURRENT_MAX`**. + +Core registration flow: @ref page_device_power_supply. + +**Kconfig**: **`RT_POWER_SUPPLY_CHARGER_GPIO`** → **`components/drivers/power/supply/gpio-charger.c`**. + +--- + +## Registration flow (`gpio-charger`) + +``` +RT_PLATFORM_DRIVER_EXPORT(gpio_charger_driver) + | + v +gpio_charger_probe(pdev) + | + +-- rt_pin_get_named_pin (VBUS / status GPIOs) + +-- optional charge-current-limit GPIOs + mapping table + +-- parse charger-type → psy->type + +-- build gpio_charger_properties[] (only supported props) + +-- psy->dev = &pdev->parent; psy->ops = &gpio_charger_ops + +-- rt_power_supply_register(&gpioc->parent) + +-- GPIO IRQ → gpio_charger_isr → rt_power_supply_changed() +``` + +--- + +## Device tree (typical) + +```dts +charger { + compatible = "gpio-charger"; + charger-type = "usb-dcp"; + gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>; /* online */ + charge-status-gpios = <&gpio0 13 GPIO_ACTIVE_LOW>; + charge-current-limit-gpios = <&gpio0 14>, <&gpio0 15>; + charge-current-limit-mapping = <100000 0>, <500000 1>, <1500000 3>; +}; +``` + +Properties actually exposed depend on which GPIOs/mapping are present — the driver builds **`properties[]`** dynamically in **`probe`**. + +--- + +## `get_property` / `set_property` + +| Property | Behavior | +| --- | --- | +| **`ONLINE`** | Read VBUS GPIO | +| **`STATUS`** | Charging vs not from **`charge-status`** GPIO | +| **`CONSTANT_CHARGE_CURRENT_MAX`** | Mapping table + limit GPIOs; **`set_property`** selects limit | + +--- + +## Emulator + +**`emu-power.c`** (**`RT_POWER_SUPPLY_EMU`**): registers test battery + charger; MSH **`emu_charger`** toggles state for thermal/PM tests. + +--- + +## See also + +- @ref page_device_power_supply +- @ref page_device_regulator +- @ref page_device_pin +- `components/drivers/power/supply/gpio-charger.c` diff --git a/documentation/6.components/device-driver/power/power.md b/documentation/6.components/device-driver/power/power.md new file mode 100755 index 00000000000..8c2010cfcaa --- /dev/null +++ b/documentation/6.components/device-driver/power/power.md @@ -0,0 +1,85 @@ +@page page_device_power Power subsystem overview + +@subpage page_device_power_supply +@subpage page_device_power_charger +@subpage page_device_power_board_reset + +# Power (overview) + +RT-Thread splits **power** into several layers. They are related but **not interchangeable**: + +| Layer | Page | Scope | +| --- | --- | --- | +| **Regulator** | @ref page_device_regulator | Shared rails (LDO/DCDC): enable, voltage, refcount | +| **Power domain** | @ref page_device_power_domain | SoC power islands while system is running | +| **Power supply** | @ref page_device_power_supply | Battery/charger reporting (`rt_power_supply`) | +| **Board off/restart** | @ref page_device_power_board_reset | GPIO/syscon **shutdown** and **reset** | +| **System DM power** | @ref page_device_dm_power | Ordered **`rt_dm_power_off_handler`** + machine hooks | + +**Reset controller** (IP block reset lines) is a different subsystem: @ref page_device_reset — not under `components/drivers/power/reset/`. + +Sources under **`components/drivers/power/`**: + +| Path | Role | +| --- | --- | +| **`supply/`** | `rt_power_supply` core, daemon, **`gpio-charger`**, emulator | +| ** eset/** | **gpio-poweroff**, **gpio-restart**, **syscon-*** reboot/poweroff | + +Power domain providers: **components/drivers/pmdomain/** (see @ref page_device_power_domain). + +--- + +## Typical consumer `probe` order + +When a platform device comes up (@ref page_device_platform): + +``` +pinctrl (bus) + → clock defaults (bus) + → power domain attach (bus) [optional, -RT_EEMPTY OK] + → IOMMU attach (bus) [optional] + → driver probe: + regulator get/enable [your driver] + reset deassert / clk enable + rt_dm_dev_iomap + register functional device +``` + +For **battery/charger UI** or **PM policy**, drivers register **`rt_power_supply`** (see @ref page_device_power_supply). + +For **whole-system power off / reboot**, drivers register **`rt_dm_power_off_handler`** (see @ref page_device_power_board_reset, @ref page_device_dm_power). + +--- + +## Kconfig (`components/drivers/power/`) + +| Option | Role | +| --- | --- | +| **`RT_USING_POWER_SUPPLY`** | Power supply class + workqueue | +| **`RT_POWER_SUPPLY_DAEMON`** | Poll/notify policy thread | +| **`RT_POWER_SUPPLY_CHARGER_GPIO`** | **`gpio-charger`** | +| **`RT_POWER_SUPPLY_EMU`** | Test battery/charger | +| **`RT_USING_POWER_RESET`** | Board poweroff/restart drivers | +| **`RT_POWER_RESET_GPIO_*`**, **`RT_POWER_RESET_SYSCON_*`** | In-tree reset drivers | + +Regulator and power domain have separate Kconfig trees under **`components/drivers/regulator/`** and **`core/power_domain.c`**. + +--- + +## Quick map: what to implement + +| Goal | Register / API | +| --- | --- | +| Report SOC, charging, USB online | **`rt_power_supply_register`** + **`RT_PLATFORM_DRIVER_EXPORT`** | +| Shutdown PMIC / assert poweroff GPIO | **`rt_dm_power_off_handler(..., SHUTDOWN, ...)`** | +| Warm reset / recovery mode | **`rt_dm_power_off_handler(..., RESET, ...)`** + **`rt_dm_reboot_mode_register`** | +| Keep an IP block powered | **`rt_dm_power_domain_attach`** / **`rt_regulator_get`** | +| Share 3.3 V rail | **`rt_regulator_enable`** / **`put`** | + +--- + +## See also + +- @ref page_device_platform — device/driver binding +- @ref page_device_pin — GPIO for chargers and gpio-poweroff +- `components/drivers/power/Kconfig` diff --git a/documentation/6.components/device-driver/power/supply.md b/documentation/6.components/device-driver/power/supply.md new file mode 100755 index 00000000000..1678cd91496 --- /dev/null +++ b/documentation/6.components/device-driver/power/supply.md @@ -0,0 +1,213 @@ +@page page_device_power_supply Power supply + +# Power supply (`rt_power_supply`) + +Header: **`components/drivers/include/drivers/power_supply.h`**. Core: **`components/drivers/power/supply/supply.c`**. + +Models **batteries, USB ports, mains**, etc. as property objects (Linux power_supply–like). Chargers use the **same class** — @ref page_device_power_charger. + +**Kconfig**: **`RT_USING_POWER_SUPPLY`**, optional **`RT_POWER_SUPPLY_DAEMON`**. + +--- + +## Architecture + +``` +Platform driver probe + | + v + Fill struct rt_power_supply (type, properties, ops, dev) + | + v + rt_power_supply_register(psy) + | + +-- global list + rt_ofw_data(np) = psy + +-- optional thermal zone (PROP_TEMP) + +-- changed_work for notifiers / LED update + | + v + Consumers: rt_power_supply_get_property / notifier / daemon +``` + +There is **no separate platform bus** for power supplies: the **platform driver** owns **`struct rt_platform_device`**, and the **`rt_power_supply`** hangs off **`psy->dev = &pdev->parent`**. + +--- + +## Registering a power supply (important) + +### 1. Platform driver (`RT_PLATFORM_DRIVER_EXPORT`) + +Bind to DT with **`compatible`**, then register the supply in **`probe`**: + +```c +struct my_psy_priv { + struct rt_power_supply psy; + /* hardware state */ +}; + +static enum rt_power_supply_property my_props[] = { + RT_POWER_SUPPLY_PROP_STATUS, + RT_POWER_SUPPLY_PROP_CAPACITY, +}; + +static rt_err_t my_get_property(struct rt_power_supply *psy, + enum rt_power_supply_property prop, union rt_power_supply_property_val *val) +{ + /* read hardware, fill val->intval or val->strval */ + return RT_EOK; +} + +static const struct rt_power_supply_ops my_ops = { + .get_property = my_get_property, +}; + +static rt_err_t my_probe(struct rt_platform_device *pdev) +{ + struct rt_device *dev = &pdev->parent; + struct my_psy_priv *priv = rt_calloc(1, sizeof(*priv)); + + priv->psy.dev = dev; + priv->psy.type = RT_POWER_SUPPLY_TYPE_BATTERY; + priv->psy.properties = my_props; + priv->psy.properties_nr = RT_ARRAY_SIZE(my_props); + priv->psy.ops = &my_ops; + + return rt_power_supply_register(&priv->psy); +} + +static rt_err_t my_remove(struct rt_platform_device *pdev) +{ + struct my_psy_priv *priv = rt_dm_dev_get_fwdata(&pdev->parent); + rt_power_supply_unregister(&priv->psy); + rt_free(priv); + return RT_EOK; +} +``` + +Reference: **`gpio-charger.c`** — **`RT_PLATFORM_DRIVER_EXPORT(gpio_charger_driver)`**. + +### 2. `rt_power_supply_register` requirements + +| Field | Required | +| --- | --- | +| **`psy->dev`** | Yes — usually **`&pdev->parent`** | +| **`psy->ops`** + **`properties`** + **`properties_nr`** | Yes, unless **`battery_info`** is set (static battery descriptor path) | +| **`psy->type`** | Set before register (USB type, battery, mains, …) | +| **`psy->battery_info`** | Alternative to dynamic properties for some battery profiles | + +On success: + +- Inserts **`psy`** into global list +- **`rt_dm_dev_bind_fwdata(psy->dev, NULL, psy)`** when **`dev->ofw_node`** is set +- Initializes **`changed_work`** for **`rt_power_supply_changed()`** + +**`rt_power_supply_unregister`**: removes from list; fails with **`-RT_EBUSY`** if **`rt_power_supply_get`** refcount > 1. + +### 3. Optional: `set_property` + +Implement **`ops->set_property`** for writable limits (e.g. charge current). Only properties listed in **`properties[]`** are exposed via **`rt_power_supply_set_property`**. + +### 4. Notify userspace / daemon / LED + +After hardware state changes (GPIO IRQ, PMIC interrupt): + +```c +rt_power_supply_changed(psy); +``` + +This queues **`power_supply_changed_work`**, which: + +- Updates bound **`led_dev`** if **`RT_USING_LED`** +- Calls all **`rt_power_supply_notifier`** callbacks + +Register a notifier: + +```c +static rt_err_t my_notify(struct rt_power_supply_notifier *n, + struct rt_power_supply *psy) { ... return RT_EOK; } + +static struct rt_power_supply_notifier n = { + .callback = my_notify, +}; +rt_power_supply_notifier_register(&n); +``` + +**`RT_POWER_SUPPLY_DAEMON`**: built-in notifier drives low-battery / PM hints when **`RT_USING_PM`**. + +--- + +## Property API + +```c +rt_err_t rt_power_supply_get_property(psy, prop, &val); +rt_err_t rt_power_supply_set_property(psy, prop, &val); +``` + +**`get_property`** only succeeds if **`prop`** is in **`psy->properties[]`** (or **`battery_info`** path for supported static props). + +Common properties: **`STATUS`**, **`CAPACITY`**, **`ONLINE`**, **`VOLTAGE_NOW`**, **`CURRENT_NOW`**, **`TEMP`**, **`CONSTANT_CHARGE_CURRENT_MAX`**, … — see enum in **`power_supply.h`**. + +--- + +## Lookup from another device + +```c +struct rt_power_supply *psy = rt_power_supply_get(consumer_dev, "battery"); +if (rt_is_err(psy)) + ... +rt_power_supply_get_property(psy, RT_POWER_SUPPLY_PROP_CAPACITY, &val); +rt_power_supply_put(psy); +``` + +**`id`** is a **DT phandle property name** on **`consumer_dev`** (e.g. **`power-supply = <&bat>`** → **`"power-supply"`**). Resolves **`rt_ofw_data`** on the supply’s OFW node after register. + +--- + +## DT consumer example + +```dts +thermal-zone { + polling-delay = <1000>; + thermal-sensors = <&bat>; +}; + +bat: battery { + compatible = "vendor,fuel-gauge"; + monitored-battery; +}; +``` + +Fuel-gauge driver **`probe`** registers **`rt_power_supply`** on node **`bat`**. Thermal references **`&bat`** via **`rt_power_supply_thermal_register`** when **`PROP_TEMP`** exists. + +--- + +## Integration + +| Subsystem | Hook | +| --- | --- | +| **LED** | Assign **`psy->led_dev`** before register; **`changed`** updates indicators | +| **Thermal** | **`power_supply_thermal_register`** if **`PROP_TEMP`** | +| **Regulator** | Charger **`set_property`** may adjust rails (see **`gpio-charger`**) | +| **PM** | Daemon may request sleep modes on low SOC | + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Register before **`psy->dev`** set | **`register`** returns **`-RT_EINVAL`** | +| Empty **`properties`** | Must list every prop **`get_property`** handles | +| Forget **`rt_power_supply_changed`** | Notifiers/LED/PM stay stale | +| **`unregister` with active get** | **`put`** all references first | +| **`get_property` from ISR** | Defer to workqueue; gauge I2C may block | +| Stale SOC cache | Re-read hardware in **`get_property`** | + +--- + +## See also + +- @ref page_device_power — stack overview +- @ref page_device_power_charger — **`gpio-charger`** +- @ref page_device_platform — driver export +- `components/drivers/power/supply/supply.c` diff --git a/documentation/6.components/device-driver/power_domain/power_domain.md b/documentation/6.components/device-driver/power_domain/power_domain.md new file mode 100755 index 00000000000..3e6130be3d1 --- /dev/null +++ b/documentation/6.components/device-driver/power_domain/power_domain.md @@ -0,0 +1,269 @@ +@page page_device_power_domain Power domain + +# Power domain (`rt_dm_power_domain`) + +Header: **`components/drivers/include/drivers/core/power_domain.h`**. Core: **`components/drivers/core/power_domain.c`**. + +A **power domain** models a switchable SoC power island (GPU block, USB PHY, display cluster, …). Consumers reference domains in DT via **`power-domains`**; the **provider** driver registers **`struct rt_dm_power_domain`** (or a **proxy**) and implements **`power_on` / `power_off`**. + +This is **not** system shutdown (@ref page_device_dm_power) and **not** a regulator rail (@ref page_device_regulator). + +In-tree provider example: **`components/drivers/pmdomain/pm-domain-scmi.c`** (SCMI **`genpd`**). + +--- + +## End-to-end flow + +### Consumer (device with `power-domains` in DT) + +``` +platform_probe (platform.c) + | + v +rt_dm_power_domain_attach(dev, RT_TRUE) + | + +-- Parse first phandle in "power-domains" (index 0 only in attach) + +-- Resolve provider rt_ofw_data → domain or proxy->ofw_parse() + +-- domain->attach_dev(domain, dev) [optional] + +-- if on: rt_dm_power_domain_power_on(domain) + | + v +pdrv->probe(pdev) /* MMIO safe after domain on */ +``` + +On **remove/shutdown** / failed probe: + +``` +rt_dm_power_domain_detach(dev, RT_TRUE) /* optional power_off */ +``` + +**`-RT_EEMPTY`**: no **`power-domains`** property — @ref page_device_platform treats this as OK. + +### Provider (controller / firmware) + +``` +Driver probe (platform / SCMI) + | + v +Per domain: fill power_on, power_off [, attach_dev, detach_dev] + | + v +rt_dm_power_domain_register(domain) + | + +-- For DT phandle resolution: rt_ofw_data(provider_np) = domain + | OR proxy + rt_dm_power_domain_proxy_ofw_bind(np) + | + v +Consumer attach / get_by_index → power_on when ref transitions 0→1 usage +``` + +--- + +## Device tree + +**Consumer:** + +```dts +gpu@0x10000000 { + compatible = "vendor,gpu"; + reg = <0x10000000 0x10000>; + power-domains = <&pd_gpu>; + /* optional: */ + power-domain-names = "core"; +}; +``` + +| Property | Role | +| --- | --- | +| **`power-domains`** | Phandle(s) to provider; cells per **`#power-domain-cells`** | +| **`power-domain-names`** | Maps names to index for **`rt_dm_power_domain_get_by_name`** | + +**Provider** node must expose **`#power-domain-cells`** and bind **`rt_ofw_data`** to either: + +| `rt_ofw_data` object name | Type | +| --- | --- | +| **`PMD`** (`RT_POWER_DOMAIN_OBJ_NAME`) | Direct **`struct rt_dm_power_domain`** | +| **`PMP`** (`RT_POWER_DOMAIN_PROXY_OBJ_NAME`) | **`struct rt_dm_power_domain_proxy`** with **`ofw_parse`** | + +OFW core registers these in **`ofw.c`** object table. + +--- + +## Registering a provider (important) + +### Direct domain + +```c +struct my_pd { + struct rt_dm_power_domain domain; + void __iomem *regs; +}; + +static rt_err_t my_pd_power_on(struct rt_dm_power_domain *domain) +{ + struct my_pd *pd = rt_container_of(domain, struct my_pd, domain); + /* enable island — may sleep (I2C/SCMI/MMIO) */ + return RT_EOK; +} + +static rt_err_t my_pd_power_off(struct rt_dm_power_domain *domain) +{ + ... + return RT_EOK; +} + +static rt_err_t my_pd_probe(struct rt_platform_device *pdev) +{ + struct rt_device *dev = &pdev->parent; + struct my_pd *pd = rt_calloc(1, sizeof(*pd)); + + pd->domain.dev = dev; + pd->domain.power_on = my_pd_power_on; + pd->domain.power_off = my_pd_power_off; + + rt_dm_power_domain_register(&pd->domain); + rt_ofw_data(dev->ofw_node) = &pd->domain; /* object name becomes "PMD" */ + + return RT_EOK; +} +``` + +**`rt_dm_power_domain_register`**: initializes lists, **`ref`**, spinlock — does **not** touch hardware. + +### Proxy (multiple domains per node) + +When one DT node represents many domains (SCMI, generic genpd): + +```c +static struct rt_dm_power_domain *my_proxy_parse( + struct rt_dm_power_domain_proxy *proxy, struct rt_ofw_cell_args *args) +{ + return &domains[args->args[0]]; /* cell index selects domain */ +} + +/* probe: register each domain, then */ +proxy->ofw_parse = my_proxy_parse; +rt_dm_power_domain_proxy_ofw_bind(proxy, provider_ofw_node); +``` + +Reference: **`scmi_pm_domain_probe`** — registers **`num_domains`** entries, binds proxy on SCMI device node. + +### Child domains + +```c +rt_dm_power_domain_register_child(parent, child); +``` + +Sets **`child->parent_domain`**. **`power_on(parent)`** recursively powers on children listed in **`parent->child_nodes`** (BSP must link children into that list if hierarchy is used). **`unregister_child`** removes the child when the parent is idle. + +--- + +## Consumer API + +| API | Role | +| --- | --- | +| **`rt_dm_power_domain_attach(dev, on)`** | Link **`dev`** to **first** **`power-domains`** entry; **`on==RT_TRUE`** → **`power_on`** | +| **`rt_dm_power_domain_detach(dev, off)`** | Unlink; **`off==RT_TRUE`** → **`power_off`** | +| **`rt_dm_power_domain_get_by_index(dev, index)`** | Resolve domain pointer (no refcount change in **`put`** today) | +| **`rt_dm_power_domain_get_by_name(dev, name)`** | Uses **`power-domain-names`** | +| **`rt_dm_power_domain_put(domain)`** | Currently no-op success — hold domain via attach lifecycle | +| **`rt_dm_power_domain_power_on/off(domain)`** | Explicit on/off with **`ref`** counting | + +### Reference counting + +- **`rt_ref_init`**: domain starts at refcount **1** (idle / registered). +- **`power_on`**: if **`ref == 1`**, calls hardware **`power_on`**, then **`ref_get`**. +- **`power_off`**: **`ref_put`**; when **`ref` returns to 1**, calls hardware **`power_off`**. + +Multiple consumers should share one domain through **`attach`** + refcount, not duplicate **`power_on`** without coordination. + +### Manual use in driver + +Usually **unnecessary** — platform bus already calls **`attach(dev, RT_TRUE)`**. + +Extra domains (index > 0): + +```c +struct rt_dm_power_domain *pd = rt_dm_power_domain_get_by_index(dev, 1); +if (pd) + rt_dm_power_domain_power_on(pd); +``` + +**`attach` only handles index 0** (see comment in **`power_domain.c`**). + +--- + +## Optional callbacks + +| Callback | Role | +| --- | --- | +| **`attach_dev` / `detach_dev`** | Provider notified when a consumer links (clock/regulator coordination) | +| **`pirv`** | Provider private pointer (upstream spelling) | + +--- + +## Platform bus integration + +From **`platform_probe`**: + +```c +err = rt_dm_power_domain_attach(dev, RT_TRUE); +if (err && err != -RT_EEMPTY) + return err; +``` + +Failed driver **`probe`** → **`rt_dm_power_domain_detach(dev, RT_TRUE)`**. + +**RPMsg** and **PCI PME** also call **`attach`** where DT requires it. + +--- + +## Recommended sequencing + +With regulator and reset (@ref page_device_power, @ref page_device_clk): + +``` +regulator enable (if needed) + → power domain on (attach or power_on) + → clock / reset release + → rt_dm_dev_iomap + driver init +``` + +Power **off** for suspend (if implemented): reverse order per SoC manual — often **`idle clocks` → reset assert → power_off`**. + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_PMDOMAIN_SCMI`** | SCMI power protocol → **`pm-domain-scmi.c`** | +| **`SOC_DM_PMDOMAIN_DIR`** | BSP-specific genpd drivers | + +Requires **`RT_USING_DM`** and **`RT_USING_OFW`** for DT phandle parsing. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| No **`rt_ofw_data` on provider** | **`ofw_find_power_domain`** fails — bind domain or proxy at provider probe | +| **`attach` only index 0** | Use **`get_by_index(1, …)`** + **`power_on`** for more domains | +| **`-RT_EEMPTY` vs error** | Missing property is normal; log real failures only | +| **`power_on` in IRQ** | Callbacks may sleep — use thread context | +| **`put` is stub** | Rely on **`detach`** / **`power_off`** for lifecycle | +| **Provider not probed before consumer** | Ensure provider driver probes first or defer consumer | +| Confuse with **`rt_dm_power_off_handler`** | Domain = IP block; handler = whole system off | + +--- + +## See also + +- @ref page_device_power — stack overview +- @ref page_device_platform — auto **`attach`** in probe +- @ref page_device_regulator +- @ref page_device_ofw — phandles, **`#power-domain-cells`** +- @ref page_device_scmi_agent — SCMI provider +- `components/drivers/core/power_domain.c` +- `components/drivers/pmdomain/pm-domain-scmi.c` diff --git a/documentation/6.components/device-driver/regulator/regulator.md b/documentation/6.components/device-driver/regulator/regulator.md new file mode 100755 index 00000000000..0aa062a6112 --- /dev/null +++ b/documentation/6.components/device-driver/regulator/regulator.md @@ -0,0 +1,288 @@ +@page page_device_regulator Regulator + +# Regulator subsystem + +Headers: **`components/drivers/include/drivers/regulator.h`**, **`components/drivers/regulator/regulator_dm.h`**. Core: **`components/drivers/regulator/regulator.c`**, DT parse: **`regulator_dm.c`**. + +Models **LDO/DCDC/load switches** as **`struct rt_regulator_node`** (provider) and opaque **`struct rt_regulator`** (consumer handle). **Misuse can damage hardware** — use only the public API. + +**Kconfig**: **`RT_USING_REGULATOR`** (requires **`RT_USING_DM`**). In-tree: **`regulator-fixed`**, **`regulator-gpio`**, **`regulator-pwm`**, **`regulator-fan53555`**, **`regulator-scmi`**, plus **`SOC_DM_REGULATOR_DIR`**. + +--- + +## End-to-end flow + +### Provider (rail definition) + +``` +Platform/PMIC driver probe + | + v +regulator_ofw_parse(np, ¶m) /* DT limits, boot-on, always-on */ + | + v +Fill rt_regulator_ops (enable, set_voltage, ...) + | + v +rt_regulator_register(reg_np) + | + +-- rt_ofw_data(regulator_np) = reg_np + +-- optional: boot_on / always_on → enable at register +``` + +### Consumer (device that needs a rail) + +``` +Consumer probe (after regulator provider probed) + | + v +reg = rt_regulator_get(dev, "vcc") /* DT: vcc-supply = <®_xyz> */ + | + v +rt_regulator_set_voltage(reg, min_uv, max_uv) /* before enable on some PMICs */ +rt_regulator_enable(reg) + | + v +... use hardware ... + | + v +rt_regulator_disable(reg) +rt_regulator_put(reg) +``` + +**Not automatic**: unlike power domains, **`platform_probe` does not enable regulators** — the consumer driver must **`get` / `enable`** in **`probe`** and **`disable` / `put`** in **`remove`**. + +Recommended order with other power (@ref page_device_power): **regulator → power domain → clock/reset → iomap**. + +--- + +## Registering a regulator (important) + +### Requirements for `rt_regulator_register` + +| Field | Required | +| --- | --- | +| **`reg_np->dev`** | Owning **`rt_device`** (usually **`&pdev->parent`** of provider) | +| **`reg_np->param`** | **`struct rt_regulator_param`** (name, voltage/current limits, delays, flags) | +| **`reg_np->ops`** | At least **`enable`/`disable`** (and **`set_voltage`/`get_voltage`** if adjustable) | +| **`reg_np->supply_name`** | Rail name (often **`param->name`** from **`regulator-name`**) | + +```c +struct my_regulator { + struct rt_regulator_node node; + struct rt_regulator_param param; +}; + +static const struct rt_regulator_ops my_ops = { + .enable = my_enable, + .disable = my_disable, + .set_voltage = my_set_voltage, + .get_voltage = my_get_voltage, +}; + +static rt_err_t my_probe(struct rt_platform_device *pdev) +{ + struct rt_device *dev = &pdev->parent; + struct my_regulator *r = rt_calloc(1, sizeof(*r)); + + regulator_ofw_parse(dev->ofw_node, &r->param); + + r->node.dev = dev; + r->node.param = &r->param; + r->node.supply_name = r->param.name; + r->node.ops = &my_ops; + + return rt_regulator_register(&r->node); +} +``` + +On success with **`RT_USING_OFW`**: **`rt_ofw_data(dev->ofw_node) = reg_np`** so consumers can resolve phandles. + +**`boot_on` / `always_on`** in **`param`**: **`register`** calls **`regulator_enable`** immediately. + +### Platform driver export + +**`regulator-fixed`** pattern: + +```c +RT_PLATFORM_DRIVER_EXPORT(regulator_fixed_driver); +/* or INIT_SUBSYS_EXPORT + rt_platform_driver_register */ +``` + +Provider must probe **before** consumers that call **`rt_regulator_get`** (or **`get`** will **`rt_platform_ofw_request`** the regulator node). + +--- + +## Device tree + +**Regulator node** (provider): + +```dts +vcc_3v3: regulator-3v3 { + compatible = "regulator-fixed"; + regulator-name = "vcc"; + regulator-min-microvolt = <3300000>; + regulator-max-microvolt = <3300000>; + regulator-boot-on; + gpio = <&gpio0 10 GPIO_ACTIVE_HIGH>; +}; +``` + +**Consumer**: + +```dts +mmc@ffe00000 { + compatible = "vendor,mmc"; + vmmc-supply = <&vcc_3v3>; + vqmmc-supply = <&vcc_1v8>; +}; +``` + +| Consumer property | Lookup | +| --- | --- | +| **`vcc-supply`** | **`rt_regulator_get(dev, "vcc")`** | +| **`vqmmc-supply`** | **`rt_regulator_get(dev, "vqmmc")`** | + +**Parent rail**: regulator node may have **`vin-supply`**; **`rt_regulator_get`** links **`parent`** via **`regulator_check_parent`** for enable/disable/voltage propagation up the tree. + +--- + +## Consumer API + +| API | Role | +| --- | --- | +| **`rt_regulator_get(dev, id)`** | Resolve **`{id}-supply`** phandle → allocate **`struct rt_regulator`**, **`ref_get`** on node | +| **`rt_regulator_put(reg)`** | **`ref_put`**; may **`unregister`** node when last reference | +| **`rt_regulator_enable(reg)`** | Enable rail (parent first); applies **`enable_delay`** | +| **`rt_regulator_disable(reg)`** | Consumer refcount then hardware off when last user | +| **`rt_regulator_set_voltage(reg, min, max)`** | Notifiers + **`set_voltage`**; parent voltage updated too | +| **`rt_regulator_get_voltage(reg)`** | Read back µV | +| **`rt_regulator_is_supported_voltage(reg, min, max)`** | Check against **`param`** range | +| **`rt_regulator_set_mode` / `get_mode`** | Optional PMIC modes | + +### Enable / disable sharing + +Multiple consumers on one rail: + +- First **`enable`**: runs hardware **`ops->enable`**, bumps internal **`enabled_count`**. +- Further **`enable`**: if already on, returns **`RT_EOK`** immediately. +- **`disable`**: decrements consumer **`enabled_count`** until zero, then hardware **`disable`**. + +Each consumer should still call **`enable`/`disable`/`put`** symmetrically. + +### Notifiers + +```c +static rt_err_t my_notifier(struct rt_regulator_notifier *n, + rt_ubase_t msg, void *data) +{ + /* RT_REGULATOR_MSG_ENABLE, DISABLE, VOLTAGE_CHANGE, ... */ + return RT_EOK; +} + +struct rt_regulator_notifier notifier = { .callback = my_notifier }; +rt_regulator_notifier_register(reg, ¬ifier); +``` + +Keep callbacks short — they may run under regulator lock. + +--- + +## `struct rt_regulator_param` (DT via `regulator_ofw_parse`) + +| DT property | `param` field | +| --- | --- | +| **`regulator-name`** | **`name`** | +| **`regulator-min/max-microvolt`** | **`min_uvolt` / `max_uvolt`** | +| **`regulator-min/max-microamp`** | **`min_uamp` / `max_uamp`** | +| **`regulator-boot-on`** | **`boot_on`** | +| **`regulator-always-on`** | **`always_on`** | +| **`enable-active-high`** | **`enable_active_high`** | +| **`regulator-ramp-delay`** | **`ramp_delay`** (or **`ramp_disable`**) | +| **`regulator-enable-ramp-delay`** | **`enable_delay`** | + +--- + +## In-tree provider drivers (reference) + +| Driver | Compatible / binding | Registration | +| --- | --- | --- | +| **fixed** | **`regulator-fixed`** | GPIO enable, **`regulator_ofw_parse`**, **`rt_regulator_register`** | +| **gpio** | GPIO-controlled rail | Same pattern | +| **pwm** | PWM duty → voltage | **`RT_REGULATOR_PWM`** | +| **fan53555** | I2C PMIC | **`RT_REGULATOR_FAN53555`** | +| **scmi** | SCMI voltage protocol | **`RT_REGULATOR_SCMI`** | + +--- + +## Example: consumer in `probe` + +```c +static rt_err_t my_drv_probe(struct rt_platform_device *pdev) +{ + struct rt_device *dev = &pdev->parent; + struct rt_regulator *vcc; + rt_err_t err; + + vcc = rt_regulator_get(dev, "vcc"); + if (rt_is_err(vcc)) + return rt_ptr_err(vcc); + + if (!rt_regulator_is_supported_voltage(vcc, 3300000, 3300000)) + { + err = -RT_EINVAL; + goto err_put; + } + + err = rt_regulator_set_voltage(vcc, 3300000, 3300000); + if (err) + goto err_put; + + err = rt_regulator_enable(vcc); + if (err) + goto err_put; + + /* iomap, clocks, ... */ + pdev->priv = vcc; + return RT_EOK; + +err_put: + rt_regulator_put(vcc); + return err; +} + +static rt_err_t my_drv_remove(struct rt_platform_device *pdev) +{ + struct rt_regulator *vcc = pdev->priv; + + rt_regulator_disable(vcc); + rt_regulator_put(vcc); + return RT_EOK; +} +``` + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Provider not registered | **`get`** fails — ensure regulator **`probe`** runs first or defer consumer | +| Wrong property name | **`id`** must match **`{id}-supply`** in DT | +| **Enable before voltage** | Many PMICs need **`set_voltage`** then **`enable`** | +| **Skip `put`** | Leaks **`struct rt_regulator`** and node **`ref`** | +| **`set_voltage` in IRQ** | May sleep (I2C PMIC) | +| **Shared rail** | One consumer lowering voltage affects all — coordinate in DT | +| Bypass framework | Do not write PMIC registers from random drivers | +| **`always_on` rail** | **`disable`** may no-op in ops — still call **`put`** | + +--- + +## See also + +- @ref page_device_power — probe ordering +- @ref page_device_power_domain +- @ref page_device_platform — provider **`RT_PLATFORM_DRIVER_EXPORT`** +- @ref page_device_pin — GPIO enables on **regulator-fixed** +- `components/drivers/regulator/regulator.c` +- `components/drivers/regulator/regulator-fixed.c` diff --git a/documentation/6.components/device-driver/reset/reset.md b/documentation/6.components/device-driver/reset/reset.md new file mode 100755 index 00000000000..a8cf7044a49 --- /dev/null +++ b/documentation/6.components/device-driver/reset/reset.md @@ -0,0 +1,287 @@ +@page page_device_reset Reset controller + +# Reset controller (`rt_reset_control`) + +Header: **`components/drivers/include/drivers/reset.h`**. Core: **`components/drivers/reset/reset.c`**. + +Resolves device-tree **`resets`** / **`#reset-cells`** / **`reset-names`** into **`struct rt_reset_control`** handles, then drives **`assert` / `deassert` / `reset`** on the owning **`struct rt_reset_controller`**. + +This is **per-IP reset lines** (SoC reset manager), **not** whole-system power off — see @ref page_device_power_board_reset and @ref page_device_dm_power. + +**Kconfig**: **`RT_USING_RESET`** (requires **`RT_USING_DM`** + **`RT_USING_OFW`**). In-tree: **`reset-simple`**, **`reset-scmi`**, **`SOC_DM_RESET_DIR`**. + +--- + +## End-to-end flow + +### Controller (provider) + +``` +Platform / SCMI driver probe + | + v +Fill rt_reset_control_ops (ofw_parse, assert, deassert [, reset, status]) + | + v +rt_reset_controller_register(rstcer) + | + +-- rstcer->ofw_node set + +-- rt_ofw_data(np) = rstcer (object name "RSTC") +``` + +### Consumer (device using a reset line) + +``` +Consumer probe (reset controller must be registered first) + | + v +rstc = rt_reset_control_get_by_name(dev, "reset") + or rt_reset_control_get_by_index(dev, 0) + | + v +rt_reset_control_deassert(rstc) /* release IP from reset — typical */ + | + v +... clocks enabled, iomap, init ... + | + v +remove: rt_reset_control_assert(rstc) /* optional */ + rt_reset_control_put(rstc) +``` + +**Not automatic**: **`platform_probe`** does not deassert resets — the driver must **`get`** and **`deassert`** explicitly. + +Typical bring-up order (@ref page_device_power): **regulator → power domain → clock → reset deassert → MMIO**. + +--- + +## Registering a reset controller (important) + +### `rt_reset_controller_register` + +| Field | Role | +| --- | --- | +| **`rstcer->ofw_node`** | Controller DT node — **`rt_ofw_data`** stored here | +| **`rstcer->ops`** | **`struct rt_reset_control_ops`** | +| **`rstcer->priv`** | Driver private state (MMIO base, SCMI device, …) | + +```c +struct my_rstc { + struct rt_reset_controller controller; + void __iomem *base; +}; + +static rt_err_t my_assert(struct rt_reset_control *rstc) +{ + /* rstc->id from #reset-cells — put line into reset */ + return RT_EOK; +} + +static rt_err_t my_deassert(struct rt_reset_control *rstc) +{ + /* take line out of reset */ + return RT_EOK; +} + +static const struct rt_reset_control_ops my_rst_ops = { + .assert = my_assert, + .deassert = my_deassert, +}; + +static rt_err_t my_rst_probe(struct rt_platform_device *pdev) +{ + struct rt_device *dev = &pdev->parent; + struct my_rstc *r = rt_calloc(1, sizeof(*r)); + + r->base = rt_dm_dev_iomap(dev, 0); + r->controller.ofw_node = dev->ofw_node; + r->controller.ops = &my_rst_ops; + r->controller.priv = r; + + return rt_reset_controller_register(&r->controller); +} +``` + +Reference: **`reset-simple.c`** — MMIO bit per line, **`INIT_SUBSYS_EXPORT`** + **`rt_platform_driver_register`**. + +### `rt_reset_control_ops` + +| Callback | Role | +| --- | --- | +| **`ofw_parse(rstc, args)`** | Optional: validate cells, store extra data in **`rstc->priv`** | +| **`assert`** | Active **in reset** (hold block) | +| **`deassert`** | Active **out of reset** (run block) | +| **`reset`** | Optional pulse: assert → delay → deassert | +| **`status`** | Optional: report whether line is asserted | + +Default after parse: **`rstc->id = reset_args.args[0]`** (first **`#reset-cells`** argument). + +Polarity (**active-low** vs **high**) is **inside** your **`assert`/`deassert`** — read the SoC TRM, not just DT naming. + +### Unregister + +**`rt_reset_controller_unregister`**: **`-RT_EBUSY`** if any consumer handle remains on **`rstc_nodes`** — **`put`** all **`rt_reset_control`** first. + +--- + +## Device tree + +**Controller:** + +```dts +rst: reset-controller@1000 { + compatible = "vendor,reset"; + reg = <0x1000 0x100>; + #reset-cells = <1>; +}; +``` + +**Consumer:** + +```dts +uart@2000 { + compatible = "vendor,uart"; + reg = <0x2000 0x100>; + resets = <&rst 5>; + reset-names = "uart"; +}; +``` + +| Property | Role | +| --- | --- | +| **`resets`** | Phandle + cell(s) per **`#reset-cells`** | +| **`reset-names`** | Maps name → index for **`get_by_name`** | + +Multiple lines: + +```dts +dma@3000 { + resets = <&rst 10>, <&rst 11>; + reset-names = "dma", "dma-ocp"; +}; +``` + +--- + +## Consumer API + +| API | Role | +| --- | --- | +| **`rt_reset_control_get_by_index(dev, index)`** | Parse **`resets`** entry **index** | +| **`rt_reset_control_get_by_name(dev, name)`** | Match **`reset-names`** | +| **`rt_reset_control_get_array(dev)`** | Synthetic array control (see below) | +| **`rt_reset_control_put(rstc)`** | Remove from controller list, **`rt_free`** | +| **`rt_reset_control_deassert(rstc)`** | Release from reset (usual **probe** step) | +| **`rt_reset_control_assert(rstc)`** | Hold in reset (**remove** / suspend) | +| **`rt_reset_control_reset(rstc)`** | Pulse if **`ops->reset`** exists | +| **`rt_reset_control_status(rstc)`** | **`-RT_ENOSYS`** if no **`status`** op | + +OFW variants on raw node: **`rt_ofw_get_reset_control_by_index`**, **`_by_name`**, **`_array`**. + +### Error pointers + +**`get_*`** may return **`rt_err_ptr(-RT_ENOMEM)`** etc. Always test: + +```c +rstc = rt_reset_control_get_by_name(dev, "reset"); +if (rt_is_err(rstc)) + return rt_ptr_err(rstc); +if (!rstc) + return -RT_EEMPTY; /* no resets property */ +``` + +### NULL handle + +**`assert` / `deassert` / `reset`** on **`NULL`** return **`RT_EOK`** (no-op). Do not use that to detect “no reset in DT” — check **`get`** return. + +### Reset arrays + +**`rt_reset_control_get_array`**: builds a captain **`rstc`** with **`is_array`**; **`assert`/`deassert`/`reset`** recurse into child controls with **rollback** on partial failure. Use when one logical block needs several **`resets`** entries applied together. + +--- + +## Consumer `probe` / `remove` pattern + +```c +static rt_err_t my_probe(struct rt_platform_device *pdev) +{ + struct rt_device *dev = &pdev->parent; + struct rt_reset_control *rstc; + rt_err_t err; + + rstc = rt_reset_control_get_by_name(dev, "reset"); + if (rt_is_err(rstc)) + return rt_ptr_err(rstc); + + if (rstc && (err = rt_reset_control_deassert(rstc))) + goto err_put; + + /* clk_prepare_enable, rt_dm_dev_iomap, ... */ + pdev->priv = rstc; + return RT_EOK; + +err_put: + if (rstc) + rt_reset_control_put(rstc); + return err; +} + +static rt_err_t my_remove(struct rt_platform_device *pdev) +{ + struct rt_reset_control *rstc = pdev->priv; + + if (rstc) { + rt_reset_control_assert(rstc); + rt_reset_control_put(rstc); + } + return RT_EOK; +} +``` + +Example in-tree: **`dma-pl330.c`** (**`dma`**, **`dma-ocp`**), **`sdio-dw.c`**, **`8250-ofw.c`**, **`pinctrl-single.c`** (**`aib_rst`**). + +--- + +## In-tree providers + +| Driver | Binding | Registration | +| --- | --- | --- | +| **reset-simple** | **`snps,dw-low-reset`**, **`st,stm32-rcc`**, Allwinner, Aspeed, … | Platform **`probe`** → **`rt_reset_controller_register`** | +| **reset-scmi** | SCMI protocol **`reset`** | SCMI **`probe`** → register on agent node | + +--- + +## Semantics cheat sheet + +| Term in API | Typical meaning | +| --- | --- | +| **assert** | Line active → block held in reset | +| **deassert** | Line inactive → block can run | +| **reset** | Short assert then deassert (pulse) | + +Exact bit polarity is **driver-specific** (**`reset-simple`** uses **`active_low`** in platform data). + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| Controller not probed | **`get`** fails — **`rt_platform_ofw_request`** on provider or fix init order | +| **Clock vs reset order** | Enable clock only after **deassert** (or follow TRM) | +| **Skip `put`** | **`unregister`** busy; memory leak | +| **Shared reset line** | One driver **assert** while another runs → crash | +| **Wrong `id` cell** | Match **`#reset-cells`** layout in BSP doc | +| Confuse with **gpio-poweroff** | System off ≠ IP **`resets`** | +| Array partial failure | Core rolls back earlier entries — ops must be reversible | + +--- + +## See also + +- @ref page_device_power — ordering with regulator / domain / clk +- @ref page_device_power_board_reset — system shutdown GPIO (not this API) +- @ref page_device_platform — provider driver binding +- @ref page_device_clk +- @ref page_device_ofw — phandles, **`#reset-cells`** +- `components/drivers/reset/reset.c`, `reset-simple.c` diff --git a/documentation/6.components/device-driver/rpmsg/char.md b/documentation/6.components/device-driver/rpmsg/char.md new file mode 100755 index 00000000000..fb5c6a54d13 --- /dev/null +++ b/documentation/6.components/device-driver/rpmsg/char.md @@ -0,0 +1,66 @@ +@page page_device_rpmsg_char RPMsg character device + +# RPMsg char driver (`rpmsg_char.c`) + +**`RT_RPMSG_DRIVER_EXPORT(rpmsg_char_driver)`** — matches **`rt_rpmsg_device`** with **`id.name`** **`rpmsg-char`** or **`rpmsg-raw`** (set by **`virtio-rpmsg.c`**). + +Prerequisite: transport + **`rt_rpmsg_device_register`** — see @ref page_device_rpmsg (VirtIO stack). + +--- + +## Topology + +| Device | Name pattern | Ops | +| --- | --- | --- | +| Control | **`rpmsg_charN`** | **`control`** only — create/destroy endpoints | +| Per endpoint | **`rpmsg_x`** | **`read` / `write` / `control`** | + +**`probe`**: allocates **`rpmsg_char_ctrl`**, stores in **`rdev->parent.user_data`**, registers control **`rt_device`**. + +--- + +## Create endpoint + +On control device: + +```c +struct rt_rpmsg_endpoint_info info; + +rt_strncpy(info.name, "my-channel", RT_RPMSG_NAME_SIZE); +info.src = local_addr; +info.dst = remote_addr; + +rt_device_control(ctrl, RT_DEVICE_CTRL_RPMSG_CREATE_EPT, &info); +``` + +Flow: + +1. Default name **`rpmsg-raw`** if **`info.name`** empty +2. **`rt_rpmsg_create_endpoint(rdev, &info, rpmsg_char_rx_callback)`** +3. Ring buffer pool: **`RT_RPMSG_CHAR_MSG_MAX`** × **`RT_RPMSG_CHAR_MSG_SIZE_MAX`** (Kconfig) +4. Register char device **`rpmsg_%ux%u`** + +--- + +## Data path + +| Direction | Path | +| --- | --- | +| **TX** | **`write`** → **`rt_rpmsg_send`** | +| **RX** | VirtIO/backend → endpoint cb → **`rt_ringbuffer_put`** → **`read`** | +| **Overwrite** | **`RT_DEVICE_CTRL_RPMSG_DATA_OVERWRITE`** → **`put_force`** when full | + +RX matching uses **`find_endpoint`** with **`dst = incoming src`**, **`src = ANY`** — addresses must match how the endpoint was created. + +--- + +## Destroy + +**`RT_DEVICE_CTRL_RPMSG_DESTROY_EPT`**: deferred via **`rt_work`** until **`ref_count == 1`** and device closed. + +--- + +## See also + +- @ref page_device_rpmsg +- `components/drivers/rpmsg/rpmsg_char.c` diff --git a/documentation/6.components/device-driver/rpmsg/ns.md b/documentation/6.components/device-driver/rpmsg/ns.md new file mode 100755 index 00000000000..53763a8ce7d --- /dev/null +++ b/documentation/6.components/device-driver/rpmsg/ns.md @@ -0,0 +1,68 @@ +@page page_device_rpmsg_ns RPMsg name service + +# RPMsg name service (`rpmsg_ns.c`) + +**`RT_RPMSG_DRIVER_EXPORT(rpmsg_ns_driver)`** — matches **`id.name = "rpmsg-name-service"`** (second **`rt_rpmsg_device`** from **`virtio-rpmsg.c`** when **`VIRTIO_RPMSG_F_NS`**). + +Stack context: @ref page_device_rpmsg. + +--- + +## Fixed endpoint + +**`rpmsg_ns_probe`** creates: + +| Field | Value | +| --- | --- | +| **`info.name`** | `"name-service"` | +| **`src` / `dst`** | **`RT_RPMSG_NS_ADDR` (0x35)** | +| RX | **`rpmsg_ns_rx_callback`** | + +--- + +## Wire message + +```c +struct rt_rpmsg_ns_msg { + char name[32]; + le32 addr; + le32 flags; /* RT_RPMSG_NS_CREATE or RT_RPMSG_NS_DESTROY */ +}; +``` + +**`rpmsg_ns_rx_callback`**: + +| Flag | Action | +| --- | --- | +| **CREATE** | **`rt_rpmsg_create_endpoint(rdev, &info, NULL)`** → driver **`rx_callback`** fallback | +| **DESTROY** | **`find_endpoint`** + **`destroy_endpoint`** | + +**`virtio_rpmsg`** routes packets with **`dst == 0x35`** to **`vrpmsg->ns`** device, not the char device. + +--- + +## Local CREATE side + +When **`virtio_rpmsg_create_endpoint`** runs on a **local** endpoint and **`supp_ns`**: + +- Sends **NS CREATE** to **`RT_RPMSG_NS_ADDR`** with **`addr = ept->info.src`** (little-endian on wire) + +Remote can then open the announced channel. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **Endianness** | **`addr`** is **le32** on wire | +| **CREATE without consumer** | Orphan endpoints until DESTROY | +| **Only master has NS feature** | **`rpmsg_virtio_get_features`** sets **`VIRTIO_RPMSG_F_NS`** on master | + +--- + +## See also + +- @ref page_device_rpmsg +- @ref page_device_rpmsg_char +- `components/drivers/rpmsg/rpmsg_ns.c` diff --git a/documentation/6.components/device-driver/rpmsg/rpmsg.md b/documentation/6.components/device-driver/rpmsg/rpmsg.md new file mode 100755 index 00000000000..c9beff748ab --- /dev/null +++ b/documentation/6.components/device-driver/rpmsg/rpmsg.md @@ -0,0 +1,305 @@ +@page page_device_rpmsg RPMsg + +@subpage page_device_rpmsg_char +@subpage page_device_rpmsg_ns + +# RPMsg subsystem + +Header: **`components/drivers/include/drivers/rpmsg.h`**. Core bus: **`components/drivers/rpmsg/rpmsg.c`**. + +RPMsg is an **endpoint-oriented IPC bus** on top of a **transport** (VirtIO + shared memory + mailbox in the in-tree example). The core matches **`struct rt_rpmsg_device`** to **`struct rt_rpmsg_driver`** by **`id.name`**, then manages **endpoints** (name + src/dst addresses) and **`send`** via **`struct rt_rpmsg_ops`**. + +| Layer | File | Role | +| --- | --- | --- | +| **RPMsg bus** | **`rpmsg/rpmsg.c`** | Endpoints, match **`rpmsg-char`** / **`rpmsg-name-service`** | +| **VirtIO RPMsg device** | **`virtio/virtio-rpmsg.c`** | **`rt_rpmsg_ops`**, virtqueues, NS announce | +| **AMP transport** | **`rpmsg/rpmsg-rt-thread-virtio.c`** | Shared mem, mailbox doorbell, VirtIO transport | +| **Class drivers** | **`rpmsg_char.c`**, **`rpmsg_ns.c`** | Char devices + name service | + +**Kconfig**: **`RT_USING_RPMSG`**, **`RT_RPMSG_RT_THREAD_VIRTIO`** (default y), **`RT_RPMSG_CHAR_*`**. + +--- + +## Complete stack (VirtIO example) + +This is the **reference integration** in-tree: **`rpmsg-rt-thread-virtio.c`** + **`virtio-rpmsg.c`**. + +``` + DT: rt-thread,virtio-rpmsg (master or slave) + | + v + rpmsg_virtio_probe (platform) + |-- mbox: rt_mbox_request_by_index + |-- shmem: ioremap from "shmem" phandle + |-- slab DMA pool in shared RAM + |-- rt_virtio_device_register (device_id = VIRTIO_DEVICE_ID_RPMSG) + | + v + virtio_rpmsg_probe (virtio driver, RT_VIRTIO_DRIVER_EXPORT) + |-- virtio_rpmsg_vq_init: rx + tx virtqueues + |-- rt_rpmsg_device_register(id.name = "rpmsg-char") + |-- optional: second device "rpmsg-name-service" if VIRTIO_RPMSG_F_NS + | + v + rpmsg bus: match rpmsg_char_driver / rpmsg_ns_driver + |-- rpmsg_char_probe: control dev rpmsg_charN + |-- rpmsg_ns_probe: endpoint name-service @ 0x35 + | + v + Application: RT_DEVICE_CTRL_RPMSG_CREATE_EPT or NS_CREATE from remote + | + v + rt_rpmsg_send -> virtio_rpmsg_send -> virtqueue kick -> mbox notify peer +``` + +### Physical pieces + +| Piece | DT / code | Role | +| --- | --- | --- | +| **Shared memory** | **`shmem = <&soc_sram>`** | Descriptor rings + virtio buffers + link registers at base | +| **Mailbox** | **`mboxes = <&mbox N>`** | **`RPMSG_VIRTIO_EVENT_LINK`** (exchange queue pointers) and **`EVENT_QUEUE`** (kick) | +| **`queue-max`** | Both sides **must match** | Virtqueue depth | +| **`master` / `slave`** | Property on node | Master publishes queue offsets; slave reads peer TX ring for RX | + +**Boot role**: **`rpmsg.mode=master`** or **`slave`** in **`/chosen` bootargs** (`INIT_CORE_EXPORT rpmsg_mode_setup` in **`rpmsg.c`**). Must match firmware on the other core. **`virtio_rpmsg_probe`** swaps RX/TX queue indices for slave vs master. + +--- + +## Device tree example (master + slave) + +```dts +/ { + chosen { + bootargs = "rpmsg.mode=master"; /* or slave on the other OS */ + }; + + soc_sram: memory@60000000 { + reg = <0x60000000 0x100000>; + }; + + soc_mailbox: mailbox@ ... { /* provider */ }; + + rpmsg_virtio0: rpmsg-virtio@0 { + compatible = "rt-thread,virtio-rpmsg"; + shmem = <&soc_sram>; + mboxes = <&soc_mailbox 0>; /* master uses index 0 */ + queue-max = <64>; + master; + }; +}; +``` + +Remote side (conceptually): + +```dts + rpmsg_virtio1: rpmsg-virtio@0 { + compatible = "rt-thread,virtio-rpmsg"; + shmem = <&soc_sram>; /* same region */ + mboxes = <&soc_mailbox 1>; /* slave uses different mbox index */ + queue-max = <64>; + slave; + }; +``` + +After both sides probe: master writes **`MASTER_QUEUE_*`** offsets; **`EVENT_LINK`** makes slave map **`peer_tx_virtq`** and process inbound RPMsg frames. + +--- + +## Layer 1 — Register transport (`rpmsg-rt-thread-virtio`) + +**`RT_PLATFORM_DRIVER_EXPORT(rpmsg_virtio_driver)`** — does **not** register **`rt_rpmsg_device`** itself. + +**`rpmsg_virtio_probe`** checklist: + +1. **`rt_mbox_request_by_index(&mbox_client, 0)`** — **`rx_callback = rpmsg_virtio_rx_callback`** +2. Parse **`shmem`**, **`rt_ioremap_cached`** +3. **`master`** property → **`is_master`**, feature **`VIRTIO_RPMSG_F_NS`** only on master +4. **`rt_slab_init`** on half of shmem (DMA buffers for virtqueues) +5. **`rt_dma_device_set_ops`** — copy to/from shmem for non-coherent AMP +6. Fill **`rt_virtio_device`**: **`VIRTIO_DEVICE_ID_RPMSG`**, **`rpmsg_virtio_trans`** +7. **`rt_virtio_device_register(vdev)`** → triggers **`virtio_rpmsg_probe`** + +**Mailbox ISR** (`rpmsg_virtio_rx_callback`): + +- **`EVENT_LINK`**: peer published descriptor ring addresses — wire **`peer_tx_virtq`** +- **`EVENT_QUEUE`**: **`rt_virtqueue_isr`** for local virtqueues +- Peer TX avail ring progress → DMA tag fixup → **`used`** index update (feeds virtio RX path) + +--- + +## Layer 2 — Register RPMsg device (`virtio-rpmsg.c`) + +**`RT_VIRTIO_DRIVER_EXPORT(virtio_rpmsg_driver)`** + +**`virtio_rpmsg_probe`**: + +```c +vrpmsg->parent.id.name = "rpmsg-char"; +vrpmsg->parent.ops = &virtio_rpmsg_ops; /* create / destroy / send */ +rt_rpmsg_device_register(&vrpmsg->parent); + +if (VIRTIO_RPMSG_F_NS) { + vrpmsg->ns->id.name = "rpmsg-name-service"; + rt_rpmsg_device_register(vrpmsg->ns); +} +``` + +### `struct rt_rpmsg_ops` (backend contract) + +| Op | **`virtio_rpmsg` behavior** | +| --- | --- | +| **`create_endpoint`** | Local: optional **NS CREATE** message to **`RT_RPMSG_NS_ADDR`**. Remote-created: **`src == RT_RPMSG_ADDR_ANY`** | +| **`destroy_endpoint`** | Optional **NS DESTROY** | +| **`send`** | Build **`struct virtio_rpmsg_hdr`** (src, dst, len), **`rt_virtqueue_add_outbuf`**, **`kick`** | + +Wire header (**`virtio-rpmsg.h`**): + +```c +struct virtio_rpmsg_hdr { + le32 src, dst, reserved; + le16 len, flags; + u8 data[]; +}; /* max payload VIRTIO_RPMSG_MAX_BUF_SIZE - sizeof(hdr) */ +``` + +### RX path (`virtio_rpmsg_rx_done`) + +1. **`rt_virtqueue_read_buf`** on **RX** queue +2. Find endpoint: **`find_endpoint(rdev, dst=hdr->dst, src=ANY)`** + If **`dst == RT_RPMSG_NS_ADDR`** → use **`vrpmsg->ns`** device +3. **`ept->rx_callback(rdev, hdr->src, data, len)`** +4. Re-queue buffer with **`rt_virtqueue_add_inbuf`** + +--- + +## Layer 3 — RPMsg bus (`rpmsg.c`) + +### Register a **class driver** (char / NS / custom) + +```c +static const struct rt_rpmsg_device_id my_ids[] = { + { .name = "rpmsg-char" }, /* must match rdev->id.name from backend */ + { }, +}; + +static struct rt_rpmsg_driver my_driver = { + .ids = my_ids, + .probe = my_probe, + .remove = my_remove, + .rx_callback = my_default_rx, /* fallback if create_endpoint(..., NULL) */ +}; +RT_RPMSG_DRIVER_EXPORT(my_driver); +``` + +**`RT_RPMSG_DRIVER_EXPORT`** → **`INIT_DEVICE_EXPORT`** → **`rt_rpmsg_driver_register`** → probes all **`rt_rpmsg_device`** on bus with matching **`id.name`**. + +### Register an **`rt_rpmsg_device`** (custom backend) + +If you implement a non-VirtIO link: + +```c +rdev->id.name = "rpmsg-char"; /* or your id, but char driver won't bind */ +rdev->ops = &my_rpmsg_ops; +rt_rpmsg_device_register(rdev); +``` + +Bus **`probe`**: **`rt_dm_power_domain_attach`** then **`rdrv->probe(rdev)`**. + +--- + +## Endpoints and addressing + +```c +struct rt_rpmsg_endpoint_info info; + +rt_strncpy(info.name, "tty", RT_RPMSG_NAME_SIZE); +info.src = 0x400; /* local address (assigned by you or NS) */ +info.dst = 0x401; /* remote peer */ + +ept = rt_rpmsg_create_endpoint(rdev, &info, my_rx_cb); +if (rt_is_err(ept)) + return rt_ptr_err(ept); + +rt_rpmsg_send(ept, buf, len); +``` + +| API | Role | +| --- | --- | +| **`rt_rpmsg_create_endpoint`** | **`ops->create_endpoint`**, insert on **`ept_nodes`** | +| **`rt_rpmsg_destroy_endpoint`** | Backend destroy + **`rt_free`** | +| **`rt_rpmsg_find_endpoint`** | Match src/dst/name wildcards (**`RT_RPMSG_ADDR_ANY`**) | +| **`rt_rpmsg_send` / `sendto` / `send_wait`** | **`ops->send`** under **`ept->lock`** | + +**RX callback**: per-endpoint **`rx_cb`**, or driver-wide **`struct rt_rpmsg_driver::rx_callback`** if **`rx_cb == NULL`**. + +--- + +## Name service (summary) + +Well-known address **`RT_RPMSG_NS_ADDR` (0x35)**. Remote **CREATE** can auto-create endpoints (driver default RX logs only). Local **CREATE** from **`virtio_rpmsg_create_endpoint`** announces name+addr to peer when **`VIRTIO_RPMSG_F_NS`**. + +Details: @ref page_device_rpmsg_ns. + +--- + +## Userspace / driver channel (summary) + +1. Open control device **`rpmsg_charN`** (from **`rpmsg_char` probe**) +2. **`RT_DEVICE_CTRL_RPMSG_CREATE_EPT`** with **`struct rt_rpmsg_endpoint_info`** +3. Open **`rpmsg_x`**, **`read`/`write`** + +Details: @ref page_device_rpmsg_char. + +--- + +## Bring-up checklist + +| Step | Check | +| --- | --- | +| 1 | **`RT_USING_RPMSG`**, **`RT_RPMSG_RT_THREAD_VIRTIO`**, mailbox + shmem drivers | +| 2 | Same **`queue-max`**, same **`shmem`** region, correct **`mboxes`** index per role | +| 3 | **`rpmsg.mode=`** bootarg matches remote | +| 4 | Master has **`master;`** property; slave has **`slave;`** | +| 5 | **`virtio_rpmsg_probe`** succeeds → **`rpmsg_char`** / **`rpmsg-name-service`** on bus | +| 6 | Create endpoint or wait for NS CREATE; verify **`rt_rpmsg_send`** returns OK | +| 7 | @ref page_device_hwspinlock / @ref page_device_mailbox ordering if shared with other AMP IPC | + +--- + +## Custom backend (non-VirtIO) + +Implement **`struct rt_rpmsg_ops`** and register **`rt_rpmsg_device`** with an **`id.name`** your class driver matches (or reuse **`rpmsg-char`**): + +- **`create_endpoint`**: book-keep addresses / shared memory slots +- **`send`**: push frame with src/dst header to remote +- On RX interrupt: parse header → **`rt_rpmsg_find_endpoint`** → call **`ept->rx_callback`** + +Do **not** call **`rt_rpmsg_*`** from ISR if your **`send`** sleeps on virtqueue completion — use workqueue. + +--- + +## Pitfalls + +| Issue | Mitigation | +| --- | --- | +| **`id.name` mismatch** | Backend **`rpmsg-char`** vs driver table **`rpmsg-char`** / **`rpmsg-raw`** | +| **Master/slave queue swap** | Wrong **`rpmsg.mode`** → no RX | +| **`queue-max` mismatch** | Corrupt rings | +| **NS without feature** | Master must advertise **`VIRTIO_RPMSG_F_NS`** | +| **Endpoint without RX cb** | **`create_endpoint(..., NULL)`** needs driver **`rx_callback`** | +| **Buffer size** | Payload ≤ **`VIRTIO_RPMSG_MAX_BUF_SIZE - sizeof(hdr)`** (512 default) | +| **NULL `send` addr** | **`RT_RPMSG_ADDR_ANY`** rejected in **`virtio_rpmsg_send`** | +| **Power domain** | Bus probe attaches domain; **`-RT_EEMPTY`** OK | + +--- + +## See also + +- @ref page_device_rpmsg_char +- @ref page_device_rpmsg_ns +- @ref page_device_mailbox +- @ref page_device_hwspinlock +- @ref page_device_platform +- `components/drivers/rpmsg/rpmsg-rt-thread-virtio.c` +- `components/drivers/virtio/virtio-rpmsg.c` +- `components/drivers/virtio/virtio_config/virtio-rpmsg.h` diff --git a/documentation/6.components/device-driver/rtc/dm.md b/documentation/6.components/device-driver/rtc/dm.md new file mode 100755 index 00000000000..cb6aaee17dc --- /dev/null +++ b/documentation/6.components/device-driver/rtc/dm.md @@ -0,0 +1,55 @@ +@page page_device_rtc_dm RTC DM helpers (`rtc_dm.h`) + +# `rtc_dm.h` API + +Header: **`components/drivers/rtc/rtc_dm.h`**. Implementation: **`components/drivers/rtc/rtc_dm.c`**. + +DM RTC chip drivers include this header at **`probe`** to set the registered device name and to convert alarm fields. Chip **`control()`** / **`RT_DEVICE_CTRL_RTC_*`** and application **`set_date` / `set_time`** are documented in @ref page_device_rtc. + +--- + +## `rtc_dev_set_name` + +```c +int rtc_dev_set_name(struct rt_device *rtc_dev); +``` + +Assigns the RTC **`rt_device`** name before **`rt_device_register`**: + +| Condition | Name set | +| --- | --- | +| No device named **`"rtc"`** yet | **`rtc`** | +| **`"rtc"`** already exists | **`rtc1`**, **`rtc2`**, … (incrementing id) | + +Returns the result of **`rt_dm_dev_set_name`** (0 on success). + +**Note:** **`set_date()` / `set_time()`** in **`dev_rtc.c`** only look up **`"rtc"`**, not **`rtc1`**. + +--- + +## `rtc_wkalarm_to_timestamp` + +```c +time_t rtc_wkalarm_to_timestamp(struct rt_rtc_wkalarm *alarm); +``` + +Builds a **`time_t`** for **today’s calendar date** (from current **`time()`**) with hour/minute/second taken from **`alarm`** (`tm_hour`, `tm_min`, `tm_sec`). Other **`rt_rtc_wkalarm`** fields are ignored. + +Uses **`localtime_r`** + **`timegm`**. + +--- + +## `rtc_timestamp_to_wkalarm` + +```c +void rtc_timestamp_to_wkalarm(time_t timestamp, struct rt_rtc_wkalarm *alarm); +``` + +Fills **`alarm->tm_hour`**, **`tm_min`**, **`tm_sec`** from **`timestamp`** (local time). Does not set **`enable`** or date fields. + +--- + +## See also + +- @ref page_device_rtc +- `components/drivers/include/drivers/dev_rtc.h` — **`struct rt_rtc_wkalarm`**, **`RT_DEVICE_CTRL_RTC_*`** diff --git a/documentation/6.components/device-driver/rtc/rtc.md b/documentation/6.components/device-driver/rtc/rtc.md index 696607338d0..e8be02cd810 100644 --- a/documentation/6.components/device-driver/rtc/rtc.md +++ b/documentation/6.components/device-driver/rtc/rtc.md @@ -1,7 +1,12 @@ @page page_device_rtc RTC Device +@subpage page_device_rtc_dm + # Introduction of RTC +With **RT_USING_DM**, hardware RTC drivers register via platform / I2C / SPI **probe** — see @ref page_device_rtc_dm. This page documents the **set_date / set_time / time()** application API. + + The RTC (Real-Time Clock) provides accurate real-time clock time, which can be used to generate information such as year, month, day, hour, minute, and second. At present, most real-time clock chips use a higher precision crystal oscillator as a clock source. In order to work when the main power supply is powered down, some clock chips will be powered by a battery to keep the time information valid. The RT-Thread RTC device provides the basic services for the operating system's time system. RTCs find many uses in IoT scenarios, and even in secure transmission processes such as SSL, RTC has become an indispensable part. diff --git a/documentation/6.components/device-driver/scmi/agent.md b/documentation/6.components/device-driver/scmi/agent.md new file mode 100755 index 00000000000..57d1a0b88bf --- /dev/null +++ b/documentation/6.components/device-driver/scmi/agent.md @@ -0,0 +1,165 @@ +@page page_device_scmi_agent SCMI agent transports + +# SCMI agent layer + +Overview and protocol drivers: @ref page_device_scmi. + +Implementation directory: **`components/drivers/firmware/arm_scmi/`**. + +The **agent** is the only piece that knows how to move a `struct rt_scmi_msg` across the SoC boundary. Every `rt_scmi_device` on the SCMI bus shares the same **`struct scmi_agent`** instance created during platform **`scmi_probe`**. + +--- + +## Agent object (`agent.h`) + +```c +struct scmi_agent_ops { + const char *name; + rt_err_t (*setup)(struct scmi_agent *agent, struct rt_device *dev); + rt_err_t (*process_msg)(struct scmi_agent *agent, struct rt_scmi_msg *msg); +}; + +struct scmi_agent { + const struct scmi_agent_ops *ops; + void *priv; /* transport state (mailbox, SMC, virtio) */ +}; +``` + +| Callback | When it runs | +| --- | --- | +| `setup` | Once per SCMI controller: parse DT, map shmem, request mailbox channel / program SMC | +| `process_msg` | Every `rt_scmi_process_msg()` — serialize request, wait for reply, fill `out_msg` | + +`rt_scmi_process_msg()` (`agent.c`) sets `msg->sdev` and calls `agent->ops->process_msg`. + +--- + +## Platform driver registration (`agent.c`) + +```c +static const struct rt_ofw_node_id scmi_ofw_ids[] = { +#ifdef RT_FIRMWARE_ARM_SCMI_TRANSPORT_MAILBOX + { .compatible = "arm,scmi", .data = &scmi_agent_mailbox_ops }, +#endif +#ifdef RT_FIRMWARE_ARM_SCMI_TRANSPORT_SMC + { .compatible = "arm,scmi-smc", .data = &scmi_agent_smc_ops }, + /* arm,scmi-smc-param, qcom,scmi-smc */ +#endif +#ifdef RT_FIRMWARE_ARM_SCMI_TRANSPORT_VIRTIO + { .compatible = "arm,scmi-virtio", .data = &scmi_agent_virtio_ops }, +#endif + { /* sentinel */ }, +}; + +static struct rt_platform_driver scmi_driver = { + .name = "arm-scmi", + .ids = scmi_ofw_ids, + .probe = scmi_probe, +}; +INIT_SUBSYS_EXPORT(scmi_drv_register); +``` + +**`scmi_probe`** sequence: + +1. `agent_ops = pdev->id->data` — transport selected by `compatible`. +2. `agent_ops->setup(agent, &pdev->parent)` — transport private state in `agent->priv`. +3. **`scmi_channels_setup`**: for each available child of the controller node, read **`reg`** → `protocol_id`, allocate `rt_scmi_device`, set `sdev->agent`, `rt_scmi_device_register(sdev)`. +4. Register an extra device for **`SCMI_PROTOCOL_ID_BASE`** (common protocol) on the same agent. + +Protocol drivers attach later via the SCMI bus (see main SCMI page). + +--- + +## Shared memory (`shmem.c` / `shmem.h`) + +Mailbox and SMC transports place the SCMI frame in **`struct scmi_shared_mem`**: + +- `channel_status` — **FREE** / **ERROR** bits; writer clears FREE before sending. +- `msg_header` — built with `scmi_header(message_id, type, protocol_id, token)` from `scmi.h`. +- `msg_payload[]` — command input; reply read back into `msg->out_msg`. + +| API | Role | +| --- | --- | +| `scmi_shmem_msg_write(shmem, msg)` | Pack header + `in_msg`, mark channel busy | +| `scmi_shmem_msg_read(shmem, msg)` | Copy reply after firmware marks channel free | +| `scmi_shmem_clear_channel(shmem)` | Reset channel after TX done (mailbox) | + +DT **`shmem`** phandle must point at a node with **`compatible = "arm,scmi-shmem"`**; address is **`rt_ioremap`**’d in agent `setup`. + +Ensure the mapping matches firmware expectations (often **device / non-cacheable**). If the region is cacheable, flush or invalidate around access per your BSP / TRM. + +--- + +## Mailbox transport (`agent-mailbox.c`) + +Depends on **`RT_USING_MBOX`** and `RT_FIRMWARE_ARM_SCMI_TRANSPORT_MAILBOX`. + +**Setup** + +- Count **`mboxes`** and **`shmem`** phandles; pick channel index when multiple mailboxes exist (e.g. high vs low priority). +- `rt_mbox_request_by_index()` with client callbacks: + - **`tx_prepare`** → `scmi_shmem_msg_write()` + - **`tx_done`** → `scmi_shmem_clear_channel()` on success + - **`rx_callback`** → optional `msg->rx_callback` for async use + +**`process_msg`** + +- Spinlock around `rt_mbox_send(chan, msg, timeout)` (30 ticks in tree). +- Mailbox driver completes the round trip and fills output via shmem read path. + +Typical DT: + +```dts +scmi { + compatible = "arm,scmi"; + mboxes = <&firmware_mbox 0>; + shmem = <&scmi_shmem>; +}; +``` + +See @ref page_device_mailbox and @ref page_device_mailbox_dm. + +--- + +## SMC transport (`agent-smc.c`) + +Depends on **`RT_FIRMWARE_ARM_SCMI_TRANSPORT_SMC`**. + +**Setup** + +- **`arm,smc-id`** — SMC function number for SCMI. +- Map **`shmem`** like mailbox; optional **`interrupts`** — ISR sets `done` flag. +- Variants with **`arm,scmi-smc-param`** use page/offset fields in `struct scmi_agent_smc` for shared memory location passed in registers. + +**`process_msg`** + +- Writes message to shmem, issues **`smccc`** call, waits for IRQ or polling `done`, reads reply from shmem. + +Use when firmware exposes SCMI only through EL3 traps (no separate mailbox IP). + +--- + +## Virtio transport (`virtio-scmi.c`) + +Enabled when **`RT_VIRTIO_SCMI`** / `RT_FIRMWARE_ARM_SCMI_TRANSPORT_VIRTIO` is on. **`compatible = "arm,scmi-virtio"`** selects `scmi_agent_virtio_ops` — SCMI over a virtio queue instead of SoC mailbox/SMC. Typical in guest / hypervisor scenarios. + +--- + +## Error handling + +| Return | Meaning | +| --- | --- | +| `-RT_*` from `setup` / `process_msg` | RT-Thread transport failure (ENOMEM, EIO, mbox timeout, …) | +| `out.status` / payload status field | Firmware **`SCMI_ERR_*`** — use `rt_scmi_strerror(status)` for logs | + +Do not assume firmware status equals POSIX `-errno` unless your wrapper converts it (protocol drivers often map `SCMI_SUCCESS` to `RT_EOK` explicitly). + +--- + +## See also + +- @ref page_device_scmi +- `components/drivers/firmware/arm_scmi/agent.c` +- `components/drivers/firmware/arm_scmi/agent-mailbox.c` +- `components/drivers/firmware/arm_scmi/agent-smc.c` +- `components/drivers/virtio/virtio-scmi.c` diff --git a/documentation/6.components/device-driver/scmi/scmi.md b/documentation/6.components/device-driver/scmi/scmi.md new file mode 100755 index 00000000000..8166cb490bd --- /dev/null +++ b/documentation/6.components/device-driver/scmi/scmi.md @@ -0,0 +1,210 @@ +@page page_device_scmi SCMI firmware protocol + +# SCMI (System Control and Management Interface) + +On AArch64 platforms, **secure firmware** (TF-A, SCP, vendor EL3) often owns clocks, regulators, resets, and power domains. The OS talks to that firmware through **SCMI** instead of touching the same hardware directly. + +RT-Thread splits the stack into: + +| Layer | Location | Role | +| --- | --- | --- | +| **Message / protocol IDs** | `components/drivers/include/drivers/scmi.h` | Payload structs, `SCMI_PROTOCOL_ID_*`, `RT_SCMI_MSG_*` helpers | +| **SCMI bus** | `components/drivers/firmware/arm_scmi/bus.c` | Match `rt_scmi_driver` ↔ `rt_scmi_device` by protocol (and optional name) | +| **Agent + transport** | `components/drivers/firmware/arm_scmi/` | Platform driver `arm-scmi`, shared memory, mailbox / SMC / virtio | +| **Protocol providers** | `*-scmi.c` under clk, regulator, reset, … | `probe` on SCMI bus → register normal DM consumers (`rt_clk_*`, `rt_regulator_*`, …) | + +Transport details: @ref page_device_scmi_agent. + +**Kconfig**: `RT_FIRMWARE_ARM_SCMI` (needs `RT_USING_FIRMWARE`, `RT_USING_OFW`, Cortex-A / ARMv8). Enable mailbox and/or SMC transport options as your DT uses. + +--- + +## End-to-end flow + +``` +DT: compatible "arm,scmi" (+ mboxes/shmem or smc-id) + ↓ +INIT_SUBSYS: platform driver "arm-scmi" registered (agent.c) + ↓ +INIT_PLATFORM: platform device probe → scmi_probe() + ├─ agent_ops->setup() bind transport + ├─ scmi_channels_setup() foreach child "reg" → rt_scmi_device_register() + └─ register BASE protocol device (0x10) + ↓ +INIT_DEVICE (or SUBSYS for clk): RT_SCMI_DRIVER_EXPORT / rt_scmi_driver_register() + ↓ +scmi bus match(protocol_id [, name]) → driver->probe(sdev) + ↓ +Provider registers clk / regulator / reset / genpd / … + ↓ +Other drivers consume via normal OFW (clocks = <&…>, resets = <&…>, …) +``` + +Application and most drivers **never call SCMI directly**; they use the same APIs as MMIO backends (`rt_clk_get_by_index`, `rt_regulator_get`, `rt_reset_control_get`, …). + +--- + +## Data structures (`scmi.h`) + +```c +struct rt_scmi_device { + struct rt_device parent; /* parent.ofw_node, parent.bus, parent.drv */ + const char *name; /* optional; used in bus match */ + rt_uint8_t protocol_id; /* from child node property "reg" */ + struct scmi_agent *agent; +}; + +struct rt_scmi_device_id { + rt_uint8_t protocol_id; + const char *name; /* NULL = match any name for this protocol */ +}; + +struct rt_scmi_driver { + struct rt_driver parent; + const char *name; + const struct rt_scmi_device_id *ids; /* sentinel: protocol_id == 0 */ + rt_err_t (*probe)(struct rt_scmi_device *sdev); + rt_err_t (*remove)(struct rt_scmi_device *sdev); + rt_err_t (*shutdown)(struct rt_scmi_device *sdev); +}; +``` + +**Bus match** (`bus.c`): for each `ids[]` entry, `id->protocol_id == device->protocol_id`, and if `id->name` is set it must equal `device->name` (from OFW `protocol-name` or similar on the channel node). + +--- + +## Boot and init order + +| Export / register | Level | What runs | +| --- | --- | --- | +| `scmi_bus_init` | `INIT_CORE_EXPORT` | `rt_bus_register(&scmi_bus)` | +| `scmi_drv_register` (`arm-scmi` platform driver) | `INIT_SUBSYS_EXPORT` | Agent ready before bulk DT probe | +| `platform_ofw_device_probe` | `INIT_PLATFORM_EXPORT` | Creates `arm,scmi` platform device → `scmi_probe` | +| `RT_SCMI_DRIVER_EXPORT(...)` | `INIT_DEVICE_EXPORT` | Protocol driver registers → probes pending `rt_scmi_device`s | +| `scmi_clk_drv_register` | `INIT_SUBSYS_EXPORT` | **Exception**: `clk-scmi.c` calls `rt_scmi_driver_register()` manually (earlier than `RT_SCMI_DRIVER_EXPORT`) | + +If a protocol driver registers **after** its `rt_scmi_device` already exists, `rt_bus_add_driver` walks devices and runs `probe` (same pattern as platform bus). + +--- + +## Sending a message + +All transports funnel through: + +```c +rt_err_t rt_scmi_process_msg(struct rt_scmi_device *sdev, struct rt_scmi_msg *msg); +``` + +Build payloads with typed structs in `scmi.h` and: + +```c +struct rt_scmi_msg msg = RT_SCMI_MSG_IN_OUT(SCMI_VOLTAGE_DOMAIN_LEVEL_SET, &in, &out); +err = rt_scmi_process_msg(sdev, &msg); +/* check err, then out.status (firmware SCMI status, not always -errno) */ +``` + +| Helper | Use | +| --- | --- | +| `RT_SCMI_MSG_IN_OUT(id, in, out)` | Fixed-size in + out | +| `RT_SCMI_MSG_IN(id, in)` | Command with input only | +| `RT_SCMI_MSG_OUT(id, out)` | Command with output only | +| `RT_SCMI_MSG_RAW(...)` | Variable buffer sizes | + +Firmware returns **`SCMI_SUCCESS`** (0) or negative **`SCMI_ERR_*`** in message status fields; `rt_scmi_strerror()` maps those codes to short names (`agent.c`). + +**Threading**: `process_msg` is blocking for mailbox/SMC paths; do not call from ISR unless your transport is explicitly IRQ-safe. + +--- + +## Device tree (controller) + +Example mailbox transport (simplified): + +```dts +scmi: scmi { + compatible = "arm,scmi"; + mboxes = <&mbox 0>; + shmem = <&scmi_shmem>; + + /* Optional per-protocol channel nodes */ + protocol@14 { + reg = <0x14>; /* SCMI_PROTOCOL_ID_CLOCK */ + protocol-name = "clocks"; /* must match driver ids[].name if set */ + }; + protocol@17 { + reg = <0x17>; + protocol-name = "regulator"; + }; +}; + +scmi_shmem: sram@… { + compatible = "arm,scmi-shmem"; + reg = <…>; +}; +``` + +| `compatible` | Transport (`agent.c` `scmi_ofw_ids`) | +| --- | --- | +| `arm,scmi` | Mailbox (`agent-mailbox.c`) | +| `arm,scmi-smc`, `arm,scmi-smc-param`, `qcom,scmi-smc` | SMC (`agent-smc.c`) | +| `arm,scmi-virtio` | Virtio (`virtio-scmi.c`, if `RT_FIRMWARE_ARM_SCMI_TRANSPORT_VIRTIO`) | + +Child **`reg`** is the protocol ID (same values as `SCMI_PROTOCOL_ID_*` in `scmi.h`). The agent registers one **`rt_scmi_device`** per child; protocol drivers bind to that node’s `parent.ofw_node` for provider-specific subnodes (e.g. `regulators` under voltage protocol). + +--- + +## Built-in protocol drivers + +| Protocol ID | `ids[].name` | Source | Registers | +| --- | --- | --- | --- | +| `0x14` CLOCK | `"clocks"` | `clk/clk-scmi.c` | `rt_clk` provider | +| `0x17` VOLTAGE | `"regulator"` | `regulator/regulator-scmi.c` | `rt_regulator_register` | +| `0x16` RESET | (see driver) | `reset/reset-scmi.c` | reset controller | +| `0x11` POWER | `"genpd"` | `pmdomain/pm-domain-scmi.c` | power domain proxy | +| `0x19` PINCTRL | (see driver) | `pinctrl/pinctrl-scmi.c` | pin config via SCMI | +| SENSOR / thermal | (see driver) | `thermal/thermal-scmi.c` | thermal zone | + +Consumers in DT stay unchanged: e.g. `clocks = <&scmi_clk 42>;`, `resets = <&scmi_reset 3>;`, `power-domains = <&scmi_pd 1>;` — see @ref page_device_clk, @ref page_device_regulator, @ref page_device_reset, @ref page_device_power_domain, @ref page_device_pinctrl. + +--- + +## Adding a protocol driver + +1. Implement `probe(struct rt_scmi_device *sdev)` — use `sdev->parent.ofw_node` and `sdev->agent` is already set. +2. Issue commands with `rt_scmi_process_msg(sdev, &msg)`. +3. Register the high-level provider (clock, regulator, …) your consumers expect. +4. Publish the driver: + +```c +static const struct rt_scmi_device_id my_ids[] = { + { SCMI_PROTOCOL_ID_xxx, "my-protocol-name" }, /* name must match DT if used */ + { /* sentinel */ }, +}; + +static struct rt_scmi_driver my_scmi_driver = { + .name = "my-scmi", + .ids = my_ids, + .probe = my_probe, +}; +RT_SCMI_DRIVER_EXPORT(my_scmi_driver); +``` + +Match **`protocol-name`** on the channel node to `ids[].name` when the table entry sets `name` non-NULL. + +--- + +## Debugging + +- Confirm **`arm-scmi` probe** and child `reg` values in logs (`DBG_TAG` `scmi.agent`, `scmi.bus`). +- Mailbox path: mailbox TX/RX, shared memory **channel free** bit (`shmem.c`), 30-tick timeout on `rt_mbox_send`. +- SMC path: `arm,smc-id`, IRQ completion, page/offset for `arm,scmi-smc-param`. +- Version skew: use BASE protocol `SCMI_COM_MSG_VERSION` if commands return `SCMI_ERR_PROTOCOL`. + +--- + +## See also + +- @ref page_device_scmi_agent — mailbox, SMC, shmem layout +- @ref page_device_mailbox — doorbell for `arm,scmi` +- `components/drivers/include/drivers/scmi.h` +- `components/drivers/firmware/arm_scmi/agent.c`, `bus.c` diff --git a/documentation/6.components/device-driver/scsi/scsi.md b/documentation/6.components/device-driver/scsi/scsi.md new file mode 100755 index 00000000000..df4a920ac72 --- /dev/null +++ b/documentation/6.components/device-driver/scsi/scsi.md @@ -0,0 +1,221 @@ +@page page_device_scsi SCSI host and commands + +# SCSI subsystem + +RT-Thread provides a **small SCSI core** for storage backends: a **host** (`rt_scsi_host`) scans targets, issues commands through **`rt_scsi_ops::transfer`**, and attaches **type-specific upper drivers** (disk `sd*`, CD-ROM, …) that register **`rt_blk_disk`** devices. + +The core is **not** a device-model bus (see comment in `scsi.c`) — there is no `rt_scmi`-style match/probe bus. Host adapters call **`rt_scsi_host_register()`** after filling **`rt_scsi_host`**. + +| Piece | Path | +| --- | --- | +| API / CDB layouts | `components/drivers/include/drivers/scsi.h` | +| Core | `components/drivers/scsi/scsi.c` | +| Block disk (type 0) | `components/drivers/scsi/scsi_sd.c` | +| CD-ROM (type 5) | `components/drivers/scsi/scsi_cdrom.c` | + +**Kconfig**: `RT_USING_SCSI` (needs `RT_USING_DM`). Options `RT_SCSI_SD` / `RT_SCSI_CDROM` (need `RT_USING_BLK`). + +--- + +## End-to-end flow + +``` +Host adapter (AHCI / UFS / VirtIO-SCSI / …) + fills rt_scsi_host { dev, ops, max_id, max_lun, parallel_io } + calls rt_scsi_host_register() + ↓ +For each (id, lun): INQUIRY → devtype / removable + unknown type or inquiry fail → skip LUN + devtype >= SCSI_DEVICE_TYPE_MAX → shrink scan, stop + ↓ +scsi_device_setup(sdev): optional reset → TEST UNIT READY (5 s) → driver_table[devtype].probe + ↓ +scsi_sd_probe / scsi_cdrom_probe: READ CAPACITY → rt_hw_blk_disk_register ("sd*a", …) + ↓ +Applications use blk API (@ref page_device_blk, @ref page_device_disk) +``` + +**Unregister**: `rt_scsi_host_unregister()` walks **`lun_nodes`**, optional **`ops->reset`**, type **`remove`**, frees each **`rt_scsi_device`**. + +--- + +## Data structures (`scsi.h`) + +### Host and device + +```c +struct rt_scsi_host { + struct rt_device *dev; /* required; DMA / naming */ + const struct rt_scsi_ops *ops; + rt_size_t max_id; /* both must be non-zero */ + rt_size_t max_lun; + rt_bool_t parallel_io; /* hint for blk layer */ + rt_list_t lun_nodes; /* filled by register */ +}; + +struct rt_scsi_device { + struct rt_scsi_host *host; + rt_list_t list; + rt_size_t id; + rt_size_t lun; + rt_uint32_t devtype; /* from INQUIRY */ + rt_uint32_t removable; + rt_size_t last_block; /* filled by READ CAPACITY helpers */ + rt_size_t block_size; + void *priv; /* scsi_sd / scsi_cdrom */ +}; + +struct rt_scsi_ops { + rt_err_t (*reset)(struct rt_scsi_device *sdev); /* optional */ + rt_err_t (*transfer)(struct rt_scsi_device *sdev, struct rt_scsi_cmd *cmd); /* required */ +}; +``` + +### Command envelope + +```c +struct rt_scsi_cmd { + union { /* CDB: inquiry, read10, write16, … */ } op; + rt_size_t op_size; + + union { + struct { /* fixed response structs */ } ; + struct { void *ptr; rt_size_t size; } data; /* DMA buffer for read/write */ + } data; +}; +``` + +Build CDBs with packed structs in **`scsi.h`** (`rt_scsi_inquiry`, `rt_scsi_read10`, …) or fill **`cmd.op`** manually. Set **`op_size`** to the CDB length your transport accepts. + +High-level helpers in **`scsi.c`** (`rt_scsi_inquiry`, `rt_scsi_read10`, `rt_scsi_write16`, `rt_scsi_read_capacity10`, …) assemble **`rt_scsi_cmd`** and call **`host->ops->transfer`**. On failure they often issue **`rt_scsi_request_sense`**. + +**`rt_scsi_cmd_is_write(cmd)`** — used by VirtIO-SCSI to pick IN vs OUT virtqueue buffers. + +--- + +## `rt_scsi_host_register` / `unregister` + +### Before register (caller) + +| Field | Rule | +| --- | --- | +| `dev` | Non-NULL | +| `ops` | Non-NULL; **`transfer`** required | +| `max_id` / `max_lun` | Both non-zero; define scan grid | +| `parallel_io` | Set if adapter can pipeline multiple LUN commands | +| `lun_nodes` | Do not pre-fill; core initializes the list | + +### Scan behavior (`scsi.c`) + +1. Nested loop **`id ∈ [0, max_id)`**, **`lun ∈ [0, max_lun)`**. +2. **`rt_scsi_inquiry(&tmp_sdev, NULL)`** — failure skips the LUN. +3. If **`devtype >= SCSI_DEVICE_TYPE_MAX`**, scan **stops** and **`max_id` / `max_lun`** are clipped to the current index (simple bus assumption). +4. Allocated **`rt_scsi_device`** is copied from the temp device (id, lun, devtype, removable). +5. **`scsi_device_setup`**: optional **`reset`** → wait **TEST UNIT READY** (up to ~5 s) → **`driver_table[devtype].probe`**. + +Returns **`RT_EOK`** if at least one LUN probed; **`-RT_EEMPTY`** if none; **`-RT_EINVAL`** / **`-RT_ENOMEM`** on bad args or OOM (partial LUNs may remain on ENOMEM mid-scan). + +### `transfer` contract + +- Fill or consume **`cmd->data`** (`ptr` + `size` for READ/WRITE; embedded structs for INQUIRY / capacity). +- Return **`RT_EOK`** on command completion; RT error codes on transport failure. +- Sense: adapters may copy sense into **`cmd->data.request_sense`**; helpers already call **REQUEST SENSE** after some failures. + +--- + +## Upper-layer drivers (`driver_table`) + +| `devtype` (`SCSI_DEVICE_TYPE_*`) | Kconfig | Probe | Block device | +| --- | --- | --- | --- | +| `0x00` DIRECT | `RT_SCSI_SD` | `scsi_sd_probe` | `sd*a` — read/write/sync/erase | +| `0x05` CDROM | `RT_SCSI_CDROM` | `scsi_cdrom_probe` | `sr*a` — read-only | + +Other inquiry types log **not supported** and are skipped. + +**`scsi_sd_probe`**: READ CAPACITY 10; if last LBA is `0xffffffff`, READ CAPACITY 16 and use READ/WRITE **16** ops; sets geometry from **`sdev->block_size`** / **`last_block`**; **`rt_hw_blk_disk_register`**. Honors **`host->parallel_io`** on the disk. + +**`scsi_cdrom_probe`**: capacity + read-only **`rt_blk_disk`** (uses READ 10/12/16 by LBA width). + +--- + +## Built-in SCSI hosts (examples) + +These embed **`struct rt_scsi_host parent`** and call **`rt_scsi_host_register`** after hardware init: + +| Backend | Source | Notes | +| --- | --- | --- | +| **AHCI** | `ata/ahci.c` | SATA/ATAPI → `ahci_scsi_ops.transfer`; see @ref page_device_ata | +| **UFS** | `ufs/ufs.c` | UTP carries SCSI CDBs; `ufs_host_ops`, often `parallel_io = RT_TRUE` | +| **VirtIO SCSI** | `virtio/virtio-scsi.c` | `RT_VIRTIO_DRIVER_EXPORT`; 3 queues; hotplug via `config_changed` + work queue re-register | + +Typical host setup (pattern): + +```c +struct my_hba { + struct rt_scsi_host parent; + /* private */ +}; + +static rt_err_t my_scsi_transfer(struct rt_scsi_device *sdev, struct rt_scsi_cmd *cmd) +{ + /* map id/lun, push CDB + data to hardware */ + return RT_EOK; +} + +static const struct rt_scsi_ops my_scsi_ops = { + .reset = my_scsi_reset, /* optional */ + .transfer = my_scsi_transfer, +}; + +/* after controller ready */ +hba->parent.dev = &controller_dev; +hba->parent.ops = &my_scsi_ops; +hba->parent.max_id = 1; +hba->parent.max_lun = 8; +hba->parent.parallel_io = RT_FALSE; +rt_scsi_host_register(&hba->parent); +``` + +Teardown: **`rt_scsi_host_unregister(&hba->parent)`** before freeing the controller. + +--- + +## Command reference (`scsi.h`) + +Opcodes and structs are defined for common storage commands: + +| Opcode macro | Struct | Helper | +| --- | --- | --- | +| `RT_SCSI_CMD_INQUIRY` | `rt_scsi_inquiry` | `rt_scsi_inquiry()` | +| `RT_SCSI_CMD_TEST_UNIT_READY` | `rt_scsi_test_unit_ready` | `rt_scsi_test_unit_ready()` | +| `RT_SCSI_CMD_READ_CAPACITY10/16` | capacity structs | `rt_scsi_read_capacity10/16()` | +| `RT_SCSI_CMD_READ10/12/16` | read structs | `rt_scsi_read10/12/16()` | +| `RT_SCSI_CMD_WRITE10/12/16` | write structs | `rt_scsi_write10/12/16()` | +| `RT_SCSI_CMD_SYNCHRONIZE_CACHE10/16` | sync cache | `rt_scsi_synchronize_cache10/16()` | +| `RT_SCSI_CMD_WRITE_SAME10/16` | write same | `rt_scsi_write_same10/16()` (erase path in `scsi_sd`) | +| Mode select/sense | mode structs | `rt_scsi_mode_*` (auto-refresh in `scsi_sd`) | + +Use these when implementing **`transfer`** for a new HBA or when extending **`driver_table`** for another device type. + +--- + +## Debugging + +| Symptom | Checks | +| --- | --- | +| `-RT_EEMPTY` | No LUN answered INQUIRY — wiring, power, `max_id`/`max_lun`, firmware | +| `-RT_ETIMEOUT` in setup | Media not ready — TEST UNIT READY loop | +| `-RT_ENOSYS` | Unsupported `devtype` — enable `RT_SCSI_SD` / `RT_SCSI_CDROM` or add `driver_table` entry | +| READ/WRITE fail | Sense via `rt_scsi_request_sense`; verify `cmd->data.size` is in **sectors** for READ/WRITE helpers | +| VirtIO hotplug | `virtio_scsi_config_changed` → rescan; log `virtio.dev.scsi` | + +--- + +## See also + +- @ref page_device_ata — AHCI port → SCSI → `sd*` +- @ref page_device_blk — block layer API +- @ref page_device_disk — `rt_blk_disk` registration +- `components/drivers/include/drivers/scsi.h` +- `components/drivers/scsi/scsi.c` +- `components/drivers/virtio/virtio-scsi.c` diff --git a/documentation/6.components/device-driver/sdio/dm.md b/documentation/6.components/device-driver/sdio/dm.md new file mode 100755 index 00000000000..6003cf0150f --- /dev/null +++ b/documentation/6.components/device-driver/sdio/dm.md @@ -0,0 +1,67 @@ +@page page_device_sdio_dm SDIO device model + +# SDIO / MMC device model (DM) + +The **MMC/SD core** (`dev_mmcsd_core.c`, …) builds with **`RT_USING_SDIO`** alone. **DM helpers** are an extra layer: only when **`RT_USING_DM`** is on does **`SConscript`** add **`dev_sdio_dm.c`** (see **`components/drivers/sdio/SConscript`**). APIs live in **`dev_sdio_dm.h`**. + +| Feature | Kconfig (typical) | Source (only with **`RT_USING_DM`**) | +| --- | --- | --- | +| Host naming, OFW parse | **`RT_USING_DM`** + **`RT_USING_OFW`** for parse | `dev_sdio_dm.c` | +| SDHCI stack | **`RT_USING_DM`** + **`RT_USING_SDIO`** + **`RT_USING_SDHCI`** | `dev_sdhci.c`, `dev_sdhci_dm.c`, `dev_sdhci_host.c` | +| **`vmmc` / `vqmmc`** | **`RT_USING_DM`** + **`RT_USING_REGULATOR`** | `dev_regulator.c` | +| SoC SDHCI / DW MMC hosts | **`RT_USING_DM`** + **`RT_USING_SDIO`** | `sdio/host/*` (see **`host/Kconfig`**) | + +Without **`RT_USING_DM`**, implement **`struct rt_mmcsd_host_ops`** yourself and use @ref page_device_sdio; there is **no** in-tree **`dev_sdhci_*`** or **`sdio_regulator_*`** build. + +## Driver / BSP quick notes + +| Topic | Guidance | +| --- | --- | +| **Parse before card activity** | Call **`sdio_ofw_parse`** (when **`RT_USING_OFW`**) while filling **`struct rt_mmcsd_host`** and **before** the core starts enumeration—invalid **`bus-width`** yields **`-RT_EIO`**; handle in **`probe`** instead of at first transaction. | +| **Stable host names** | Prefer DTS **`aliases { mmcN = &...; }`** so **`sdio_host_set_name`** matches board wiring and userland scripts; purely software hosts still rely on **`rt_ofw_get_alias_last_id("mmc")`** to avoid colliding with aliased indices. | +| **Supplies** | **`vmmc` / `vqmmc`** need DM + regulator—see @ref page_device_sdio_regulator and @ref page_device_regulator. | +| **SDHCI (DM only)** | **`RT_USING_SDHCI`** is implemented only with DM. Platform drivers use **`dev_sdhci_dm.c`** (**`rt_dm_dev_iomap`**, **`rt_dm_dev_get_irq`**, **`rt_sdhci_get_property`**)—see @ref page_device_sdhci. | + +## Host naming: `sdio_host_set_name` + +**`sdio_host_set_name(host, out_devname)`** fills **`host->name`** with a stable **`sd%u`** string. + +- If **`RT_USING_OFW`** and **`host->ofw_node`** are set, it first tries **`rt_ofw_get_alias_id(host->ofw_node, "mmc")`** so names align with **`aliases { mmc0 = &...; }`** in DTS. +- Otherwise it allocates a monotonic numeric id (atomically, seeded from **`rt_ofw_get_alias_last_id("mmc")`** when OFW is enabled so software-only hosts do not collide with aliased indices). + +Optional **`out_devname`** receives a copy of the same string (up to **`RT_NAME_MAX`**). + +## Open Firmware: `sdio_ofw_parse` + +When **`RT_USING_OFW`** is enabled, **`sdio_ofw_parse(dev_np, host)`** parses common MMC controller properties from **`dev_np`** into **`struct rt_mmcsd_host`**: + +| Property | Effect | +| --- | --- | +| **`bus-width`** | **`1` / `4` / `8`** → sets **`MMCSD_BUSWIDTH_*`** in **`host->flags`**; invalid values return **`-RT_EIO`**. | +| **`max-frequency`** | **`host->freq_max`**. | +| **`non-removable`** | **`MMCSD_SUP_NONREMOVABLE`**. | +| **`cap-sdio-irq`** | **`MMCSD_SUP_SDIO_IRQ`**. | +| **`cap-sd-highspeed`** / **`cap-mmc-highspeed`** | **`MMCSD_SUP_HIGHSPEED`**. | +| **`mmc-ddr-*`**, **`mmc-hs200-*`**, **`mmc-hs400-*`** | DDR / HS200 / HS400 capability flags. | +| **`sd-uhs-*`** | UHS SDR/DDR capability flags. | + +It also assigns **`host->ofw_node`** if it was not already set (**`host->ofw_node ? : dev_np`**), and forces **`MMCSD_MUTBLKWRITE`** in **`host->flags`**. + +Without **`RT_USING_OFW`**, **`sdio_ofw_parse`** is a stub that returns **`RT_EOK`** and does nothing. + +## Regulator hooks (DM + regulator) + +**`dev_regulator.c`** is compiled only when **`RT_USING_DM`** and **`RT_USING_REGULATOR`** are both set (regulator framework itself depends on DM). See @ref page_device_sdio_regulator for **`vmmc` / `vqmmc`**. + +## SDHCI (DM + SDHCI) + +**`RT_USING_SDHCI`** pulls in **`dev_sdhci.c`** only under **`RT_USING_DM`**. Platform probe uses **`dev_sdhci_dm.c`** (**`rt_sdhci_pltfm_init`**, **`rt_dm_dev_iomap`**, **`rt_dm_dev_get_irq`**, **`rt_sdhci_get_property`**). Details: @ref page_device_sdhci. + +## See also + +- `components/drivers/sdio/dev_sdio_dm.c`, `dev_sdio_dm.h` +- `components/drivers/sdio/SConscript` +- Core bus / driver binding: `documentation/6.components/device-driver/core/bus.md` +- OFW runtime API: `documentation/6.components/device-driver/ofw/ofw.md` +- @ref page_device_sdio_regulator +- @ref page_device_sdhci diff --git a/documentation/6.components/device-driver/sdio/regulator.md b/documentation/6.components/device-driver/sdio/regulator.md new file mode 100755 index 00000000000..0c704c66ad6 --- /dev/null +++ b/documentation/6.components/device-driver/sdio/regulator.md @@ -0,0 +1,44 @@ +@page page_device_sdio_regulator SDIO supplies (regulator) + +# SDIO / MMC and regulators + +**Requires device model.** **`dev_regulator.c`** is added in **`components/drivers/sdio/SConscript`** only when **`RT_USING_DM`** and **`RT_USING_REGULATOR`** are both enabled (the regulator subsystem also depends on **`RT_USING_DM`**). You still need **`RT_USING_SDIO`** for the MMC stack. + +This file links **`struct rt_mmcsd_host`** to optional **`vmmc`** (card VDD) and **`vqmmc`** (I/O / VCCQ) through **`struct rt_mmcsd_supply`** in **`drivers/mmcsd_host.h`**. Public helpers are declared in **`dev_sdio_dm.h`** (under **`#ifdef RT_USING_REGULATOR`**). + +Without **`RT_USING_DM`**, there is no **`sdio_regulator_*`** implementation in-tree—handle power in your host driver or board init. + +## Supply handles + +| Field | Role | +| --- | --- | +| **`host->supply.vmmc`** | Card power rail from **`rt_regulator_get(dev, "vmmc")`**. | +| **`host->supply.vqmmc`** | I/O rail from **`rt_regulator_get(dev, "vqmmc")`**. | + +Missing supplies are allowed (**`RT_NULL`**); hard errors from **`rt_regulator_get`** are propagated and any acquired handles are **`rt_regulator_put`** on failure paths in **`sdio_regulator_get_supply`**. + +## API summary + +| Function | Role | +| --- | --- | +| **`sdio_regulator_get_supply(dev, host)`** | Resolve **`vmmc`** / **`vqmmc`** for the DM **`rt_device`** backing the host. Call once from probe after **`host`** exists. | +| **`sdio_regulator_set_ocr(host, supply, vdd_bit)`** | Drives **`vmmc`** from the card OCR **vdd** bit: **`rt_regulator_set_voltage`** after **`ocrbitnum_to_vdd`**; enable/disable paths use **`host->supply.regulator_enabled`** (see **`components/drivers/sdio/dev_regulator.c`**). **`supply`** may be **`RT_NULL`** (no-op). | +| **`sdio_regulator_set_vqmmc(host, ios)`** | Adjusts **`vqmmc`** voltage for **`ios->signal_voltage`** (**`MMCSD_SIGNAL_VOLTAGE_330` / `_180` / `_120`**), using **`rt_regulator_is_supported_voltage`**, **`rt_regulator_get_voltage`**, and **`rt_regulator_set_voltage_triplet`**. For **3.3 V**, the target may be derived from **`host->io_cfg.vdd`**. Returns **`-RT_EINVAL`** if **`vqmmc`** is missing. | +| **`sdio_regulator_enable_vqmmc` / `sdio_regulator_disable_vqmmc`** | Enable or disable **`vqmmc`** and track **`vqmmc_enabled`**. | + +**`host->supply.regulator_enabled`** tracks whether **`vmmc`** was enabled through the OCR path; **`vqmmc_enabled`** tracks the I/O rail. + +SDHCI and DesignWare MMC hosts in-tree call these helpers during power and UHS voltage switch when **`RT_USING_REGULATOR`** is enabled (those host drivers also require DM). + +## Device tree + +Use the same supply names your **`rt_regulator_get`** path expects (**`vmmc`**, **`vqmmc`**) so phandles resolve to the correct **`struct rt_regulator`**. The generic regulator subsystem is described in @ref page_device_regulator. + +## See also + +- `components/drivers/sdio/dev_regulator.c`, `dev_sdio_dm.h` +- `components/drivers/sdio/SConscript` +- `components/drivers/include/drivers/mmcsd_host.h` (`struct rt_mmcsd_supply`, **`RT_USING_REGULATOR`**) +- @ref page_device_regulator +- @ref page_device_sdio_dm +- @ref page_device_sdhci diff --git a/documentation/6.components/device-driver/sdio/sdhci.md b/documentation/6.components/device-driver/sdio/sdhci.md new file mode 100644 index 00000000000..ddd7526c28f --- /dev/null +++ b/documentation/6.components/device-driver/sdio/sdhci.md @@ -0,0 +1,95 @@ +@page page_device_sdhci SDHCI host controller + +# SDHCI (SD Host Controller Interface) + +**Requires device model.** In **`components/drivers/sdio/SConscript`**, **`dev_sdhci.c`**, **`dev_sdhci_dm.c`**, and **`dev_sdhci_host.c`** are built only when **`RT_USING_DM`**, **`RT_USING_SDIO`**, and **`RT_USING_SDHCI`** are all enabled. Kconfig may list **`RT_USING_SDHCI`** under SDIO alone, but the SDHCI sources in this tree **do not compile without DM**. + +**`dev_sdhci.c`** implements **SDHCI 2.x/3.x/4.x** on the shared MMC/SD stack. Types and register offsets: **`drivers/dev_sdhci.h`**; **`struct rt_mmc_host`** bridge: **`drivers/dev_sdhci_host.h`**. + +| Path | DM needed? | Notes | +| --- | --- | --- | +| Platform **`rt_sdhci_pltfm_*`** | Yes | **`dev_sdhci_dm.c`**: **`rt_dm_dev_iomap`**, **`rt_dm_dev_get_irq`**, DT quirks | +| PCI **`sdhci-pci.c`** | Yes | **`host/Kconfig`** is under **`RT_USING_DM && RT_USING_SDIO`** | +| Custom host without SDHCI | No (core only) | **`rt_mmcsd_host_ops`** per @ref page_device_sdio | + +**`vmmc` / `vqmmc`**: power/UHS paths call **`sdio_regulator_*`** when **`RT_USING_REGULATOR`** is on—see @ref page_device_sdio_regulator. + +## Driver / BSP quick notes + +| Topic | Guidance | +| --- | --- | +| **`quirks` / `quirks2`** | Prefer DT-driven bits from **`rt_sdhci_get_property`** / **`rt_sdhci_get_of_property`**, then add SoC-only flags—wrong **ADMA** or **DMA width** quirks often show up only under load as CMD / data errors. | +| **Order of init** | With DM, **`rt_dm_dev_iomap` / `rt_dm_dev_get_irq`** (or PCI BAR/IRQ) must be valid **before** **`rt_sdhci_setup_host`** reads capabilities and selects SDMA/ADMA/PIO. | +| **Probe failure / remove** | Mirror **`rt_sdhci_init_host`** with **`rt_sdhci_uninit_host`** and **`rt_sdhci_free_host`** on error and on **`remove`**—leaving a registered IRQ or workqueue causes use-after-free on unplug. | +| **UHS / 1.8 V** | **`set_uhs_signaling`**, **`voltage_switch`**, and tuning paths are board-sensitive; with regulator support, **`sdio_regulator_set_vqmmc`** must succeed. | +| **Card detect** | DT **`broken-cd`**, **`non-removable`**, and **`cap-*`** must match the socket—wrong CD polarity or missing **`non-removable`** generates spurious detect storms or no card at all. | +| **OFW** | Property names and capability flags: @ref page_device_sdio_dm. | + +## Layering + +| Piece | Role | +| --- | --- | +| **`struct rt_sdhci_host`** | Hardware state: **`ioaddr`**, **`irq`**, **`quirks`/`quirks2`**, **`ops`**, DMA flags, linked **`struct rt_mmc_host *mmc`**. | +| **`struct rt_mmc_host`** | Core MMC/SD/SDIO view; embedded **`rthost`** (**`struct rt_mmcsd_host`**) is what **`mmcsd_core`** uses. | +| **`struct rt_sdhci_ops`** | Optional MMIO overrides and SoC hooks (clock, bus width, reset, UHS, tuning, …). | + +Default MMIO access uses **`rt_sdhci_readl` / `writel`** (and 8/16-bit variants) when **`ops->read_*`/`write_*`** are not set. + +## Bring-up sequence (driver writer) + +Typical order matches **`rt_sdhci_set_and_add_host`** in **`dev_sdhci.c`**: + +1. **`rt_sdhci_alloc_host(dev, priv_size)`** — allocates **`rt_sdhci_host`** plus tail **`private`** bytes; wires **`mmc`** and default **`mmc_host_ops`**. +2. Fill **`host->ioaddr`**, **`host->irq`**, **`host->hw_name`**, optional **`host->ops`**, **`quirks`**, **`dma_mask`**, etc. (platform: via **`rt_sdhci_pltfm_init`**). +3. **`rt_sdhci_setup_host`** — read HCI capabilities, OCR masks, choose SDMA/ADMA/PIO, host limits. +4. **`rt_sdhci_init_host`** — timers, workqueues, IRQ **`rt_hw_interrupt_install`**, **`rt_mmc_add_host`**, card-detect enable. **`rt_sdhci_set_and_add_host`** wraps **`setup_host` + `init_host`**. + +Teardown: **`rt_sdhci_uninit_host`**, **`rt_sdhci_free_host`**. + +## `struct rt_sdhci_ops` (selected callbacks) + +| Callback | Typical use | +| --- | --- | +| **`set_clock` / `set_bus_width` / `reset` / `set_uhs_signaling`** | Platform defaults in **`dev_sdhci_dm.c`** delegate to **`rt_sdhci_set_clock`**, **`rt_sdhci_set_bus_width`**, **`rt_sdhci_reset`**, **`rt_sdhci_set_uhs`**. | +| **`read_*` / `write_*`** | Non-memory-mapped HCI (sparse IO) or endian fixups. | +| **`irq`** | Vendor-specific interrupt demux before generic SDHCI handling. | +| **`set_power`**, **`voltage_switch`**, **`hw_reset`**, **`card_event`** | Power / card-detect / eMMC HW reset integration. | +| **`platform_execute_tuning`** | SoC-specific tuning when the generic CMD19 path is not enough. | + +Full list: **`drivers/dev_sdhci.h`**. + +## Quirks + +**`RT_SDHCI_QUIRK_*`** and **`RT_SDHCI_QUIRK2_*`** in **`dev_sdhci.h`** document controller-specific workarounds (DMA width, ADMA, card-detect, HS200/DDR50, CMD23, preset values, …). Set them from **`struct rt_sdhci_pltfm_data`** or after **`rt_sdhci_get_property`** merges DT-driven bits. + +## Platform DM (`dev_sdhci_dm.c` / `dev_sdhci_dm.h`) + +Only with **`RT_USING_DM`**. **`rt_platform_device`** probe should use these helpers instead of duplicating MMIO/IRQ mapping. + +| API | Role | +| --- | --- | +| **`rt_sdhci_pltfm_init(pdev, pdata, priv_size)`** | **`rt_dm_dev_iomap(dev, 0)`**, **`rt_dm_dev_get_irq(dev, 0)`**, **`rt_sdhci_alloc_host`**, assign **`sdhci_pltfm_ops`** or **`pdata->ops`**, copy **`pdata->quirks`**. | +| **`rt_sdhci_get_property` / `rt_sdhci_get_of_property`** | Reads **`sdhci,auto-cmd12`**, **`sdhci,1-bit-only`**, **`bus-width`**, **`broken-cd`**, **`no-1-8-v`**, **`clock-frequency`**, **`keep-power-in-suspend`**, **`wakeup-source`** / **`enable-sdio-wakeup`**, and sets **`quirks`**, **`quirks2`**, **`pm_caps`**. | +| **`rt_sdhci_pltfm_init_and_add_host`** | **`pltfm_init`** → **`get_property`** → **`rt_sdhci_init_host`**. | +| **`rt_sdhci_pltfm_remove` / `rt_sdhci_pltfm_free`** | **`rt_sdhci_uninit_host`** and **`rt_sdhci_free_host`**. | +| **`rt_sdhci_pltfm_clk_get_max_clock`** | **`rt_clk_get_rate`** on the platform **`clk`**. | + +**`struct rt_sdhci_pltfm_data`** carries **`ops`**, **`quirks`**, **`quirks2`**. **`struct rt_sdhci_pltfm_host`** holds **`clk`**, **`clock`**, **`xfer_mode_shadow`**, and trailing private data (**`rt_sdhci_pltfm_priv`**). + +## PCI glue + +**`components/drivers/sdio/host/sdhci-pci.c`** (Kconfig: **`RT_SDIO_SDHCI_PCI`**, requires **`RT_USING_DM`** and **`RT_USING_SDIO`**) allocates the host with **`rt_sdhci_alloc_host`**, maps BAR0 via **`rt_pci_iomap`**, uses **`pdev->irq`**, **`rt_pci_irq_unmask`**, **`rt_pci_set_master`**, then **`rt_sdhci_set_and_add_host`**. + +## Registers and spec + +**`drivers/dev_sdhci.h`** defines **`RT_SDHCI_*`** MMIO offsets and capability bits aligned with the **SDHCI** and **SD Physical Layer** documents. For behaviour beyond this tree, refer to the **SD Host Controller Simplified Specification** (SD Association). + +## See also + +- `components/drivers/sdio/dev_sdhci.c`, `dev_sdhci_host.c`, `dev_sdhci_dm.c`, `host/sdhci-pci.c` +- `components/drivers/sdio/SConscript` +- `components/drivers/include/drivers/dev_sdhci.h`, `dev_sdhci_host.h`, `sdio/dev_sdhci_dm.h` +- @ref page_device_sdio +- @ref page_device_sdio_dm +- @ref page_device_sdio_regulator +- @ref page_device_pci diff --git a/documentation/6.components/device-driver/sdio/sdio.md b/documentation/6.components/device-driver/sdio/sdio.md new file mode 100644 index 00000000000..844fefe96b5 --- /dev/null +++ b/documentation/6.components/device-driver/sdio/sdio.md @@ -0,0 +1,84 @@ +@page page_device_sdio SDIO / MMC core + +# SDIO / MMC core (API) + +**`RT_USING_SDIO`** enables the shared MMC/SD/SDIO core (`dev_mmcsd_core.c`, `dev_sd.c`, `dev_sdio.c`, …). It does **not** require **`RT_USING_DM`**. + +| Topic | Page | Needs DM? | +| --- | --- | --- | +| OFW parse, **`sd%u`** naming | @ref page_device_sdio_dm | Yes | +| **`vmmc` / `vqmmc`** | @ref page_device_sdio_regulator | Yes (+ **`RT_USING_REGULATOR`**) | +| SDHCI controller | @ref page_device_sdhci | Yes (+ **`RT_USING_SDHCI`**) | + +Headers: `drivers/dev_mmcsd_core.h`, `drivers/mmcsd_host.h`, `drivers/mmcsd_card.h`, `drivers/mmcsd_cmd.h`. Symbols below are in **lexicographic order** (only representative macros where there are many; see headers for full lists). + +## `mmcsd_*` functions (A–Z) + +| Symbol | Description | +| --- | --- | +| `mmcsd_alloc_host` | Allocate `rt_mmcsd_host`. | +| `mmcsd_change` | Host/card state change entry (after identification and negotiation). | +| `mmcsd_detect` | Card-detect worker / callback entry. | +| `mmcsd_deselect_cards` | Deselect cards. | +| `mmcsd_excute_tuning` | Run tuning (spelling matches header). | +| `mmcsd_free_host` | Free host. | +| `mmcsd_get_cid` / `mmcsd_get_csd` | Read CID/CSD. | +| `mmcsd_go_idle` | GO_IDLE_STATE. | +| `mmcsd_host_init` | Initialize host software state. | +| `mmcsd_host_lock` / `mmcsd_host_unlock` | Host bus lock. | +| `mmcsd_host_set_uhs_voltage` | Host-side UHS voltage switch. | +| `mmcsd_req_complete` | Called by low level when one `rt_mmcsd_req` completes. | +| `mmcsd_select_card` | Select card. | +| `mmcsd_select_voltage` | Choose operating voltage from OCR. | +| `mmcsd_send_abort_tuning` | Abort tuning sequence. | +| `mmcsd_send_cmd` | Send one command (with retries). | +| `mmcsd_send_request` | Submit full `rt_mmcsd_req`. | +| `mmcsd_send_tuning` | Send tuning-related command. | +| `mmcsd_set_bus_mode` / `mmcsd_set_bus_width` / `mmcsd_set_clock` / `mmcsd_set_timing` | Update host `io_cfg` and usually call `set_iocfg`. | +| `mmcsd_set_chip_select` | SPI chip select mode. | +| `mmcsd_set_data_timeout` | Set data timeout from card capability. | +| `mmcsd_set_initial_signal_voltage` | Initial signal voltage. | +| `mmcsd_set_signal_voltage` | Switch signal voltage. | +| `mmcsd_set_uhs_voltage` | UHS voltage path. | +| `mmcsd_spi_read_ocr` / `mmcsd_spi_use_crc` | SPI OCR/CRC. | +| `mmcsd_wait_cd_changed` | Wait for card-detect change. | + +## `rt_mmcsd_*` functions (A–Z) + +| Symbol | Description | +| --- | --- | +| `rt_mmcsd_blk_probe` / `rt_mmcsd_blk_remove` | Bind/unbind block device. | +| `rt_mmcsd_core_init` | Initialize MMC core. | + +## `struct rt_mmcsd_host_ops` callbacks (A–Z) + +| Symbol | Description | +| --- | --- | +| `card_busy` | Query card busy. | +| `enable_sdio_irq` | SDIO IRQ enable/disable. | +| `execute_tuning` | Sampling tuning. | +| `get_card_status` | Read card status. | +| `request` | Submit `rt_mmcsd_req`. | +| `set_iocfg` | Apply `rt_mmcsd_io_cfg` (clock, width, timing, voltage, …). | +| `signal_voltage_switch` | Signal voltage switch. | + +## Main structs (A–Z) + +| Symbol | Notes | +| --- | --- | +| `struct rt_mmcsd_cmd` | `cmd_code`, `arg`, `resp`, `flags`, `data`, `retries`, `err`, … | +| `struct rt_mmcsd_data` | `blksize`, `blks`, `buf`, `flags`, timeout, scatter-gather fields. | +| `struct rt_mmcsd_host` | `ops`, `io_cfg`, `freq_*`, `flags`, `card`, mutex/semaphore; with **`RT_USING_REGULATOR`**: optional `supply`; with OFW: `ofw_node`. | +| `struct rt_mmcsd_io_cfg` | `clock`, `vdd`, `bus_mode`, `bus_width`, `timing`, `signal_voltage`, … | +| `struct rt_mmcsd_req` | `cmd`, `data`, `stop`, `sbc`, `cap_cmd_during_tfr`. | + +## See also + +- @ref page_device_sdio_dm — OFW / host naming (DM) +- @ref page_device_sdio_regulator — supplies (DM + regulator) +- @ref page_device_sdhci — SDHCI (DM + SDHCI) +- `components/drivers/sdio/SConscript` +- `components/drivers/include/drivers/dev_mmcsd_core.h` +- `components/drivers/include/drivers/mmcsd_host.h` +- `components/drivers/include/drivers/mmcsd_card.h` +- `components/drivers/include/drivers/mmcsd_cmd.h` diff --git a/documentation/6.components/device-driver/spi/dm.md b/documentation/6.components/device-driver/spi/dm.md new file mode 100755 index 00000000000..ce2f1bc7690 --- /dev/null +++ b/documentation/6.components/device-driver/spi/dm.md @@ -0,0 +1,286 @@ +@page page_device_spi_dm SPI device model (DM) + +# SPI bus framework under `RT_USING_DM` + +When **`RT_USING_DM`** and **`RT_USING_SPI`** are enabled, RT-Thread adds an **SPI-specific `rt_bus`** (`name = "spi"`) on top of the core DM layer (@ref page_device_bus). **Bus controllers** register as **`RT_Device_Class_SPIBUS`** devices (`spi0`, `spi1`, …); **slave drivers** bind as **`struct rt_spi_device`** instances discovered from device-tree children. + +Legacy **manual** attach (`rt_spi_bus_attach_device`, `rt_hw_spi_device_attach`, GPIO **`user_data`**) remains valid — see @ref page_device_spi. This page covers **DT scan**, **`spi_device_ofw_parse`**, and **`RT_SPI_DRIVER_EXPORT`**. + +Sources: + +| File | Role | +| --- | --- | +| **`dev_spi_bus.c`** | **`struct rt_bus spi_bus`**, match/probe, **`spi_bus_scan_devices`** | +| **`dev_spi_dm.c`** | **`spi_device_ofw_parse`** | +| **`dev_spi_core.c`** | **`spi_bus_register`** → scan + CS GPIO from DT | +| **`dev_spi.h`** | **`rt_spi_driver`**, **`RT_SPI_DRIVER_EXPORT`** | +| **`dev_spi_dm.h`** | Parse / scan declarations | + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_SPI`** | Core (`dev_spi_core.c`, `dev_spi.c`, …) | +| **`RT_USING_DM`** | Adds **`dev_spi_bus.c`** + **`dev_spi_dm.c`** | +| **`RT_USING_OFW`** | Required for **`spi_bus_scan_devices`** and **`spi_device_ofw_parse`** | +| **`RT_USING_QSPI`** | QSPI bus mode **`RT_SPI_BUS_MODE_QSPI`**, alloc **`rt_qspi_device`** in scan | +| **`SOC_DM_SPI_DIR`** | BSP SoC SPI/QSPI platform drivers | + +Without **`RT_USING_DM`**, **`struct rt_spi_device`** has no **`name` / `id` / `ofw_id` / `chip_select[]`** — use manual attach only. + +--- + +## Architecture + +``` + Platform SPI/QSPI controller + | + | probe: iomap, clk, pinctrl; bus->parent.ofw_node = controller np + | rt_spi_bus_register / rt_qspi_bus_register + v + rt_device "spiN" (RT_Device_Class_SPIBUS) + | + | spi_bus_scan_devices(bus) /* dev_spi_core.c, after CS pins */ + v + For each DT child with "compatible": + alloc rt_spi_device or rt_qspi_device + spi_device_ofw_parse → config, reg → chip_select[] + rt_spi_device_register → rt_bus_add_device("spi", ...) + v + spi_match: driver ids[].name OR ofw_ids compatible + | + v + spi_probe → driver->probe(device) + | map cs_pin from bus->cs_pins[chip_select[0]] + | if parent.type == Unknown → rt_spidev_device_init (user-visible dev) + v + App: rt_spi_transfer* on device, or rt_device_find for spidev-style node +``` + +**Two layers**: + +| Layer | Object | Example | +| --- | --- | --- | +| **SPI bus** | **`struct rt_spi_bus`** | Hardware adapter; **`ops->configure`**, **`ops->xfer`**, mutex **`lock`** | +| **DM SPI bus** | **`struct rt_bus` `spi_bus`** | Binds **`rt_spi_driver`** ↔ **`rt_spi_device`** | + +The **platform** bus probes the **controller**; subsystem **`spi`** bus probes **children** under that controller’s OFW node. + +--- + +## `struct rt_bus spi` (`dev_spi_bus.c`) + +**`INIT_CORE_EXPORT(spi_bus_init)`** → **`rt_bus_register(&spi_bus)`**. + +| Callback | Behavior | +| --- | --- | +| **`spi_match`** | 1) **`driver->ids[].name`** vs **`device->name`** (DT node name). 2) Else **`rt_ofw_node_match(ofw_node, driver->ofw_ids)`**. | +| **`spi_probe`** | Requires **`device->bus`**; **`driver->probe(device)`**; then **`cs_pin = bus->cs_pins[chip_select[0]]`** if controller listed **`cs` GPIOs**; if **`parent.type == RT_Device_Class_Unknown`**, **`rt_spidev_device_init`** registers a **`RT_Device_Class_SPIDevice`** for **`rt_device_find`**. | +| **`spi_remove` / `spi_shutdown`** | Driver hook, **`rt_free(device)`**. | + +**Driver registration**: + +```c +rt_err_t rt_spi_driver_register(struct rt_spi_driver *driver); +/* RT_SPI_DRIVER_EXPORT(drv) -> RT_DRIVER_EXPORT(drv, spi, BUILIN) */ +``` + +**Client registration**: + +```c +rt_err_t rt_spi_device_register(struct rt_spi_device *device); +/* rt_bus_add_device(&spi_bus, &device->parent) */ +``` + +--- + +## Device tree + +### Controller + +```dts +spi1: spi@ff640000 { + compatible = "rockchip,rk3588-spi", "rockchip,rk3066-spi"; + reg = <0x0 0xff640000 0x0 0x1000>; + interrupts = ; + clocks = <&cru CLK_SPI1>, <&cru PCLK_SPI1>; + clock-names = "spiclk", "apb_pclk"; + #address-cells = <1>; + #size-cells = <0>; + cs-gpios = <&gpio3 RK_PB3 GPIO_ACTIVE_LOW>; + + flash@0 { + compatible = "jedec,spi-nor"; + reg = <0>; + spi-max-frequency = <50000000>; + }; +}; +``` + +| Property (controller) | Role | +| --- | --- | +| **`#address-cells` / `#size-cells`** | Standard SPI child addressing | +| **`cs-gpios`** | Optional; parsed via **`rt_pin_get_named_pin(&bus->parent, "cs", i, …)`** into **`bus->cs_pins[]`** | +| **`num-cs`** (driver-specific) | Some BSPs set **`bus->num_chipselect`** | + +### Child (slave) + +| Property | Role | +| --- | --- | +| **`compatible`** | Required for scan (children without it are skipped) | +| **`reg`** | Chip select index(es) → **`chip_select[]`** (up to **`RT_SPI_CS_CNT_MAX`**) | +| **`spi-max-frequency`** | **`config.max_hz`** | +| **`spi-cpol`**, **`spi-cpha`**, **`spi-cs-high`**, **`spi-lsb-first`**, **`spi-3wire`** | **`config.mode`** bits | +| **`spi-tx-bus-width`**, **`spi-rx-bus-width`** | Dual/quad line width hints (DM **`data_width_tx/rx`**) | +| **`spi-cs-setup-delay-ns`**, **`spi-cs-hold-delay-ns`**, **`spi-cs-inactive-delay-ns`** | **`rt_spi_delay`** on device | + +**`spi_device_ofw_parse`** (`dev_spi_dm.c`) fills **`struct rt_spi_configuration`** and CS indices before **`rt_spi_device_register`**. + +--- + +## `spi_bus_scan_devices` + +Called from **`spi_bus_register`** when **`RT_USING_DM`** and **`bus->parent.ofw_node`** is set. + +For each **available** child with **`compatible`**: + +1. Allocate **`struct rt_spi_device`** or **`struct rt_qspi_device`** from **`bus->mode`**. +2. **`spi_dev->bus = bus`**, **`name = rt_ofw_node_name`**, **`parent.ofw_node = child`**. +3. **`rt_dm_dev_set_name(..., rt_ofw_node_full_name)`** (full path string). +4. **`spi_device_ofw_parse(spi_dev)`** — on error, skip device. +5. **`rt_spi_device_register(spi_dev)`** → match + probe. + +**Order**: Controller **`probe`** should run before client drivers need the bus; **`RT_SPI_DRIVER_EXPORT`** drivers link at **`INIT_DEVICE_EXPORT`**. If a driver registers before the bus exists, no auto re-scan — ensure bus registers first (typical platform init order). + +--- + +## Bus controller driver (adapter) + +Pattern (SoC-specific sources under **`SOC_DM_SPI_DIR`** or BSP): + +```c +static rt_err_t my_spi_probe(struct rt_platform_device *pdev) +{ + struct rt_device *dev = &pdev->parent; + struct my_spi *spi = rt_calloc(1, sizeof(*spi)); + + spi->regs = rt_dm_dev_iomap(dev, 0); + spi->irq = rt_dm_dev_get_irq(dev, 0); + /* rt_clk_get_by_name, prepare_enable; pinctrl from DT */ + + spi->parent.parent.ofw_node = dev->ofw_node; + spi->parent.ops = &my_spi_ops; + + rt_dm_dev_set_name_auto(&spi->parent.parent, "spi"); + + return rt_spi_bus_register(&spi->parent, rt_dm_dev_get_name(dev), &my_spi_ops); +} +``` + +| Step | API | +| --- | --- | +| Resources | **`rt_dm_dev_iomap`**, **`rt_dm_dev_get_irq`**, **`rt_clk_*`** (@ref page_device_clk) | +| Pins | **`cs-gpios`** on controller → automatic in **`spi_bus_register`**; **`pinctrl-*`** on controller (@ref page_device_pinctrl) | +| Publish | **`rt_spi_bus_register`** or **`rt_qspi_bus_register`** — triggers **scan** | + +Bit-bang: **`rt_spi_bit_add_bus`** → same **`rt_spi_bus_register`** path. + +--- + +## Client driver (SPI slave) + +```c +static const struct rt_ofw_node_id my_ids[] = { + { .compatible = "vendor,my-flash" }, + { /* sentinel */ }, +}; + +static struct rt_spi_driver my_driver = { + .ofw_ids = my_ids, + .probe = my_probe, + .remove = my_remove, +}; +RT_SPI_DRIVER_EXPORT(my_driver); + +static rt_err_t my_probe(struct rt_spi_device *device) +{ + /* device->bus, device->chip_select[], device->config already parsed */ + /* Optional: rt_spi_configure(device, &device->config); */ + /* Register MTD/sensor or use transfers directly */ + return RT_EOK; +} +``` + +| Field / API | Role | +| --- | --- | +| **`device->bus`** | Parent adapter — required (**`-RT_EINVAL`** in **`spi_probe`** if NULL) | +| **`device->chip_select[0]`** | From DT **`reg`**; used with **`bus->cs_pins[]`** | +| **`device->cs_pin`** | Set in **`spi_probe`** from GPIO table or **`PIN_NONE`** | +| **`rt_spi_device_id_data(device)`** | **`id->data`** or **`ofw_id->data`** | + +### Generic `spidev` path (`dev_spi.c`) + +Built-in **`spidev_driver`** matches known **`compatible`** strings and DT node **names** (table in **`dev_spi.c`**). After **`spidev_probe`**, **`spi_probe`** may call **`rt_spidev_device_init`** so users get **`rt_device_find("spi1_0")`**-style names (**`%s_%d`**, bus name + CS). + +**Note**: Nodes whose **`compatible`** is exactly **`"spidev"`** are **rejected** in **`spidev_probe`** — use a vendor entry from the table (e.g. **`rockchip,spidev`** is listed but contains **`spidev`** substring — the check uses **`rt_dm_dev_prop_index_of_string(..., "spidev")`** which may match **`rockchip,spidev`** — that would fail! Line 175-179 checks index of "spidev" in compatible - rockchip,spidev would match and return error. That might be intentional to block linux spidev nodes. + +--- + +## Transfer path (unchanged by DM) + +DM only affects **how devices appear**. Data path is still: + +```c +rt_spi_configure(device, &cfg); /* or use pre-parsed device->config */ +rt_spi_transfer(device, tx, rx, len); +/* or rt_spi_transfer_message, send_then_recv, take_bus / release */ +``` + +Bus mutex: **`rt_spi_take_bus`** / **`rt_spi_release_bus`**. Do not call transfer APIs from ISR (they take mutex). + +--- + +## DM vs non-DM + +| Topic | With **`RT_USING_DM`** | Without DM | +| --- | --- | --- | +| Device struct | **`rt_spi_device`** + **`rt_device parent`**, OFW fields | **`bus`**, **`config`**, **`cs_pin`**, **`user_data`** | +| Binding | **`RT_SPI_DRIVER_EXPORT`** + DT scan | **`rt_spi_bus_attach_device`**, manual **`cs_pin`** | +| Bus register | **`spi_bus_scan_devices`**, **`cs-gpios`** parse | No scan | +| Config from DT | **`spi_device_ofw_parse`** | **`rt_spi_configure`** in init code | + +--- + +## Engineer checklist + +1. **Controller**: platform **`probe`** → resources → **`bus->parent.ofw_node`** → **`rt_spi_bus_register`**. +2. **DT**: controller **`#address-cells` / `#size-cells`**, **`cs-gpios`** if needed; child **`reg`** + **`compatible`** + **`spi-max-frequency`**. +3. **Client driver**: **`ofw_ids`** (+ optional **`ids`** by node name), **`RT_SPI_DRIVER_EXPORT`**, **`probe`** uses **`device->bus`** and **`rt_spi_*`** APIs. +4. **CS**: Prefer controller **`cs-gpios`** + child **`reg`**; legacy boards can still pass GPIO in **`rt_spi_bus_attach_device`** **`user_data`** without DM. +5. **Remove**: Tear down controller bus; **`spi_remove`** frees scanned devices when bus unregisters (verify BSP order). + +--- + +## Pitfalls + +- **`device->bus == NULL`** → probe fails — scan did not run or manual device missing **`bus`**. +- **No `compatible` on child** — skipped by scan silently. +- **Wrong `reg` (CS index)** — probe OK, wrong chip selected on wire. +- **Driver before bus** — no in-tree re-scan; child never probes. +- **`ids[].name`** must match DT **node name**, not **`compatible`** — prefer **`ofw_ids`**. +- **QSPI child on SPI bus** — scan picks type from **`bus->mode`**; mismatch causes alloc/assert issues. +- **Mixing DM scan with manual attach** on same bus — possible but avoid duplicate CS / duplicate device names. + +--- + +## See also + +- @ref page_device_spi — **`rt_spi_transfer`**, attach API, QSPI, examples +- @ref page_device_bus — core **`rt_bus`** +- @ref page_device_platform — controller **`RT_PLATFORM_DRIVER_EXPORT`** +- @ref page_device_pin — **`cs-gpios`** +- @ref page_device_ofw +- `components/drivers/include/drivers/dev_spi.h` +- `components/drivers/spi/dev_spi_bus.c`, `dev_spi_core.c`, `dev_spi_dm.c` diff --git a/documentation/6.components/device-driver/spi/spi.md b/documentation/6.components/device-driver/spi/spi.md index b4c7950c352..4352f497b0f 100644 --- a/documentation/6.components/device-driver/spi/spi.md +++ b/documentation/6.components/device-driver/spi/spi.md @@ -1,5 +1,19 @@ -@page page_device_spi SPI Device +@page page_device_spi SPI device +# SPI (Serial Peripheral Interface) + +SPI is a synchronous full-duplex bus (MOSI, MISO, SCLK, CS). RT-Thread exposes a **bus device** (`spi0`, ...) and **SPI slave devices** (`spi10`, `spi1_0`, or driver-specific names) for transfers. + +| Topic | Page | +| --- | --- | +| Application API (`rt_spi_transfer`, QSPI, ...) | This page | +| DM bus, DT scan, **`RT_SPI_DRIVER_EXPORT`** | @ref page_device_spi_dm | + +**Kconfig**: **`RT_USING_SPI`**. With **`RT_USING_DM`**, **`dev_spi_bus.c`** / **`dev_spi_dm.c`** add automatic child binding from device tree (@ref page_device_spi_dm). Without DM, use manual mount below. + +Implementation: **`components/drivers/spi/`**, headers **`drivers/dev_spi.h`**. + +--- # Introduction to SPI SPI (Serial Peripheral Interface) is a high-speed, full-duplex, synchronous communication bus commonly used for short-range communication. It is mainly used in EEPROM, FLASH, real-time clock, AD converter, and digital signal processing and between the device and the digital signal decoder. SPI generally uses 4 lines of communication, as shown in the following figure: @@ -36,6 +50,8 @@ So for SPI Flash, there are three types of standard SPI Flash, Dual SPI Flash, Q # Mount SPI Device +> **Device model (`RT_USING_DM`)**: After **`rt_spi_bus_register`**, children with **`compatible`** under the controller node are scanned automatically; use **`RT_SPI_DRIVER_EXPORT`** for chip drivers (@ref page_device_spi_dm). The APIs below are the **legacy manual attach** path (still supported for BSP init and boards without OFW). + The SPI driver registers the SPI bus and the SPI device needs to be mounted to the SPI bus that has already been registered. ```C @@ -739,3 +755,15 @@ static void spi_w25q_sample(int argc, char *argv[]) MSH_CMD_EXPORT(spi_w25q_sample, spi w25q sample); ``` +## See also (driver) + +- @ref page_device_spi_dm — DT scan, **spi_device_ofw_parse**, **RT_SPI_DRIVER_EXPORT** +- @ref page_device_platform — SPI controller platform drivers +- @ref page_device_pin — **cs-gpios** +- components/drivers/spi/SConscript +## See also (driver) + +- @ref page_device_spi_dm +- @ref page_device_platform +- @ref page_device_pin +- `components/drivers/spi/SConscript` diff --git a/documentation/6.components/device-driver/syscon/syscon.md b/documentation/6.components/device-driver/syscon/syscon.md new file mode 100755 index 00000000000..8ac4d67f8b7 --- /dev/null +++ b/documentation/6.components/device-driver/syscon/syscon.md @@ -0,0 +1,215 @@ +@page page_device_syscon System controller (Syscon) + +# Syscon (system control registers) + +**Syscon** is a small DM helper for a **shared MMIO register block** that several drivers touch (reboot magic, boot mode, LED bits, strap latches). One **`struct rt_syscon`** maps the region once and serializes **32-bit** accesses with a per-instance spinlock. + +| Piece | Path | +| --- | --- | +| API | `components/drivers/include/drivers/syscon.h` | +| Provider | `components/drivers/mfd/mfd-syscon.c` | +| Kconfig | **`RT_USING_MFD`** → **`RT_MFD_SYSCON`** (needs **`RT_USING_DM`**, **`RT_USING_OFW`**) | + +--- + +## End-to-end flow + +``` +DT: compatible "syscon" (+ reg) + ↓ +INIT_SUBSYS: platform driver "mfd-syscon" registered + ↓ +INIT_PLATFORM: platform device probe → syscon_probe() + ├─ rt_ofw_get_address → rt_ioremap + ├─ insert on global _syscon_nodes list + └─ rt_ofw_data(np) = syscon + ↓ +Consumer (reboot, LED, …) in its probe: + rt_syscon_find_by_ofw_phandle(np, "regmap") + or rt_syscon_find_by_ofw_node(parent_np) + ↓ +rt_syscon_read / write / update_bits (spinlock, HWREG32) +``` + +**Lazy probe**: If a node was not probed at init but is **`compatible = "syscon"`** or **`"simple-mfd"`**, **`rt_syscon_find_by_ofw_node`** can run **`syscon_probe`** on a stack **`rt_platform_device`** and then return **`rt_ofw_data(np)`**. Prefer normal DT probe for the syscon node so **`iomem_base`** exists before consumers start. + +--- + +## `struct rt_syscon` + +```c +struct rt_syscon { + rt_list_t list; + struct rt_ofw_node *np; + void *iomem_base; + rt_size_t iomem_size; /* byte size of mapped region */ + struct rt_spinlock rw_lock; +}; +``` + +| Field | Set by | +| --- | --- | +| `np` | Syscon DT node | +| `iomem_base` / `iomem_size` | First **`reg`** region in **`syscon_probe`** | +| `list` | Global registry **`_syscon_nodes`** (lookup under **`_syscon_nodes_lock`**) | + +Published to OFW: **`rt_ofw_data(syscon_np) = syscon`** after probe. + +--- + +## Provider driver (`mfd-syscon.c`) + +```c +static const struct rt_ofw_node_id syscon_ofw_ids[] = { + { .compatible = "syscon" }, + { /* sentinel */ }, +}; + +static struct rt_platform_driver syscon_driver = { + .name = "mfd-syscon", + .ids = syscon_ofw_ids, + .probe = syscon_probe, + .remove = syscon_remove, +}; +INIT_SUBSYS_EXPORT(syscon_drv_register); +``` + +| Step (`syscon_probe`) | Action | +| --- | --- | +| Map | **`rt_ofw_get_address(np, 0, &addr, &size)`** → **`rt_ioremap`** | +| Register | Insert into **`_syscon_nodes`**, init **`rw_lock`** | +| Publish | **`rt_ofw_data(np) = syscon`**, **`pdev->parent.user_data = syscon`** | + +**`compatible = "simple-mfd"`** is **not** in **`syscon_ofw_ids`**; those nodes are only handled via **`rt_syscon_find_by_ofw_node`** lazy probe (or as parent of children that call **`find`** on the parent). + +--- + +## Access API + +All three functions use **byte offset** into **`iomem_base`** (must satisfy **`offset < iomem_size`**). Each access is one **32-bit** **`HWREG32`** — offsets are **not** auto-scaled to word index. + +```c +rt_err_t rt_syscon_read(struct rt_syscon *syscon, rt_off_t offset, rt_uint32_t *out_val); +rt_err_t rt_syscon_write(struct rt_syscon *syscon, rt_off_t offset, rt_uint32_t val); +rt_err_t rt_syscon_update_bits(struct rt_syscon *syscon, rt_off_t offset, + rt_uint32_t mask, rt_uint32_t val); +``` + +**`rt_syscon_update_bits`** (under spinlock): + +```text +new = (old & ~mask) | (val & mask) +``` + +Only bits set in **`mask`** are changed; **`val`** bits outside **`mask`** are ignored. Safe for typical RMW glue; **W1C** / hardware that ignores read data may need a dedicated driver instead of blind **`update_bits`**. + +Callable from thread or ISR context (spinlock-based); keep critical sections short. + +--- + +## Lookup helpers + +| API | Use | +| --- | --- | +| **`rt_syscon_find_by_ofw_node(np)`** | Direct syscon (or **`simple-mfd`**) node; walks global list, else lazy **`syscon_probe`** | +| **`rt_syscon_find_by_ofw_phandle(np, "regmap")`** | Consumer node property → target syscon (Linux-style **`regmap`** phandle) | +| **`rt_syscon_find_by_ofw_compatible("vendor,foo-syscon")`** | First matching compatible in DT | + +Returns **`RT_NULL`** if map fails or compatible is not **`syscon`** / **`simple-mfd`** (for lazy path). + +--- + +## Device tree + +### Provider node + +```dts +syscon: syscon@10000000 { + compatible = "syscon"; + reg = <0x10000000 0x1000>; +}; +``` + +### Consumer: reboot (`syscon-reboot`) + +```dts +reboot { + compatible = "syscon-reboot"; + regmap = <&syscon>; + offset = <0x200>; + mask = <0x1>; + value = <0x1>; +}; +``` + +Kconfig: **`RT_POWER_RESET_SYSCON_REBOOT`**. Installs **`rt_dm_machine_reset`** hook that calls **`rt_syscon_update_bits`**. + +### Consumer: poweroff (`syscon-poweroff`) + +Same pattern with **`compatible = "syscon-poweroff"`** → **`rt_dm_machine_poweroff`**. + +### Consumer: reboot mode (`syscon-reboot-mode`) + +Child under syscon (or parent resolved via **`rt_ofw_get_parent`**); writes boot mode magic via **`reboot-mode`** helper. + +### Consumer: LED (`register-bit-led`) + +```dts +syscon: syscon@10000000 { + compatible = "syscon"; + reg = <0x10000000 0x100>; + + led0 { + compatible = "register-bit-led"; + offset = <0x4>; + mask = <0x10>; + default-state = "off"; + }; +}; +``` + +**`led-syscon.c`**: **`rt_syscon_find_by_ofw_node(parent)`**, **`rt_syscon_read` / `update_bits`** for on/off. See @ref page_device_led_dm. + +--- + +## Kconfig and related drivers + +| Option | Role | +| --- | --- | +| **`RT_USING_MFD`** | MFD menu (requires **`RT_USING_DM`**) | +| **`RT_MFD_SYSCON`** | Build **`mfd-syscon.c`** | +| **`RT_POWER_RESET_SYSCON_REBOOT`** | `power/reset/syscon-reboot.c` | +| **`RT_POWER_RESET_SYSCON_POWEROFF`** | `power/reset/syscon-poweroff.c` | +| **`RT_POWER_RESET_SYSCON_REBOOT_MODE`** | `power/reset/syscon-reboot-mode.c` | +| **`RT_LED_SYSCON`** | `led/led-syscon.c` | + +--- + +## Writing a consumer driver + +1. In **`probe`**, obtain **`struct rt_syscon *`** via phandle or parent node — do not **`rt_ioremap`** the same **`reg`** again. +2. Parse **`offset`**, **`mask`**, **`value`** (or chip-specific properties) from your DT node. +3. Use **`rt_syscon_read`** for status; **`rt_syscon_update_bits`** for controlled bit changes. +4. Coordinate with @ref page_device_power_board_reset if you hook **`rt_dm_machine_reset`** / **`rt_dm_machine_poweroff`** (only one handler). + +--- + +## Pitfalls + +- **Offset vs register index**: API uses **byte** offset; register *N* is often **`N * 4`** depending on TRM. +- **Region size**: **`offset >= iomem_size`** → **`-RT_EINVAL`** — extend **`reg`** in DTS if layout needs more space. +- **64-bit / 16-bit only registers**: not supported by this API — use a dedicated MMIO driver. +- **Lazy vs platform probe**: consumers that **`find`** before syscon **`INIT_PLATFORM`** probe may rely on lazy probe; ordering bugs are easier if the syscon node is a normal platform device probed first. +- **Mask/value**: **`update_bits(..., mask, val)`** clears all **`mask`** bits then sets **`val & mask`** — do not use for status bits that clear on read (W1C). +- **Duplicate mapping**: multiple drivers **`ioremap`** the same physical address without syscon → races; centralize on one **`rt_syscon`**. + +--- + +## See also + +- @ref page_device_platform — **`RT_PLATFORM_DRIVER_EXPORT`**, probe order +- @ref page_device_power_board_reset — machine reset / poweroff hooks +- @ref page_device_led_dm — **`register-bit-led`** +- @ref page_device_ofw — phandles, **`rt_ofw_data`** +- `components/drivers/include/drivers/syscon.h` +- `components/drivers/mfd/mfd-syscon.c` diff --git a/documentation/6.components/device-driver/thermal/cool.md b/documentation/6.components/device-driver/thermal/cool.md new file mode 100755 index 00000000000..926b1c410df --- /dev/null +++ b/documentation/6.components/device-driver/thermal/cool.md @@ -0,0 +1,111 @@ +@page page_device_thermal_cool Thermal cooling devices + +# Cooling devices (in-tree) + +Cooling drivers implement **`struct rt_thermal_cooling_device_ops`** and register with **`rt_thermal_cooling_device_register`**. Zones reference them from **`cooling-maps`** (see @ref page_device_thermal). + +| Driver | Kconfig | `compatible` (typical) | +| --- | --- | --- | +| PWM fan | **`RT_THERMAL_COOL_PWM_FAN`** | **`pwm-fan`** | +| GPIO fan | **`RT_THERMAL_COOL_GPIO_FAN`** | (see `thermal-cool-gpio-fan.c`) | +| DVFS | **`RT_THERMAL_COOL_DVFS`** | (see `thermal-cool-dvfs.c`) | + +Sources: `components/drivers/thermal/thermal-cool-*.c`. + +--- + +## PWM fan (`thermal-cool-pwm-fan.c`) + +### Role + +Maps thermal **cooling level** (0 … **max_level**) to **PWM duty** via optional **`cooling-levels`** LUT. Optional **`fan`** regulator supply (property resolves as supply name **`fan`** → **`rt_regulator_get(dev, "fan")`**). + +### Device tree + +```dts +fan0: fan { + compatible = "pwm-fan"; + pwms = <&pwm0 0 40000>; /* phandle, channel, period (ns) */ + cooling-levels = <0 64 128 192 255>; + /* optional: bind regulator supply id "fan" on this node */ +}; +``` + +Referenced from zone: + +```dts +cooling-device = <&fan0 0 4>; /* min level 0, max level 4 */ +``` + +| Property | Role | +| --- | --- | +| **`pwms`** | **`#pwm-cells`**: channel, period | +| **`cooling-levels`** | PWM scale values per level; array length − 1 = **max_level** | +| Regulator | **`rt_regulator_get(dev, "fan")`** — wire **`fan`** supply in regulator/DT as on your BSP | + +### Ops behavior + +| Op | Behavior | +| --- | --- | +| **`get_max_level`** | **`levels_nr - 1`** | +| **`get_cur_level`** | Current level (spinlock) | +| **`set_cur_level`** | LUT → **`rt_pwm_set`**; level 0 powers off PWM/regulator | + +**Power sequence**: on — **`rt_pwm_enable`** then **`rt_regulator_enable`**; off — reverse with rollback on error. + +### Probe flow + +1. Parse **`pwms`**, ensure PWM controller probed (**`rt_platform_ofw_request`** if needed). +2. Optional **`rt_regulator_get(dev, "fan")`**. +3. Read **`cooling-levels`** array. +4. **`rt_thermal_cooling_device_register`** — attaches default **`dumb`** governor. +5. Initial **`set_cur_level(0)`** (fan off). + +Platform driver: **`RT_PLATFORM_DRIVER_EXPORT(pwm_fan_cool_driver)`**. + +--- + +## GPIO fan (`thermal-cool-gpio-fan.c`) + +**Kconfig**: **`RT_THERMAL_COOL_GPIO_FAN`** (**`RT_USING_PIN`**, **`RT_USING_OFW`**). + +Controls fan speed via **GPIO pins** and a **speed table** (RPM / control value). Suitable for multi-wire fan tach/control GPIOs rather than PWM. + +Inspect **`gpio_fan_cool_probe`** in the source for exact property names on your BSP. + +--- + +## DVFS cooling (`thermal-cool-dvfs.c`) + +**Kconfig**: **`RT_THERMAL_COOL_DVFS`** + **`RT_USING_DVFS`**. + +Exposes **OPP table indices** as cooling levels — **`set_cur_level`** selects a lower performance point. Use when passive trip should throttle CPU/GPU frequency instead of spinning a fan. + +--- + +## Integration checklist + +1. **Register cooling device** in platform **`probe`** before dependent zone (or rely on init order). +2. Set **`parent.ofw_node`** on **`rt_thermal_cooling_device.parent`** so **`thermal_ofw_setup`** can match **`cooling-device`** phandles. +3. Define **`cooling-levels`** (PWM) or max level in ops consistently with DT **`cooling-device`** min/max cells. +4. Zone **`cooling-maps`** **`trip`** must point at a trip node parsed under the same thermal zone. +5. Test with **`rt_thermal_zone_device_update`** or wait for poller — verify **`set_cur_level`** at each trip. + +--- + +## Pitfalls + +- **Level 0**: PWM driver turns off output; confirm acoustic/thermal spec allows full stop. +- **Regulator + PWM order**: supply must match driver sequence (enable PWM before rail or as coded). +- **`set_cur_level` in ISR**: uses spinlock — do not call from ISR. +- **Governor**: **`dumb`** only nudges level on temperature delta vs **`hysteresis`** — custom governor may be needed for fine control. + +--- + +## See also + +- @ref page_device_thermal +- @ref page_device_pwm +- @ref page_device_regulator +- @ref page_device_pin +- `components/drivers/thermal/thermal-cool-pwm-fan.c` diff --git a/documentation/6.components/device-driver/thermal/thermal.md b/documentation/6.components/device-driver/thermal/thermal.md new file mode 100755 index 00000000000..148dc1395a6 --- /dev/null +++ b/documentation/6.components/device-driver/thermal/thermal.md @@ -0,0 +1,258 @@ +@page page_device_thermal Thermal management + +# Thermal subsystem + +RT-Thread thermal management ties **temperature sensors** (**thermal zones**) to **cooling devices** (fan, DVFS, …) using **trip points** and **governors**. Temperatures are in **millidegrees Celsius (m°C)**. + +| Piece | Path | +| --- | --- | +| API | `components/drivers/include/drivers/thermal.h` | +| Core + OFW | `components/drivers/thermal/thermal.c`, `thermal_dm.c` | +| Kconfig | **`RT_USING_THERMAL`** (requires **`RT_USING_DM`**, **`RT_USING_OFW`** for DT maps) | + +Cooling device details: @ref page_device_thermal_cool. + +--- + +## Architecture + +``` +Sensor driver (I2C, on-chip, SCMI, …) + embeds struct rt_thermal_zone_device + fills rt_thermal_zone_ops (get_temp required) + sets parent.ofw_node, zone_id + ↓ +rt_thermal_zone_device_register(zdev) + ├─ thermal_ofw_setup() ← walks /thermal-zones, trips, cooling-maps + ├─ work queue: thermal_zone_poll → rt_thermal_zone_device_update() + └─ global thermal_zone_device_nodes list + +Cooling driver (pwm-fan, dvfs, gpio-fan, …) + rt_thermal_cooling_device_register(cdev) + └─ global thermal_cooling_device_nodes list + ↓ +OFW cooling-map links trip → cdev via phandle + thermal_bind() in thermal_ofw_setup + ↓ +Trip exceeded → rt_thermal_cooling_device_kick() + governor tuning → cdev->ops->set_cur_level() +``` + +**Governor**: Built-in **`dumb`** governor is registered at **`INIT_CORE_EXPORT`**. **`rt_thermal_cooling_device_register`** attaches **`dumb`** unless **`rt_thermal_cooling_device_change_governor`** selects another. + +--- + +## Kconfig + +| Option | Role | +| --- | --- | +| **`RT_USING_THERMAL`** | Core **`thermal.c`** + **`thermal_dm.c`** | +| **`RT_THERMAL_SCMI`** | Sensor zones via SCMI (`thermal-scmi.c`, needs @ref page_device_scmi) | +| **`RT_THERMAL_COOL_PWM_FAN`** | `thermal-cool-pwm-fan.c` | +| **`RT_THERMAL_COOL_GPIO_FAN`** | `thermal-cool-gpio-fan.c` | +| **`RT_THERMAL_COOL_DVFS`** | `thermal-cool-dvfs.c` (needs **`RT_USING_DVFS`**) | +| **`SOC_DM_THERMAL_DIR`** | BSP sensor drivers | +| **`SOC_DM_THERMAL_COOL_DIR`** | BSP cooling drivers | + +--- + +## Thermal zone + +```c +struct rt_thermal_zone_device { + struct rt_device parent; + int zone_id; + const struct rt_thermal_zone_ops *ops; + struct rt_thermal_trip *trips; + rt_size_t trips_nr; + struct rt_thermal_cooling_map *cooling_maps; + rt_tick_t polling_delay, passive_delay; + /* temperature, cooling, poller work, mutex, … */ +}; + +struct rt_thermal_zone_ops { + rt_err_t (*get_temp)(struct rt_thermal_zone_device *zdev, int *out_temp); /* required */ + rt_err_t (*set_trips)(...); /* optional HW trip window */ + rt_err_t (*set_trip_temp)(...); + rt_err_t (*set_trip_hyst)(...); + void (*hot)(...); + void (*critical)(...); +}; +``` + +### Registering a zone + +```c +static rt_err_t my_get_temp(struct rt_thermal_zone_device *zdev, int *out_temp) +{ + *out_temp = read_sensor_mc(); /* m°C, or RT_THERMAL_TEMP_INVALID */ + return RT_EOK; +} + +static const struct rt_thermal_zone_ops my_tz_ops = { + .get_temp = my_get_temp, +}; + +/* probe */ +zdev->ops = &my_tz_ops; +zdev->zone_id = 0; +zdev->parent.ofw_node = sensor_np; +rt_thermal_zone_device_register(zdev); +``` + +| Requirement | Detail | +| --- | --- | +| **`get_temp`** | Mandatory; called from **`rt_thermal_zone_device_update`** | +| **`parent.ofw_node`** | Sensor node referenced from **`/thermal-zones/.../thermal-sensors`** | +| **Trips** | From DT under zone, or pre-filled **`zdev->trips`** before register | +| **Polling** | **`rt_work`** resubmits every **`polling_delay`** / **`passive_delay`** (from DT, ms → ticks) | + +--- + +## Device tree (`thermal_ofw_setup`) + +Linux-style layout under **`/thermal-zones`**: + +```dts +thermal-zones { + cpu-thermal { + polling-delay-passive = <250>; + polling-delay = <1000>; + thermal-sensors = <&cpu_temp 0>; + + trips { + passive: trip-passive { + temperature = <85000>; /* 85 °C in m°C */ + hysteresis = <2000>; + type = "passive"; + }; + critical: trip-critical { + temperature = <105000>; + hysteresis = <2000>; + type = "critical"; + }; + }; + + cooling-maps { + map0 { + trip = <&passive>; + cooling-device = <&fan0 0 3>, <&cpu_dvfs 0 5>; + contribution = <1024>; + }; + }; + }; +}; + +cpu_temp: cpu-temp@... { /* sensor OFW node */ }; + +fan0: fan { + compatible = "pwm-fan"; + pwms = <&pwm0 0 40000>; + cooling-levels = <0 50 100 150 200 255>; + /* optional: regulator consumer supply id "fan" (see regulator DT) */ +}; +``` + +| Zone property | Effect | +| --- | --- | +| **`thermal-sensors`** | Phandle + optional index → matches sensor **`ofw_node`** and **`zone_id`** | +| **`polling-delay`** / **`polling-delay-passive`** | Work reschedule interval | +| **`sustainable-power`**, **`coefficients`** | **`rt_thermal_zone_params`** | +| **Child `trips`** | **`temperature`**, **`hysteresis`**, **`type`** (`active` / `passive` / `hot` / `critical`) | +| **`cooling-maps`** | **`trip`**, **`cooling-device`** (`#cooling-cells`: min/max level), **`contribution`** | + +**`cooling-device`** phandle resolves to a registered **`rt_thermal_cooling_device`** by matching **`cdev->parent.ofw_node`**. Register **cooling devices before** the zone that references them (or ensure cooling nodes are probed first). + +--- + +## Cooling device + +```c +struct rt_thermal_cooling_device { + struct rt_device parent; + const struct rt_thermal_cooling_device_ops *ops; + rt_ubase_t max_level; + struct rt_thermal_cooling_governor *gov; + /* … */ +}; + +struct rt_thermal_cooling_device_ops { + rt_err_t (*bind)(struct rt_thermal_cooling_device *cdev, + struct rt_thermal_zone_device *zdev); + rt_err_t (*unbind)(...); + rt_err_t (*get_max_level)(...); + rt_err_t (*get_cur_level)(...); + rt_err_t (*set_cur_level)(struct rt_thermal_cooling_device *cdev, rt_ubase_t level); +}; +``` + +**`rt_thermal_cooling_device_register`** requires **`get_max_level`**, **`get_cur_level`**, **`set_cur_level`**. **`bind`/`unbind`** optional (forwarded by **`thermal_bind`** in `thermal_dm.c`). + +--- + +## Update loop and trips + +**`rt_thermal_zone_device_update(zdev, msg)`**: + +1. Reads temperature via **`get_temp`**. +2. For each trip with **`temperature < current`**, handles type: + - **`PASSIVE`** / default → **`cooling = true`**, **`rt_thermal_cooling_device_kick`** + - **`CRITICAL`** → **`ops->critical`** or cool; may **`rt_hw_cpu_reset`** if still over limit + - **`HOT`** → **`ops->hot`** if present +3. Governor **`tuning`** adjusts levels per **cooling-map** cell; **`set_cur_level`** applied. +4. Notifiers receive **`msg`** bitmask (`RT_THERMAL_MSG_TRIP_VIOLATED`, `RT_THERMAL_MSG_EVENT_TEMP_SAMPLE`, …). + +Call **`update`** from the zone poller (automatic after register) or manually for testing. + +--- + +## Built-in drivers + +| Driver | Binding | Role | +| --- | --- | --- | +| **`thermal-scmi.c`** | **`RT_SCMI_DRIVER_EXPORT`**, protocol sensor `"thermal"` | One zone per SCMI temperature sensor | +| **`thermal-cool-pwm-fan.c`** | **`compatible = "pwm-fan"`** | PWM + optional regulator — @ref page_device_thermal_cool | +| **`thermal-cool-gpio-fan.c`** | GPIO + speed table | Discrete fan speeds | +| **`thermal-cool-dvfs.c`** | DVFS OPP levels as cooling steps | Throttle CPU/GPU via @ref page_device_clk / DVFS | + +BSP sensors (e.g. board thermal IC) typically **`rt_thermal_zone_device_register`** in platform **`probe`** after filling trips in code or relying entirely on **`thermal_ofw_setup`**. + +--- + +## Key APIs + +| API | Role | +| --- | --- | +| `rt_thermal_zone_device_register` / `unregister` | Zone lifecycle + OFW parse + start poller | +| `rt_thermal_zone_device_update` | Sample temp, trips, cooling, notifiers | +| `rt_thermal_zone_set_trip` / `get_trip` | Runtime trip change | +| `rt_thermal_zone_notifier_register` | Event callback | +| `rt_thermal_cooling_device_register` / `unregister` | Cooling device | +| `rt_thermal_cooling_device_kick` | Re-run governor for a zone | +| `rt_thermal_cooling_governor_register` | Custom governor | +| `rt_thermal_cooling_device_change_governor` | Per-device governor selection | + +Constants: **`RT_THERMAL_TEMP_INVALID`** (−274000), **`RT_THERMAL_NO_LIMIT`**. + +--- + +## Pitfalls + +- **Units**: always **m°C** in trips and **`get_temp`** (85 °C → `85000`). +- **`get_temp` cost**: slow buses (I2C) in every poll — cache or shorten **`polling-delay`** thoughtfully. +- **Cooling registration order**: maps reference **`cdev`** by OFW node; probe cooling before zone register. +- **Missing maps**: trips fire but no **`cooling-maps`** → log/events only, no fan/DVFS change. +- **Hysteresis / noise**: tight trips without **`hysteresis`** cause level oscillation. +- **Mutex**: transfer APIs in other subsystems aside — zone **`update`** uses **`zdev->mutex`** when not in ISR. +- **Unregister**: detach notifiers and stop polling before destroying hardware. + +--- + +## See also + +- @ref page_device_thermal_cool — PWM fan cooling device +- @ref page_device_scmi — SCMI sensor protocol +- @ref page_device_pwm +- @ref page_device_regulator +- `components/drivers/include/drivers/thermal.h` +- `components/drivers/thermal/thermal.c` diff --git a/documentation/6.components/device-driver/uart/dm.md b/documentation/6.components/device-driver/uart/dm.md new file mode 100755 index 00000000000..a4609e88f99 --- /dev/null +++ b/documentation/6.components/device-driver/uart/dm.md @@ -0,0 +1,100 @@ +@page page_device_uart_dm UART device model (DM) + +# UART / serial DM helpers + +Kconfig uses **`RT_USING_SERIAL`**; registered device names are **`uart0`**, **`uart1`**, … Implementation directory: **`components/drivers/serial/`** (serial core + controller drivers). + +| Piece | Built when | Source | +| --- | --- | --- | +| Serial framework | **`RT_USING_SERIAL`** | `dev_serial.c` or `dev_serial_v2.c` | +| DM naming / bootargs parse | **`RT_USING_DM`** | `serial_dm.c` | +| OFW platform drivers (8250, PL011, …) | **`RT_USING_DM`** + **`RT_USING_SERIAL`** | `serial/device/*` (`host/Kconfig`) | + +Header: **`components/drivers/include/drivers/serial_dm.h`**. + +Application **`rt_device_find("uart2")`** usage: @ref page_device_uart. Early TX before probe: @ref page_device_uart_earlycon. + +--- + +## `serial_dev_set_name` + +```c +int serial_dev_set_name(struct rt_serial_device *sdev); +``` + +- With **`RT_USING_OFW`**, tries DTS alias id for **`serial`** then **`uart`** on **`sdev->parent.ofw_node`**. +- Otherwise uses an atomic counter seeded from **`rt_ofw_get_alias_last_id("serial")`** / **`uart`** so extra ports do not collide with aliased indices. +- Sets the parent **`rt_device`** name to **`uart%u`** via **`rt_dm_dev_set_name`**. + +**`INIT_PLATFORM_EXPORT(serial_dm_naming_framework_init)`** runs before platform UART probes to initialize **`uid_min`**. + +Prefer **`aliases { serial0 = &uart0 }`** or **`uart0 = &…`** in DTS so names match board docs and FinSH/console scripts. + +--- + +## Boot argument parsing + +### `serial_cfg_from_args` + +```c +struct serial_configure serial_cfg_from_args(char *str); +``` + +Parses the **earlycon / console** option tail (after the MMIO base in **`earlycon=`** strings): + +| Segment | Meaning | +| --- | --- | +| Leading digits | Baud rate (default from **`RT_SERIAL_CONFIG_DEFAULT`** if missing) | +| `n` / `o` / `e` | Parity none / odd / even | +| One digit | Data bits | +| `r` | RTS/CTS (`RT_SERIAL_FLOWCONTROL_CTSRTS`) | + +Example: **`115200n8`** → 115200, no parity, 8 data bits. + +If the string ends with the four-byte suffix **`OFW`**, that marker is cleared—firmware earlycon handoff so the full driver does not re-parse stale bootargs text. **Pass a writable buffer** when this path is used. + +### `serial_base_from_args` + +```c +void *serial_base_from_args(char *str); +``` + +Finds **`0x`** in the option string and parses a hexadecimal **physical base** for early bring-up (before OFW **`iomap`**). + +### `serial_for_each_args` + +Macro to iterate comma-separated sub-arguments in a boot string (multi-earlycon lists). + +--- + +## Typical platform `probe` (8250 / PL011) + +1. Map registers: **`rt_ofw_iomap(np, 0)`** or **`rt_dm_dev_iomap(dev, 0)`** (driver-specific). +2. **`rt_ofw_get_irq`** / **`rt_dm_dev_get_irq`** for the interrupt line. +3. Optional **`rt_clk_get`**, **`rt_reset_control_get`**, **`current-speed`**, **`reg-shift`** from DT. +4. Fill **`struct rt_serial_device`** / SoC wrapper, **`serial_dev_set_name`** (often inside **`serial8250_setup`** / driver `setup`). +5. **`rt_hw_serial_register(&serial->parent, name, flags, priv)`** — exposes **`uartN`** to applications. + +Example OFW 8250: **`components/drivers/serial/device/8250/8250-ofw.c`**, **`core.c`** (`serial8250_setup`). + +RX DMA: request a channel from @ref page_device_dma; completion feeds the serial ISR (**`rt_hw_serial_isr`**, events **`RT_SERIAL_EVENT_RX_DMADONE`**). + +--- + +## Pitfalls + +- **Alias vs probe order**: ports without aliases get **`uart0`**, **`uart1`** in registration order—not always DTS label order. +- **Pinctrl**: apply **`pinctrl-0`** (or board init) **before** first TX—see @ref page_device_pinctrl. +- **Console vs app**: reopening the shell UART can conflict—document exclusive use or use Serial V2 policy if enabled (**`RT_USING_SERIAL_V2`**). +- **`RT_DEVICE_CTRL_CONFIG`**: must reach hardware in the driver **`control`** / **`configure`** path or users see wrong baud after open. + +--- + +## See also + +- @ref page_device_uart — application API +- @ref page_device_uart_earlycon — FDT **`earlycon`**, **`stdout-path`** +- @ref page_device_platform — platform driver registration +- @ref page_device_dma +- `components/drivers/include/drivers/dev_serial.h`, `dev_serial_v2.h` +- `components/drivers/serial/serial_dm.c` diff --git a/documentation/6.components/device-driver/uart/earlycon.md b/documentation/6.components/device-driver/uart/earlycon.md new file mode 100755 index 00000000000..608816fe6e2 --- /dev/null +++ b/documentation/6.components/device-driver/uart/earlycon.md @@ -0,0 +1,90 @@ +@page page_device_uart_earlycon UART early console + +# Early console (before full UART driver) + +Early output runs **before** the normal **`rt_serial_device`** / **`rt_hw_serial_register`** path: a minimal **putc** hook is installed from FDT **`/chosen`** **`stdout-path`** and/or **`earlycon=`** in **`bootargs`**. + +Sources: + +- **8250 / NS16550 / DW APB**: `components/drivers/serial/device/8250/early.c` +- **Hypervisor console**: `components/drivers/serial/device/serial-early-hvc.c` + +Baud/format/base parsing: **`serial_cfg_from_args`**, **`serial_base_from_args`** in **`serial_dm.c`** — see @ref page_device_uart_dm. + +--- + +## 8250 earlycon (`early.c`) + +### Registration + +**`RT_FDT_EARLYCON_EXPORT`** entries (examples): + +| Symbol | `earlycon` name | Example compatible | +| --- | --- | --- | +| `ns16550` | `uart8250` | `ns16550`, `ns16550a` | +| `dw8250` | `uart8250` | `snps,dw-apb-uart` | +| `tegra20` | `uart8250` | `nvidia,tegra20-uart` | +| `bcm2835aux` | `uart8250` | `brcm,bcm2835-aux-uart` | + +Bootargs example: + +```text +earlycon=uart8250,mmio32,0x10009000,115200n8 +``` + +The FDT earlycon core passes **`options`** into **`serial8250_early_fdt_setup`**, which calls **`serial8250_config`** then maps MMIO with **`rt_ioremap_early`**. + +### `serial8250_early_putc` + +Polls **UART LSR** (**TEMT | THRE**) after writing **UART_TX**—safe with interrupts masked. Installed as **`con->console_putc`**. + +### Lifecycle: `serial8250_early_kick` + +| `why` | Action | +| --- | --- | +| `FDT_EARLYCON_KICK_UPDATE` | **`rt_ioremap`** full mapping when switching from early to runtime map | +| `FDT_EARLYCON_KICK_COMPLETED` | **`rt_iounmap`** early mapping after the platform driver owns the port | + +If **`config.baud_rate`** is zero at setup, firmware already programmed the UART; the code only clears **IER**. + +### Divisor + +When **`serial->freq`** is set, **`init_serial`** programs **DLL/DLM** from **`freq / (16 * baud_rate)`**. + +--- + +## HVC earlycon (`serial-early-hvc.c`) + +```c +RT_FDT_EARLYCON_EXPORT(hvc, "hvc", "vmrt-thread,hvc-console", hvc_early_setup); +``` + +- **`hvc_early_setup`** calls **`rt_hv_version`**; on success **`console_putc`** → **`rt_hv_console(c)`**. +- No MMIO **`reg`**—hypervisor console path. + +--- + +## Handoff to full driver + +1. Earlycon prints via **`rt_kprintf`** / **`console_putc`**. +2. Platform probe (e.g. **`8250-ofw`**) maps the same UART, **`serial_dev_set_name`**, **`rt_hw_serial_register`** → **`uartN`** for apps. +3. Early mapping is released on **`FDT_EARLYCON_KICK_COMPLETED`** when the serial core finishes transition. + +Do not leave early **putc** and the full driver writing the same UART without the kick handshake. + +--- + +## Pitfalls + +- **`reg-shift`**: early defaults may differ from DT **`reg-shift`** in probe—keep them consistent for your IP. +- **Options buffer**: **`serial_cfg_from_args`** may modify the string ( **`OFW`** suffix). +- **RX**: early path is **TX-only**; input needs the full UART driver or another console backend. + +--- + +## See also + +- @ref page_device_uart_dm +- @ref page_device_uart +- @ref page_device_ofw — **`chosen`**, **`stdout-path`** +- `components/drivers/serial/device/8250/core.c`, `8250-ofw.c` diff --git a/documentation/6.components/device-driver/uart/uart.md b/documentation/6.components/device-driver/uart/uart.md index 0a87440b71a..b34bcf48490 100644 --- a/documentation/6.components/device-driver/uart/uart.md +++ b/documentation/6.components/device-driver/uart/uart.md @@ -1,22 +1,37 @@ -@page page_device_uart UART Device +@page page_device_uart UART -# UART Introduction +# UART (serial) device -UART (Universal Asynchronous Receiver/Transmitter), as a kind of asynchronous serial communication protocol, the working principle is to transmit each character of the transmitted data one by one. It is the most frequently used data bus during application development. +UART (Universal Asynchronous Receiver/Transmitter) is the usual **character-device** path for console, AT commands, and board bring-up. In RT-Thread the **driver stack** lives under **`components/drivers/serial/`** (Kconfig **`RT_USING_SERIAL`**), while applications see devices named **`uart0`**, **`uart1`**, … -The UART serial port is characterized by sequentially transmitting data one bit at a time. As long as two transmission lines can realize two-way communication, one line transmits data while the other receives data . There are several important functions for UART serial communication, namely baud rate, start bit, data bit, stop bit and parity bit. For two ports that use UART serial port communication, these functions must be matched, otherwise the communication can't be carried out normally. The data format of the UART serial port transmission is as shown below: +![Serial transmission data format](figures/uart1.png) -![Serial Transmission Data Format](figures/uart1.png) +| Layer | Doc / source | +| --- | --- | +| Application (`rt_device_*`) | This page — **Application access** | +| Framework (`rt_serial_device`, V1/V2) | `dev_serial.c` / `dev_serial_v2.c`, `dev_serial.h` | +| DM naming, bootargs | @ref page_device_uart_dm — `serial_dm.c` | +| Early printk | @ref page_device_uart_earlycon | +| Controllers (8250, PL011, …) | `serial/device/*`, platform **`RT_PLATFORM_DRIVER_EXPORT`** | -* Start bit: Indicates the start of data transfer and the level logic is "0". -- Data bits: Possible values are 5, 6, 7, 8, and 9, indicating that these bits are transmitted. The value is generally 8, because an ASCII character value is 8 bits. -- Parity check bit: It it used by the receiver to verify the received data. The number of bits is used in the check of "1" is even (even parity) or odd (odd parity) ,in order to verify the data transmission. It is also fine by not using this bit . -- Stop Bit: Indicates the end of one frame of data. The level logic is "1". -- Baudrate: It is the rate at which a serial port communicates, which expressed in bits per second (bps) of the binary code transmitted in unit time. The common baud rate values are 4800, 9600, 14400, 38400, 115200, etc. The higher the value is, the faster the data transmission will be. +**Kconfig**: **`RT_USING_SERIAL`** (default on). **`RT_USING_SERIAL_V1`** vs **`RT_USING_SERIAL_V2`**; optional **`RT_SERIAL_USING_DMA`**, **`RT_USING_SERIAL_BYPASS`**. SoC drivers: **`RT_USING_DM && RT_USING_SERIAL`** → `serial/device/Kconfig`. -# Access UART Device +--- -The application accesses the serial port hardware through the I/O device management interface provided by RT-Thread. The related interfaces are as follows: +## Driver writer (quick path) + +1. Implement **`struct rt_uart_ops`** / serial ops: **`configure`**, **`control`**, **`putc`**, **`getc`**, optional **`dma_transmit`**. +2. With DM + OFW: map MMIO/IRQ, clocks/resets, **`serial_dev_set_name`**, then **`rt_hw_serial_register`** (see @ref page_device_uart_dm). +3. Report RX/TX completion via **`rt_hw_serial_isr(serial, RT_SERIAL_EVENT_*)`**. +4. Apply **pinctrl** and regulators (if any) before enabling TX. + +--- + +## Application access + +The application uses the **generic device interface** (`rtdevice.h`). The UART is a **character device**; **`struct serial_configure`** and **`RT_DEVICE_CTRL_CONFIG`** are in **`drivers/dev_serial.h`** (V1) or **`dev_serial_v2.h`** (V2). + +Main APIs: | **Funtion** | **Description** | | --------------------------- | -------------------------- | @@ -77,9 +92,9 @@ oflags parameters support the following values (Use OR logic to support multiple /* Receive mode function */ #define RT_DEVICE_FLAG_INT_RX 0x100 /* Interrupt receive mode */ #define RT_DEVICE_FLAG_DMA_RX 0x200 /* DMA receiving mode */ -/* Receive mode function */ -#define RT_DEVICE_FLAG_INT_TX 0x400 /* Interrupt receive mode*/ -#define RT_DEVICE_FLAG_DMA_TX 0x800 /* DMA receive mode */ +/* Transmit mode flags */ +#define RT_DEVICE_FLAG_INT_TX 0x400 /* Interrupt transmit mode */ +#define RT_DEVICE_FLAG_DMA_TX 0x800 /* DMA transmit mode */ ``` There are three modes of uart data receiving and sending: interrupt mode, polling mode and DMA mode. When used, only one of the three modes can be selected. If the open parameter oflag of the serial port does not specify the use of interrupt mode or DMA mode, the polling mode is used by default. @@ -644,3 +659,10 @@ static int uart_dma_sample(int argc, char *argv[]) MSH_CMD_EXPORT(uart_dma_sample, uart device dma sample); ``` +## See also (driver) + +- @ref page_device_uart_dm — naming, bootargs, platform probe +- @ref page_device_uart_earlycon — earlycon, handoff to uartN +- @ref page_device_pinctrl — pin mux before UART enable +- @ref page_device_dma — RX/TX DMA +- components/drivers/serial/SConscript — build matrix diff --git a/documentation/6.components/device-driver/ufs/ufs.md b/documentation/6.components/device-driver/ufs/ufs.md new file mode 100755 index 00000000000..8971907b27d --- /dev/null +++ b/documentation/6.components/device-driver/ufs/ufs.md @@ -0,0 +1,159 @@ +@page page_device_ufs UFS host + +# UFS host controller + +Header: **`components/drivers/include/drivers/ufs.h`**. Core: **`components/drivers/ufs/ufs.c`**, link/PA PM: **`ufs_pm.c`**. + +RT-Thread implements a **UFSHCI host**: UIC for UniPro link (DME), UTP for SCSI-over-UPIU, then **`rt_scsi_host_register`** so the existing SCSI core scans LUNs and registers **`sd*`** disks (@ref page_device_scsi, **`RT_SCSI_SD`**). + +There is **no separate UFS device-model bus** in-tree—only the **host adapter** API plus optional **PCI** glue (`ufs-pci.c`). SoC controllers use **`struct rt_ufs_ops::init` / `exit`** (and BSP code under **`SOC_DM_UFS_DIR`** when enabled in Kconfig) for clocks, reset, PHY, and supplies. + +--- + +## Kconfig + +| Option | Depends on | Role | +| --- | --- | --- | +| **`RT_USING_UFS`** | **`RT_USING_DM`**, **`RT_USING_DMA`**, **`RT_SCSI_SD`** | **`ufs.c`**, **`ufs_pm.c`** | +| **`RT_UFS_PCI`** | **`RT_USING_UFS`**, **`RT_USING_PCI`** | **`ufs-pci.c`** (`RT_PCI_DRIVER_EXPORT`) | +| **`SOC_DM_UFS_DIR`** | **`RT_USING_UFS`** | BSP Kconfig / SoC UFS platform driver (out of tree here) | + +Enable **`RT_USING_SCSI`** and **`RT_SCSI_SD`** before UFS; the stack ends at **`rt_blk_disk`** / **`sd*`** (@ref page_device_blk, @ref page_device_disk). + +--- + +## End-to-end flow + +``` + Platform / PCI: MMIO (regs), IRQ, parent.dev for DMA + | + | static struct my_ufs { struct rt_ufs_host parent; ... }; + | fill regs, irq, ops; parent.dev = &controller->parent; + v + rt_ufs_host_register(ufs) + | CAP → nutrs; alloc UTRL/UCD/bounce (coherent via parent.dev) + | ops->init (optional): clk, reset, regulator, PHY + | HCE, program UTRLBA/UTMRLBA, UTRLRSR/UTMRLRSR + | DME_LINKSTARTUP if HCS not ready (+ link_startup_notify PRE/POST) + | rt_ufs_pm_post_linkup (IRQ aggregation, Auto-H8 AHIT) + | IRQ install; parent.ops = ufs_host_ops; parallel_io = RT_TRUE + v + rt_scsi_host_register(&ufs->parent) + | INQUIRY per (id, lun) → scsi_sd_probe → sd* + v + Applications use blk API +``` + +**Unregister**: **`rt_ufs_host_unregister`** → **`rt_scsi_host_unregister`** → **`ops->exit`**, free DMA, mask IRQ. + +**Runtime I/O**: SCSI **`transfer`** builds UPIU + UTRD, rings doorbell, waits on **`ufs->done`** (ISR sets completion on **`UTRCS`** / fatal bits). Small buffers use an internal **bounce** region (4 KiB) so inquiry/sense/capacity do not require caller DMA mapping. + +--- + +## Host driver responsibilities (before register) + +| Field | Caller | Notes | +| --- | --- | --- | +| **`parent.dev`** | **Required** | Every **`rt_dma_alloc_coherent`** uses this device; set **`ofw_node`** consistently for DM naming. | +| **`regs`** | **Required** | UFSHCI MMIO base. | +| **`irq`** | **Required** | Installed at end of register; name **`ufs-`**. | +| **`ops`** | **Required** | Struct pointer; individual hooks optional. | +| **`ucd_size`** | Optional | **0** → **`RT_UFS_UCD_SIZE`**. | +| **`ahit`** | Optional | Raw **AHIT** register; **0** → default in **`rt_ufs_pm_post_linkup`** when **`CAP_AUTOH8`**. | +| **`parent.max_id` / `max_lun`** | Optional | Raised to at least **1** if zero; tune scan range for your topology. | +| UTRL/UCD/bounce/`cap`/`nutrs`/… | **Core** | Do not pre-allocate. | + +After success, **`ufs->parent.ops`** points to internal **`ufs_host_ops`** (SCSI path). Vendor hooks stay in **`struct rt_ufs_ops`** only. + +--- + +## `struct rt_ufs_ops` + +| Callback | When | Role | +| --- | --- | --- | +| **`init`** | After DMA alloc, before **HCE** and list base programming | Clocks, reset, PHY, vendor regs; failure rolls back buffers. | +| **`link_startup_notify`** | Before/after **`DME_LINKSTARTUP`** when lists not ready | **`RT_UFS_NOTIFY_CHANGE_STATUS_PRE` / `POST`** for extra DME traffic—keep short. | +| **`reset`** | SCSI host **`reset`** path | Controller/link recovery (optional). | +| **`exit`** | After **`rt_scsi_host_unregister`** | Undo **`init`**; errors logged only. | + +Minimal PCI reference uses empty **`pci_ufs_std_ops`**—BAR map + **`rt_ufs_host_register`** only (`ufs-pci.c`). SoC hosts should implement **`init`/`exit`** for @ref page_device_regulator, @ref page_device_clk, @ref page_device_reset, @ref page_device_power_domain. + +--- + +## `rt_ufs_host_register` sequence (core) + +1. Validate **`ufs`**, **`ops`**, **`regs`**; read **`CAP`**, **`nutrs`** (cap 32 slots). +2. Allocate **UTRL** (1 KiB aligned), **UCD**, **bounce** via **`parent.dev`**. +3. **`ops->init`** if present. +4. **HCE** pulse; program **UTRLBA/U**, **UTMRLBA/U**; set **UTRLRSR**, **UTMRLRSR**. +5. If **HCS** lacks **UTRLRDY/UTMRLRDY/UCRDY**: optional notify → **`rt_ufs_uic_cmd_send(DME_LINKSTARTUP)`** → notify. +6. **`rt_ufs_pm_post_linkup`** (see below). +7. IRQ + **`rt_scsi_host_register`** with **`ufs_host_ops`**. + +UIC helpers (**`rt_ufs_uic_cmd_send`**, **`rt_ufs_dme_set`/`get`**) are for bring-up and PM; normal storage traffic stays on the SCSI path. + +--- + +## Link and HCI power management + +Implementation: **`ufs_pm.c`**. This layer is **UniPro PA / Hibernate8 / UFSHCI idle features**, not system suspend. Rails and clocks belong in **`init`/`exit`**. + +### Boot defaults (`rt_ufs_pm_post_linkup`) + +Called from register after link-up (safe to call again after custom training): + +| Step | API | Role | +| --- | --- | --- | +| IRQ aggregation | **`rt_ufs_intr_aggr_configure(ufs, RT_TRUE, cnt, 2)`** | **UTRIACR** — batch completion IRQs (`cnt` ≈ **`nutrs - 1`**, max 31) | +| Auto-Hibernate8 | **`rt_ufs_auto_hibern8_set`** | If **`cap & RT_UFS_REG_CAP_AUTOH8`**, programs **AHIT** | + +**`ahit`**: **0** uses **`RT_UFS_AHIT_DEFAULT`** (~150 ms idle); non-zero is written as-is; disable later with **`rt_ufs_auto_hibern8_set(ufs, 0)`**. Encode timers with **`rt_ufs_ahit_encode(timer, scale)`**. + +### PA power mode (optional, not auto at register) + +```c +struct rt_ufs_pa_layer_attr attr = { /* gear, lanes, pwr_rx/tx, hs_rate */ }; +rt_ufs_pa_power_mode_set(ufs, &attr, force); +``` + +Skips when attributes match cached **`pwr_active`** unless **`force`**. Use after link is stable for performance vs power; **`ufs-pci.c`** does not set PA mode today. + +### Hibernate8 + +| API | Use | +| --- | --- | +| **`rt_ufs_hibern8_enter` / `exit`** | Explicit **DME_HIBERNATE_ENTER/EXIT** when **`CAP_AUTOH8`** is absent or for long idle policy | + +Finish outstanding UTP transfers before enter; exit before new SCSI commands. + +### Suggested bring-up order + +1. **`ops->init`**: SoC clock / reset / regulator / PHY. +2. **`rt_ufs_host_register`** → link startup → **`rt_ufs_pm_post_linkup`**. +3. Optional **`rt_ufs_pa_power_mode_set`** for target gear/mode. +4. Runtime: Auto-H8 and/or manual Hibernate8. +5. **`rt_ufs_host_unregister`** → **`ops->exit`**. + +--- + +## PCI binding (`RT_UFS_PCI`) + +**`ufs-pci.c`**: **`pci_ufs_probe`** iomaps BAR0, takes **`pdev->irq`**, **`rt_pci_set_master`**, **`rt_ufs_host_register`**. IDs include Red Hat **`0x0013`** and Samsung **`0xc00c`**. **`remove`/`shutdown`** call **`rt_ufs_host_unregister`**, unmap, free host struct. + +No regulator or PA tuning in PCI glue—add a **`pci_ufs_quirk`** with custom **`rt_ufs_ops`** if a device needs it. + +--- + +## Caches and debugging + +UFS DMA paths use **`rt_hw_cpu_dcache_ops`** around UTRD/UCD (see @ref page_device_hwcache). Log tags: **`rtdm.ufs`**, **`rtdm.ufs.pm`**. + +--- + +## See also + +| Topic | Page / path | +| --- | --- | +| SCSI scan, **`sd*`**, CDB flow | @ref page_device_scsi | +| AHCI as another SCSI host | @ref page_device_ata | +| Sources | `components/drivers/ufs/ufs.c`, `ufs_pm.c`, `ufs-pci.c` |