diff --git a/Documentation/devicetree/bindings/pwm/thead,th1520-pwm.yaml b/Documentation/devicetree/bindings/pwm/thead,th1520-pwm.yaml new file mode 100644 index 00000000000000..855aec59ac53c4 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/thead,th1520-pwm.yaml @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/thead,th1520-pwm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: T-HEAD TH1520 PWM controller + +maintainers: + - Michal Wilczynski + +allOf: + - $ref: pwm.yaml# + +properties: + compatible: + const: thead,th1520-pwm + + reg: + maxItems: 1 + + clocks: + items: + - description: SoC PWM clock + + "#pwm-cells": + const: 3 + +required: + - compatible + - reg + - clocks + +unevaluatedProperties: false + +examples: + - | + #include + soc { + #address-cells = <2>; + #size-cells = <2>; + pwm@ffec01c000 { + compatible = "thead,th1520-pwm"; + reg = <0xff 0xec01c000 0x0 0x4000>; + clocks = <&clk CLK_PWM>; + #pwm-cells = <3>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index a92290fffa163f..daaa5c82b70d13 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20070,6 +20070,12 @@ F: include/linux/pwm.h F: include/linux/pwm_backlight.h K: pwm_(config|apply_might_sleep|apply_atomic|ops) +PWM SUBSYSTEM BINDINGS [RUST] +M: Michal Wilczynski +S: Maintained +F: rust/helpers/pwm.c +F: rust/kernel/pwm.rs + PXA GPIO DRIVER M: Robert Jarzmik L: linux-gpio@vger.kernel.org @@ -21385,6 +21391,7 @@ F: Documentation/devicetree/bindings/firmware/thead,th1520-aon.yaml F: Documentation/devicetree/bindings/mailbox/thead,th1520-mbox.yaml F: Documentation/devicetree/bindings/net/thead,th1520-gmac.yaml F: Documentation/devicetree/bindings/pinctrl/thead,th1520-pinctrl.yaml +F: Documentation/devicetree/bindings/pwm/thead,th1520-pwm.yaml F: Documentation/devicetree/bindings/reset/thead,th1520-reset.yaml F: arch/riscv/boot/dts/thead/ F: drivers/clk/thead/clk-th1520-ap.c @@ -21393,6 +21400,7 @@ F: drivers/mailbox/mailbox-th1520.c F: drivers/net/ethernet/stmicro/stmmac/dwmac-thead.c F: drivers/pinctrl/pinctrl-th1520.c F: drivers/pmdomain/thead/ +F: drivers/pwm/pwm_th1520.rs F: drivers/reset/reset-th1520.c F: include/dt-bindings/clock/thead,th1520-clk-ap.h F: include/dt-bindings/power/thead,th1520-power.h diff --git a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts index 4020c727f09e8e..c58c2085ca92a3 100644 --- a/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts +++ b/arch/riscv/boot/dts/thead/th1520-lichee-pi-4a.dts @@ -28,9 +28,76 @@ chosen { stdout-path = "serial0:115200n8"; }; + + thermal-zones { + cpu-thermal { + polling-delay = <1000>; + polling-delay-passive = <1000>; + thermal-sensors = <&pvt 0>; + + trips { + fan_config0: fan-trip0 { + temperature = <39000>; + hysteresis = <5000>; + type = "active"; + }; + + fan_config1: fan-trip1 { + temperature = <50000>; + hysteresis = <5000>; + type = "active"; + }; + + fan_config2: fan-trip2 { + temperature = <60000>; + hysteresis = <5000>; + type = "active"; + }; + }; + + cooling-maps { + map-active-0 { + cooling-device = <&fan 1 1>; + trip = <&fan_config0>; + }; + + map-active-1 { + cooling-device = <&fan 2 2>; + trip = <&fan_config1>; + }; + + map-active-2 { + cooling-device = <&fan 3 3>; + trip = <&fan_config2>; + }; + }; + }; + }; + + fan: pwm-fan { + pinctrl-names = "default"; + pinctrl-0 = <&fan_pins>; + compatible = "pwm-fan"; + #cooling-cells = <2>; + pwms = <&pwm 1 10000000 0>; + cooling-levels = <0 66 196 255>; + }; + }; &padctrl0_apsys { + fan_pins: fan-0 { + pwm1-pins { + pins = "GPIO3_3"; /* PWM1 */ + function = "pwm"; + bias-disable; + drive-strength = <25>; + input-disable; + input-schmitt-disable; + slew-rate = <0>; + }; + }; + uart0_pins: uart0-0 { tx-pins { pins = "UART0_TXD"; diff --git a/arch/riscv/boot/dts/thead/th1520.dtsi b/arch/riscv/boot/dts/thead/th1520.dtsi index 1db0054c4e0934..bef30780034e06 100644 --- a/arch/riscv/boot/dts/thead/th1520.dtsi +++ b/arch/riscv/boot/dts/thead/th1520.dtsi @@ -490,6 +490,13 @@ status = "disabled"; }; + pwm: pwm@ffec01c000 { + compatible = "thead,th1520-pwm"; + reg = <0xff 0xec01c000 0x0 0x4000>; + clocks = <&clk CLK_PWM>; + #pwm-cells = <3>; + }; + clk: clock-controller@ffef010000 { compatible = "thead,th1520-clk-ap"; reg = <0xff 0xef010000 0x0 0x1000>; @@ -662,6 +669,17 @@ thead,pad-group = <1>; }; + pvt: pvt@fffff4e000 { + compatible = "moortec,mr75203"; + reg = <0xff 0xfff4e000 0x0 0x80>, + <0xff 0xfff4e080 0x0 0x100>, + <0xff 0xfff4e180 0x0 0x680>, + <0xff 0xfff4e800 0x0 0x600>; + reg-names = "common", "ts", "pd", "vm"; + clocks = <&aonsys_clk>; + #thermal-sensor-cells = <1>; + }; + gpio@fffff52000 { compatible = "snps,dw-apb-gpio"; reg = <0xff 0xfff52000 0x0 0x1000>; diff --git a/drivers/clk/thead/clk-th1520-ap.c b/drivers/clk/thead/clk-th1520-ap.c index ebfb1d59401d05..cf7f6bd428a0fa 100644 --- a/drivers/clk/thead/clk-th1520-ap.c +++ b/drivers/clk/thead/clk-th1520-ap.c @@ -792,11 +792,12 @@ static CCU_GATE(CLK_AON2CPU_A2X, aon2cpu_a2x_clk, "aon2cpu-a2x", axi4_cpusys2_ac 0x134, BIT(8), 0); static CCU_GATE(CLK_X2X_CPUSYS, x2x_cpusys_clk, "x2x-cpusys", axi4_cpusys2_aclk_pd, 0x134, BIT(7), 0); -static CCU_GATE(CLK_CPU2AON_X2H, cpu2aon_x2h_clk, "cpu2aon-x2h", axi_aclk_pd, 0x138, BIT(8), 0); +static CCU_GATE(CLK_CPU2AON_X2H, cpu2aon_x2h_clk, "cpu2aon-x2h", axi_aclk_pd, + 0x138, BIT(8), CLK_IGNORE_UNUSED); static CCU_GATE(CLK_CPU2PERI_X2H, cpu2peri_x2h_clk, "cpu2peri-x2h", axi4_cpusys2_aclk_pd, 0x140, BIT(9), CLK_IGNORE_UNUSED); static CCU_GATE(CLK_PERISYS_APB1_HCLK, perisys_apb1_hclk, "perisys-apb1-hclk", perisys_ahb_hclk_pd, - 0x150, BIT(9), 0); + 0x150, BIT(9), CLK_IGNORE_UNUSED); static CCU_GATE(CLK_PERISYS_APB2_HCLK, perisys_apb2_hclk, "perisys-apb2-hclk", perisys_ahb_hclk_pd, 0x150, BIT(10), CLK_IGNORE_UNUSED); static CCU_GATE(CLK_PERISYS_APB3_HCLK, perisys_apb3_hclk, "perisys-apb3-hclk", perisys_ahb_hclk_pd, diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index d9bcd1e8413eae..be05658a568cb9 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -719,6 +719,16 @@ config PWM_TEGRA To compile this driver as a module, choose M here: the module will be called pwm-tegra. +config PWM_TH1520_RUST + tristate "TH1520 PWM support (Rust)" + depends on RUST_PWM_ABSTRACTIONS + help + This option enables the driver for the PWM controller found on the + T-HEAD TH1520 SoC. This driver is written in Rust. + + To compile this driver as a module, choose M here; the module + will be called pwm-th1520. If you are unsure, say N. + config PWM_TIECAP tristate "ECAP PWM support" depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX || ARCH_KEYSTONE || ARCH_K3 || COMPILE_TEST @@ -790,4 +800,17 @@ config PWM_XILINX To compile this driver as a module, choose M here: the module will be called pwm-xilinx. + config RUST_PWM_ABSTRACTIONS + bool "Rust PWM abstractions support" + depends on RUST + depends on PWM=y + help + This option enables the safe Rust abstraction layer for the PWM + subsystem. It provides idiomatic wrappers and traits necessary for + writing PWM controller drivers in Rust. + + The abstractions handle resource management (like memory and reference + counting) and provide safe interfaces to the underlying C core, + allowing driver logic to be written in safe Rust. + endif diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 96160f4257fcb0..d41b1940df903b 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -73,3 +73,4 @@ obj-$(CONFIG_PWM_TWL_LED) += pwm-twl-led.o obj-$(CONFIG_PWM_VISCONTI) += pwm-visconti.o obj-$(CONFIG_PWM_VT8500) += pwm-vt8500.o obj-$(CONFIG_PWM_XILINX) += pwm-xilinx.o +obj-$(CONFIG_PWM_TH1520_RUST) += pwm_th1520.o diff --git a/drivers/pwm/pwm_th1520.rs b/drivers/pwm/pwm_th1520.rs new file mode 100644 index 00000000000000..9e43474f5123b5 --- /dev/null +++ b/drivers/pwm/pwm_th1520.rs @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2025 Samsung Electronics Co., Ltd. +// Author: Michal Wilczynski + +//! Rust T-HEAD TH1520 PWM driver + +use core::ops::Deref; +use kernel::{ + c_str, + clk::Clk, + device::{Bound, Core, Device}, + devres, + error::{code::*, Result}, + io::mem::IoMem, + math::KernelMathExt, + of, platform, + prelude::*, + pwm, time, +}; + +const MAX_PWM_NUM: u32 = 6; + +// Register offsets +const fn th1520_pwm_chn_base(n: u32) -> usize { + (n * 0x20) as usize +} +const fn th1520_pwm_ctrl(n: u32) -> usize { + th1520_pwm_chn_base(n) +} +const fn th1520_pwm_per(n: u32) -> usize { + th1520_pwm_chn_base(n) + 0x08 +} +const fn th1520_pwm_fp(n: u32) -> usize { + th1520_pwm_chn_base(n) + 0x0c +} + +// Control register bits +const PWM_START: u32 = 1 << 0; +const PWM_CFG_UPDATE: u32 = 1 << 2; +const PWM_CONTINUOUS_MODE: u32 = 1 << 5; +const PWM_FPOUT: u32 = 1 << 8; + +const TH1520_PWM_REG_SIZE: usize = 0xB0; + +/// Hardware-specific waveform representation for TH1520. +#[derive(Copy, Clone, Debug, Default)] +struct Th1520WfHw { + period_cycles: u32, + duty_cycles: u32, + ctrl_val: u32, + enabled: bool, +} + +/// The driver's private data struct. It holds all necessary devres-managed resources. +struct Th1520PwmDriverData { + iomem: devres::Devres>, + clk: Clk, +} + +impl pwm::PwmOps for Th1520PwmDriverData { + type WfHw = Th1520WfHw; + + fn get_state( + chip: &mut pwm::Chip, + pwm: &mut pwm::Device, + state: &mut pwm::State, + parent_dev: &Device, + ) -> Result { + let data: &Self = chip.drvdata().ok_or(EINVAL)?; + let hwpwm = pwm.hwpwm(); + let iomem_guard = data.iomem.access(parent_dev)?; + let iomap = iomem_guard.deref(); + let ctrl = iomap.read32(th1520_pwm_ctrl(hwpwm)); + let period_cycles = iomap.read32(th1520_pwm_per(hwpwm)); + let duty_cycles = iomap.read32(th1520_pwm_fp(hwpwm)); + + state.set_enabled(duty_cycles != 0); + + let rate_hz = data.clk.rate().as_hz(); + let period_ns = (period_cycles as u64) + .mul_div(time::NSEC_PER_SEC as u64, rate_hz as u64) + .unwrap_or(0); + state.set_period(period_ns); + + let duty_ns = (duty_cycles as u64) + .mul_div(time::NSEC_PER_SEC as u64, rate_hz as u64) + .unwrap_or(0); + state.set_duty_cycle(duty_ns); + + if (ctrl & PWM_FPOUT) != 0 { + state.set_polarity(pwm::Polarity::Normal); + } else { + state.set_polarity(pwm::Polarity::Inversed); + } + + Ok(()) + } + + fn round_waveform_tohw( + chip: &mut pwm::Chip, + pwm: &mut pwm::Device, + wf: &pwm::Waveform, + ) -> Result<(i32, Self::WfHw)> { + let data: &Self = chip.drvdata().ok_or(EINVAL)?; + let hwpwm = pwm.hwpwm(); + + if wf.duty_offset_ns != 0 { + dev_err!(chip.device(), "PWM-{}: Duty offset not supported\n", hwpwm); + return Err(ENOTSUPP); + } + + if wf.period_length_ns == 0 { + return Ok(( + 0, + Th1520WfHw { + enabled: false, + ..Default::default() + }, + )); + } + + let rate_hz = data.clk.rate().as_hz(); + + let period_cycles = wf + .period_length_ns + .mul_div(rate_hz as u64, time::NSEC_PER_SEC as u64) + .ok_or(EINVAL)?; + if period_cycles > u32::MAX as u64 { + dev_err!( + chip.device(), + "PWM-{}: Calculated period {} cycles is out of range\n", + hwpwm, + period_cycles + ); + return Err(EINVAL); + } + + let duty_cycles = wf + .duty_length_ns + .mul_div(rate_hz as u64, time::NSEC_PER_SEC as u64) + .ok_or(EINVAL)?; + if duty_cycles > period_cycles { + dev_err!( + chip.device(), + "PWM-{}: Duty {}ns > period {}ns\n", + hwpwm, + wf.duty_length_ns, + wf.period_length_ns + ); + return Err(EINVAL); + } + + let mut ctrl_val = PWM_CONTINUOUS_MODE; + if pwm.state().polarity() == pwm::Polarity::Normal { + ctrl_val |= PWM_FPOUT; + } + + let wfhw = Th1520WfHw { + period_cycles: period_cycles as u32, + duty_cycles: duty_cycles as u32, + ctrl_val, + enabled: true, + }; + + dev_dbg!( + chip.device(), + "wfhw -- Period: {}, Duty: {}, Ctrl: 0x{:x}\n", + wfhw.period_cycles, + wfhw.duty_cycles, + wfhw.ctrl_val + ); + Ok((0, wfhw)) + } + + fn write_waveform( + chip: &mut pwm::Chip, + pwm: &mut pwm::Device, + wfhw: &Self::WfHw, + parent_dev: &Device, + ) -> Result { + let data: &Self = chip.drvdata().ok_or(EINVAL)?; + let hwpwm = pwm.hwpwm(); + let iomem_guard = data.iomem.access(parent_dev)?; + let iomap = iomem_guard.deref(); + let was_enabled = pwm.state().enabled(); + + if !wfhw.enabled { + if was_enabled { + let mut ctrl = iomap.read32(th1520_pwm_ctrl(hwpwm)); + + ctrl &= !PWM_CFG_UPDATE; + + iomap.write32(ctrl, th1520_pwm_ctrl(hwpwm)); + iomap.write32(0, th1520_pwm_fp(hwpwm)); + iomap.write32(ctrl | PWM_CFG_UPDATE, th1520_pwm_ctrl(hwpwm)); + } + return Ok(()); + } + + let ctrl = wfhw.ctrl_val & !PWM_CFG_UPDATE; + + iomap.write32(ctrl, th1520_pwm_ctrl(hwpwm)); + iomap.write32(wfhw.period_cycles, th1520_pwm_per(hwpwm)); + iomap.write32(wfhw.duty_cycles, th1520_pwm_fp(hwpwm)); + iomap.write32(wfhw.ctrl_val | PWM_CFG_UPDATE, th1520_pwm_ctrl(hwpwm)); + + if !was_enabled { + iomap.write32(wfhw.ctrl_val | PWM_START, th1520_pwm_ctrl(hwpwm)); + } + + Ok(()) + } +} + +impl Drop for Th1520PwmDriverData { + fn drop(&mut self) { + self.clk.disable_unprepare(); + } +} + +static TH1520_PWM_OPS: pwm::PwmOpsVTable = pwm::create_pwm_ops::(); + +struct Th1520PwmPlatformDriver { + _registration: pwm::Registration, +} + +kernel::of_device_table!( + OF_TABLE, + MODULE_OF_TABLE, + ::IdInfo, + [(of::DeviceId::new(c_str!("thead,th1520-pwm")), ())] +); + +impl platform::Driver for Th1520PwmPlatformDriver { + type IdInfo = (); + const OF_ID_TABLE: Option> = Some(&OF_TABLE); + + fn probe( + pdev: &platform::Device, + _id_info: Option<&Self::IdInfo>, + ) -> Result>> { + let dev = pdev.as_ref(); + let resource = pdev.resource(0).ok_or(ENODEV)?; + let iomem = pdev.ioremap_resource_sized::(resource)?; + let clk = Clk::get(pdev.as_ref(), None)?; + + clk.prepare_enable()?; + + let rate_hz = clk.rate().as_hz(); + if rate_hz == 0 { + dev_err!(dev, "Clock rate is zero\n"); + return Err(EINVAL); + } + + if rate_hz > time::NSEC_PER_SEC as usize { + dev_err!( + dev, + "Clock rate {} Hz is too high, not supported.\n", + rate_hz + ); + return Err(ERANGE); + } + + let chip = pwm::Chip::new(dev, MAX_PWM_NUM, 0)?; + + let drvdata = KBox::new(Th1520PwmDriverData { iomem, clk }, GFP_KERNEL)?; + chip.set_drvdata(drvdata); + + let registration = pwm::Registration::new(chip, &TH1520_PWM_OPS)?; + + Ok(KBox::new( + Th1520PwmPlatformDriver { + _registration: registration, + }, + GFP_KERNEL, + )? + .into()) + } +} + +kernel::module_platform_driver! { + type: Th1520PwmPlatformDriver, + name: "pwm-th1520", + author: "Michal Wilczynski ", + description: "T-HEAD TH1520 PWM driver", + license: "GPL v2", +} diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h index bc494745f67b82..e794dada5537c5 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -63,6 +63,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c index 0f1b5d11598591..73902d8bd87e93 100644 --- a/rust/helpers/helpers.c +++ b/rust/helpers/helpers.c @@ -30,6 +30,7 @@ #include "platform.c" #include "pci.c" #include "pid_namespace.c" +#include "pwm.c" #include "rbtree.c" #include "rcu.c" #include "refcount.c" diff --git a/rust/helpers/pwm.c b/rust/helpers/pwm.c new file mode 100644 index 00000000000000..d75c588863685d --- /dev/null +++ b/rust/helpers/pwm.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2025 Samsung Electronics Co., Ltd. +// Author: Michal Wilczynski + +#include + +struct device *rust_helper_pwmchip_parent(const struct pwm_chip *chip) +{ + return pwmchip_parent(chip); +} + +void *rust_helper_pwmchip_get_drvdata(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +void rust_helper_pwmchip_set_drvdata(struct pwm_chip *chip, void *data) +{ + pwmchip_set_drvdata(chip, data); +} diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 6b4774b2b1c37f..ce1d08b14e4569 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -105,6 +105,8 @@ pub mod security; pub mod seq_file; pub mod sizes; mod static_assert; +#[cfg(CONFIG_RUST_PWM_ABSTRACTIONS)] +pub mod pwm; #[doc(hidden)] pub mod std_vendor; pub mod str; diff --git a/rust/kernel/pwm.rs b/rust/kernel/pwm.rs new file mode 100644 index 00000000000000..b5839703c49ed7 --- /dev/null +++ b/rust/kernel/pwm.rs @@ -0,0 +1,864 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2025 Samsung Electronics Co., Ltd. +// Author: Michal Wilczynski + +//! PWM (Pulse Width Modulator) abstractions. +//! +//! This module provides safe Rust abstractions for working with the Linux +//! kernel's PWM subsystem, leveraging types generated by `bindgen` +//! from `` and `drivers/pwm/core.c`. + +use crate::{ + bindings, + device::{self, Bound}, + error::{self, to_result, Result}, + prelude::*, + str::CStr, + types::{ARef, AlwaysRefCounted, ForeignOwnable, Opaque}, +}; +use core::{marker::PhantomData, mem::ManuallyDrop, ptr::NonNull}; + +/// Maximum size for the hardware-specific waveform representation buffer. +/// From C: #define WFHWSIZE 20 +pub const WFHW_MAX_SIZE: usize = 20; + +/// PWM polarity. Mirrors `enum pwm_polarity`. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum Polarity { + /// Normal polarity (duty cycle defines the high period of the signal) + Normal, + /// Inversed polarity (duty cycle defines the low period of the signal) + Inversed, +} + +impl From for Polarity { + fn from(polarity: bindings::pwm_polarity) -> Self { + match polarity { + bindings::pwm_polarity_PWM_POLARITY_NORMAL => Polarity::Normal, + bindings::pwm_polarity_PWM_POLARITY_INVERSED => Polarity::Inversed, + _ => Polarity::Normal, + } + } +} + +impl From for bindings::pwm_polarity { + fn from(polarity: Polarity) -> Self { + match polarity { + Polarity::Normal => bindings::pwm_polarity_PWM_POLARITY_NORMAL, + Polarity::Inversed => bindings::pwm_polarity_PWM_POLARITY_INVERSED, + } + } +} + +/// Represents a PWM waveform configuration. Mirrors struct pwm_waveform. +#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)] +pub struct Waveform { + /// Total duration of one complete PWM cycle, in nanoseconds. + pub period_length_ns: u64, + + /// Duration the PWM signal is in its "active" state during one period, + /// in nanoseconds. For a typical "normal" polarity configuration where active is high, + /// this represents the high time of the signal. + pub duty_length_ns: u64, + + /// Time delay from the start of the period to the first active edge + /// of the duty cycle, in nanoseconds. For many simpler PWM configurations, + /// this is 0, meaning the duty cycle's active phase starts at the beginning + /// of the period. + pub duty_offset_ns: u64, +} + +impl From for Waveform { + fn from(wf: bindings::pwm_waveform) -> Self { + Waveform { + period_length_ns: wf.period_length_ns, + duty_length_ns: wf.duty_length_ns, + duty_offset_ns: wf.duty_offset_ns, + } + } +} + +impl From for bindings::pwm_waveform { + fn from(wf: Waveform) -> Self { + bindings::pwm_waveform { + period_length_ns: wf.period_length_ns, + duty_length_ns: wf.duty_length_ns, + duty_offset_ns: wf.duty_offset_ns, + } + } +} + +/// Wrapper for board-dependent PWM arguments (`struct pwm_args`). +#[repr(transparent)] +pub struct Args(Opaque); + +impl Args { + /// Creates an `Args` wrapper from a C struct pointer. + /// + /// # Safety + /// The caller must ensure that `c_args_ptr` is a valid, non-null pointer + /// to `bindings::pwm_args` and that the pointed-to data is valid + /// for the duration of this function call (as data is copied). + unsafe fn from_c_ptr(c_args_ptr: *const bindings::pwm_args) -> Self { + // SAFETY: Caller guarantees `c_args_ptr` is valid. We dereference it to copy. + Args(Opaque::new(unsafe { *c_args_ptr })) + } + + /// Returns the period of the PWM signal in nanoseconds. + pub fn period(&self) -> u64 { + // SAFETY: `self.0.get()` returns a pointer to the `bindings::pwm_args` + // managed by the `Opaque` wrapper. This pointer is guaranteed to be + // valid and aligned for the lifetime of `self` because `Opaque` owns a copy. + unsafe { (*self.0.get()).period } + } + + /// Returns the polarity of the PWM signal. + pub fn polarity(&self) -> Polarity { + // SAFETY: `self.0.get()` returns a pointer to the `bindings::pwm_args` + // managed by the `Opaque` wrapper. This pointer is guaranteed to be + // valid and aligned for the lifetime of `self`. + let raw_polarity = unsafe { (*self.0.get()).polarity }; + Polarity::from(raw_polarity) + } +} + +/// Wrapper for PWM state (`struct pwm_state`). +#[repr(transparent)] +pub struct State(bindings::pwm_state); + +impl Default for State { + fn default() -> Self { + Self::new() + } +} + +impl State { + /// Creates a new zeroed `State`. + pub fn new() -> Self { + State(bindings::pwm_state::default()) + } + + /// Creates a `State` wrapper by taking ownership of a C `pwm_state` value. + pub(crate) fn from_c(c_state: bindings::pwm_state) -> Self { + State(c_state) + } + + /// Gets the period of the PWM signal in nanoseconds. + pub fn period(&self) -> u64 { + self.0.period + } + + /// Sets the period of the PWM signal in nanoseconds. + pub fn set_period(&mut self, period_ns: u64) { + self.0.period = period_ns; + } + + /// Gets the duty cycle of the PWM signal in nanoseconds. + pub fn duty_cycle(&self) -> u64 { + self.0.duty_cycle + } + + /// Sets the duty cycle of the PWM signal in nanoseconds. + pub fn set_duty_cycle(&mut self, duty_ns: u64) { + self.0.duty_cycle = duty_ns; + } + + /// Returns `true` if the PWM signal is enabled. + pub fn enabled(&self) -> bool { + self.0.enabled + } + + /// Sets the enabled state of the PWM signal. + pub fn set_enabled(&mut self, enabled: bool) { + self.0.enabled = enabled; + } + + /// Gets the polarity of the PWM signal. + pub fn polarity(&self) -> Polarity { + Polarity::from(self.0.polarity) + } + + /// Sets the polarity of the PWM signal. + pub fn set_polarity(&mut self, polarity: Polarity) { + self.0.polarity = polarity.into(); + } + + /// Returns `true` if the PWM signal is configured for power usage hint. + pub fn usage_power(&self) -> bool { + self.0.usage_power + } + + /// Sets the power usage hint for the PWM signal. + pub fn set_usage_power(&mut self, usage_power: bool) { + self.0.usage_power = usage_power; + } +} + +/// Wrapper for a PWM device/channel (`struct pwm_device`). +#[repr(transparent)] +pub struct Device(Opaque); + +impl Device { + /// Creates a temporary `&mut Device` from a raw C pointer for use in callbacks. + /// + /// It returns a mutable reference (`&mut Self`) because the underlying C APIs + /// for PWM operations use non-const pointers (`struct pwm_device *`). This + /// signals that the functions in the vtable are permitted to mutate the + /// device's state (e.g., by writing to hardware registers). Using `&mut` + /// allows the `PwmOps` trait to accurately model this behavior and leverage + /// Rust's aliasing rules for greater safety. + /// + /// # Safety + /// The caller must ensure that `ptr` is a valid, non-null pointer to + /// `bindings::pwm_device` that is properly initialized. + /// The `pwm_device` must remain valid for the lifetime `'a`. + /// The caller must also ensure that Rust's aliasing rules are upheld. + pub(crate) unsafe fn from_ptr<'a>(ptr: *mut bindings::pwm_device) -> &'a mut Self { + // SAFETY: Caller guarantees `ptr` is valid and meets lifetime/aliasing. + // `Self` is `#[repr(transparent)]`, so casting is valid. + unsafe { &mut *ptr.cast::() } + } + + /// Returns a raw pointer to the underlying `pwm_device`. + fn as_raw(&self) -> *mut bindings::pwm_device { + self.0.get() + } + + /// Gets the hardware PWM index for this device within its chip. + pub fn hwpwm(&self) -> u32 { + // SAFETY: `self.as_raw()` provides a valid pointer for `self`'s lifetime. + unsafe { (*self.as_raw()).hwpwm } + } + + /// Gets a reference to the parent `Chip` that this device belongs to. + pub fn chip(&self) -> &Chip { + // SAFETY: `self.as_raw()` provides a valid pointer. (*self.as_raw()).chip + // is assumed to be a valid pointer to `pwm_chip` managed by the kernel. + // Chip::from_ptr's safety conditions must be met. + unsafe { Chip::from_ptr((*self.as_raw()).chip) } + } + + /// Gets the label for this PWM device, if any. + pub fn label(&self) -> Option<&CStr> { + // SAFETY: self.as_raw() provides a valid pointer. + let label_ptr = unsafe { (*self.as_raw()).label }; + if label_ptr.is_null() { + None + } else { + // SAFETY: label_ptr is non-null and points to a C string + // managed by the kernel, valid for the lifetime of the PWM device. + Some(unsafe { CStr::from_char_ptr(label_ptr) }) + } + } + + /// Gets a copy of the board-dependent arguments for this PWM device. + pub fn args(&self) -> Args { + // SAFETY: self.as_raw() gives a valid pointer to `pwm_device`. + // The `args` field is a valid `pwm_args` struct embedded within `pwm_device`. + // `Args::from_c_ptr`'s safety conditions are met by providing this pointer. + unsafe { Args::from_c_ptr(&(*self.as_raw()).args) } + } + + /// Gets a copy of the current state of this PWM device. + pub fn state(&self) -> State { + // SAFETY: `self.as_raw()` gives a valid pointer. `(*self.as_raw()).state` + // is a valid `pwm_state` struct. `State::from_c` copies this data. + State::from_c(unsafe { (*self.as_raw()).state }) + } + + /// Returns `true` if the PWM signal is currently enabled based on its state. + pub fn is_enabled(&self) -> bool { + self.state().enabled() + } +} + +/// Wrapper for a PWM chip/controller (`struct pwm_chip`). +#[repr(transparent)] +pub struct Chip(Opaque); + +impl Chip { + /// Creates a temporary `&mut Chip` from a raw C pointer for use in callbacks. + /// + /// It returns a mutable reference (`&mut Self`) because the underlying C APIs + /// for PWM operations use non-const pointers (`struct pwm_chip *`). This + /// signals that the functions in the vtable are permitted to mutate the + /// chip's state (e.g., by calling `set_drvdata` or through operations that + /// modify hardware registers). Using `&mut` is essential for these cases. + /// + /// # Safety + /// The caller must ensure that `ptr` is a valid, non-null pointer to + /// `bindings::pwm_chip` that is properly initialized. + /// The `pwm_chip` must remain valid for the lifetime `'a`. + /// The caller must also ensure that Rust's aliasing rules are upheld. + pub(crate) unsafe fn from_ptr<'a>(ptr: *mut bindings::pwm_chip) -> &'a mut Self { + // SAFETY: Caller guarantees `ptr` is valid and meets lifetime/aliasing. + // `Self` is `#[repr(transparent)]`, so casting is valid. + unsafe { &mut *ptr.cast::() } + } + + /// Returns a raw pointer to the underlying `pwm_chip`. + pub(crate) fn as_raw(&self) -> *mut bindings::pwm_chip { + self.0.get() + } + + /// Gets the number of PWM channels (hardware PWMs) on this chip. + pub fn npwm(&self) -> u32 { + // SAFETY: `self.as_raw()` provides a valid pointer for `self`'s lifetime. + unsafe { (*self.as_raw()).npwm } + } + + /// Returns `true` if the chip supports atomic operations for configuration. + pub fn is_atomic(&self) -> bool { + // SAFETY: `self.as_raw()` provides a valid pointer for `self`'s lifetime. + unsafe { (*self.as_raw()).atomic } + } + + /// Returns a reference to the embedded `struct device` abstraction. + pub fn device(&self) -> &device::Device { + // SAFETY: `self.as_raw()` provides a valid pointer to `bindings::pwm_chip`. + // The `dev` field is an instance of `bindings::device` embedded within `pwm_chip`. + // Taking a pointer to this embedded field is valid. + // `device::Device` is `#[repr(transparent)]`. + // The lifetime of the returned reference is tied to `self`. + let dev_field_ptr = unsafe { core::ptr::addr_of!((*self.as_raw()).dev) }; + // SAFETY: `dev_field_ptr` is a valid pointer to `bindings::device`. + // Casting and dereferencing is safe due to `repr(transparent)` and lifetime. + unsafe { &*(dev_field_ptr.cast::()) } + } + + /// Returns a reference to the parent device of this PWM chip's device. + pub fn parent_device(&self) -> Option<&device::Device> { + self.device().parent() + } + + /// Gets the *typed* driver-specific data associated with this chip's embedded device. + pub fn drvdata(&self) -> Option<&T> { + // SAFETY: `self.as_raw()` gives a valid pwm_chip pointer. + // `bindings::pwmchip_get_drvdata` is the C function to retrieve driver data. + let ptr = unsafe { bindings::pwmchip_get_drvdata(self.as_raw()) }; + if ptr.is_null() { + None + } else { + // SAFETY: `ptr` is non-null. Caller ensures `T` is the correct type. + // Lifetime of data is managed by the driver that set it. + unsafe { Some(&*(ptr.cast::())) } + } + } + + /// Sets the *typed* driver-specific data associated with this chip's embedded device. + pub fn set_drvdata(&self, data: T) { + // SAFETY: `self.as_raw()` gives a valid pwm_chip pointer. + // `bindings::pwmchip_set_drvdata` is the C function to set driver data. + // `data.into_foreign()` provides a valid `*mut c_void`. + unsafe { bindings::pwmchip_set_drvdata(self.as_raw(), data.into_foreign().cast()) } + } + + /// Allocates and wraps a PWM chip using `bindings::pwmchip_alloc`. + /// + /// Returns an `ARef` managing the chip's lifetime via refcounting + /// on its embedded `struct device`. + pub fn new(parent_dev: &device::Device, npwm: u32, sizeof_priv: usize) -> Result> { + // SAFETY: `parent_device_for_dev_field.as_raw()` is valid. + // `bindings::pwmchip_alloc` returns a valid `*mut bindings::pwm_chip` (refcount 1) + // or an ERR_PTR. + let c_chip_ptr_raw = + unsafe { bindings::pwmchip_alloc(parent_dev.as_raw(), npwm, sizeof_priv) }; + + let c_chip_ptr: *mut bindings::pwm_chip = error::from_err_ptr(c_chip_ptr_raw)?; + + // Cast the `*mut bindings::pwm_chip` to `*mut Chip`. This is valid because + // `Chip` is `repr(transparent)` over `Opaque`, and + // `Opaque` is `repr(transparent)` over `T`. + let chip_ptr_as_self = c_chip_ptr.cast::(); + + // SAFETY: `chip_ptr_as_self` points to a valid `Chip` (layout-compatible with + // `bindings::pwm_chip`) whose embedded device has refcount 1. + // `ARef::from_raw` takes this pointer and manages it via `AlwaysRefCounted`. + Ok(unsafe { ARef::from_raw(NonNull::new_unchecked(chip_ptr_as_self)) }) + } +} + +// SAFETY: Implements refcounting for `Chip` using the embedded `struct device`. +unsafe impl AlwaysRefCounted for Chip { + #[inline] + fn inc_ref(&self) { + // SAFETY: `self.0.get()` points to a valid `pwm_chip` because `self` exists. + // The embedded `dev` is valid. `get_device` increments its refcount. + unsafe { + bindings::get_device(core::ptr::addr_of_mut!((*self.0.get()).dev)); + } + } + + #[inline] + unsafe fn dec_ref(obj: NonNull) { + let c_chip_ptr = obj.cast::().as_ptr(); + + // SAFETY: `obj` is a valid pointer to a `Chip` (and thus `bindings::pwm_chip`) + // with a non-zero refcount. `put_device` handles decrement and final release. + unsafe { + bindings::put_device(core::ptr::addr_of_mut!((*c_chip_ptr).dev)); + } + } +} + +// SAFETY: `Chip` is a wrapper around `*mut bindings::pwm_chip`. The underlying C +// structure's state is managed and synchronized by the kernel's device model +// and PWM core locking mechanisms. Therefore, it is safe to move the `Chip` +// wrapper (and the pointer it contains) across threads. +unsafe impl Send for Chip {} + +// SAFETY: It is safe for multiple threads to have shared access (`&Chip`) because +// the `Chip` data is immutable from the Rust side without holding the appropriate +// kernel locks, which the C core is responsible for. Any interior mutability is +// handled and synchronized by the C kernel code. +unsafe impl Sync for Chip {} + +/// Manages the registration of a PWM chip, ensuring `pwmchip_remove` is called on drop. +pub struct Registration { + chip: ManuallyDrop>, +} + +impl Registration { + /// Registers a PWM chip (obtained via `Chip::new`) with the PWM subsystem. + /// + /// Takes an `ARef`. On `Drop` of the returned `Registration` object, + /// `pwmchip_remove` is called for the chip. + pub fn new(chip: ARef, ops_vtable: &'static PwmOpsVTable) -> Result { + // Get the raw C pointer from ARef. + let c_chip_ptr = chip.as_raw().cast::(); + + // SAFETY: `c_chip_ptr` is valid (guaranteed by ARef existing). + // `ops_vtable.as_raw()` provides a valid `*const bindings::pwm_ops`. + // `bindings::__pwmchip_add` preconditions (valid pointers, ops set on chip) are met. + unsafe { + (*c_chip_ptr).ops = ops_vtable.as_raw(); + to_result(bindings::__pwmchip_add(c_chip_ptr, core::ptr::null_mut()))?; + } + Ok(Registration { + chip: ManuallyDrop::new(chip), + }) + } +} + +impl Drop for Registration { + fn drop(&mut self) { + let chip = &**self.chip; + let chip_raw: *mut bindings::pwm_chip = chip.as_raw(); + + // SAFETY: `chip_raw` points to a chip that was successfully registered via `Self::new`. + // `bindings::pwmchip_remove` is the correct C function to unregister it. + unsafe { + bindings::pwmchip_remove(chip_raw); + ManuallyDrop::drop(&mut self.chip); // Drops the ARef + } + } +} + +/// Trait defining the operations for a PWM driver. +pub trait PwmOps: 'static + Sized { + /// The driver-specific hardware representation of a waveform. + /// This type must be `Copy`, `Default`, and fit within `WFHW_MAX_SIZE`. + type WfHw: Copy + Default; + + /// Optional hook to atomically apply a new PWM config. + fn apply( + _chip: &mut Chip, + _pwm: &mut Device, + _state: &State, + _parent_dev: &device::Device, + ) -> Result { + Err(ENOTSUPP) + } + + /// Optional hook for when a PWM device is requested. + fn request(_chip: &mut Chip, _pwm: &mut Device, _parent_dev: &device::Device) -> Result { + Ok(()) + } + + /// Optional hook for when a PWM device is freed. + fn free(_chip: &mut Chip, _pwm: &mut Device, _parent_dev: &device::Device) {} + + /// Optional hook for capturing a PWM signal. + fn capture( + _chip: &mut Chip, + _pwm: &mut Device, + _result: &mut bindings::pwm_capture, + _timeout: usize, + _parent_dev: &device::Device, + ) -> Result { + Err(ENOTSUPP) + } + + /// Optional hook to get the current hardware state. + fn get_state( + _chip: &mut Chip, + _pwm: &mut Device, + _state: &mut State, + _parent_dev: &device::Device, + ) -> Result { + Err(ENOTSUPP) + } + + /// Convert a generic waveform to the hardware-specific representation. + /// This is typically a pure calculation and does not perform I/O. + fn round_waveform_tohw( + _chip: &mut Chip, + _pwm: &mut Device, + _wf: &Waveform, + ) -> Result<(i32, Self::WfHw)> { + Err(ENOTSUPP) + } + + /// Convert a hardware-specific representation back to a generic waveform. + /// This is typically a pure calculation and does not perform I/O. + fn round_waveform_fromhw( + _chip: &mut Chip, + _pwm: &Device, + _wfhw: &Self::WfHw, + _wf: &mut Waveform, + ) -> Result { + Err(ENOTSUPP) + } + + /// Read the current hardware configuration into the hardware-specific representation. + fn read_waveform( + _chip: &mut Chip, + _pwm: &mut Device, + _parent_dev: &device::Device, + ) -> Result { + Err(ENOTSUPP) + } + + /// Write a hardware-specific waveform configuration to the hardware. + fn write_waveform( + _chip: &mut Chip, + _pwm: &mut Device, + _wfhw: &Self::WfHw, + _parent_dev: &device::Device, + ) -> Result { + Err(ENOTSUPP) + } +} +/// Bridges Rust `PwmOps` to the C `pwm_ops` vtable. +struct Adapter { + _p: PhantomData, +} + +impl Adapter { + /// # Safety + /// `wfhw_ptr` must be valid for writes of `size_of::()` bytes. + unsafe fn serialize_wfhw(wfhw: &T::WfHw, wfhw_ptr: *mut core::ffi::c_void) -> Result { + let size = core::mem::size_of::(); + if size > WFHW_MAX_SIZE { + return Err(EINVAL); + } + + // SAFETY: The caller ensures `wfhw_ptr` is valid for `size` bytes. + unsafe { + core::ptr::copy_nonoverlapping(wfhw as *const _ as *const u8, wfhw_ptr.cast(), size); + } + + Ok(()) + } + + /// # Safety + /// `wfhw_ptr` must be valid for reads of `size_of::()` bytes. + unsafe fn deserialize_wfhw(wfhw_ptr: *const core::ffi::c_void) -> Result { + let size = core::mem::size_of::(); + if size > WFHW_MAX_SIZE { + return Err(EINVAL); + } + + let mut wfhw = T::WfHw::default(); + // SAFETY: The caller ensures `wfhw_ptr` is valid for `size` bytes. + unsafe { + core::ptr::copy_nonoverlapping(wfhw_ptr.cast(), &mut wfhw as *mut _ as *mut u8, size); + } + + Ok(wfhw) + } + + /// # Safety + /// C-callback. Pointers from C must be valid. + unsafe extern "C" fn apply_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + s: *const bindings::pwm_state, + ) -> i32 { + // SAFETY: This block relies on the function's safety contract: the C caller + // provides valid pointers. `Chip::from_ptr` and `Device::from_ptr` are `unsafe fn` + // whose preconditions are met by this contract. + let (chip, pwm) = unsafe { (Chip::from_ptr(c), Device::from_ptr(p)) }; + let parent_dev = match chip.parent_device() { + Some(dev) => dev, + None => { + return EINVAL.to_errno(); + } + }; + + // SAFETY: The PWM core guarantees callbacks only happen on a live, bound device. + let bound_parent = + unsafe { &*(parent_dev as *const device::Device as *const device::Device) }; + + // SAFETY: The state provided by the callback is guaranteed to be valid + let state = State::from_c(unsafe { *s }); + match T::apply(chip, pwm, &state, bound_parent) { + Ok(()) => 0, + Err(e) => e.to_errno(), + } + } + + /// # Safety + /// C-callback. Pointers from C must be valid. + unsafe extern "C" fn request_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + ) -> i32 { + // SAFETY: PWM core guarentees `c` and `p` are valid pointers. + let (chip, pwm) = unsafe { (Chip::from_ptr(c), Device::from_ptr(p)) }; + let parent_dev = match chip.parent_device() { + Some(dev) => dev, + None => { + return EINVAL.to_errno(); + } + }; + + let bound_parent = + // SAFETY: The PWM core guarantees the device is bound during callbacks. + unsafe { &*(parent_dev as *const device::Device as *const device::Device) }; + match T::request(chip, pwm, bound_parent) { + Ok(()) => 0, + Err(e) => e.to_errno(), + } + } + + /// # Safety + /// C-callback. Pointers from C must be valid. + unsafe extern "C" fn free_callback(c: *mut bindings::pwm_chip, p: *mut bindings::pwm_device) { + // SAFETY: Relies on the function's contract that `c` and `p` are valid pointers. + let (chip, pwm) = unsafe { (Chip::from_ptr(c), Device::from_ptr(p)) }; + let parent_dev = match chip.parent_device() { + Some(dev) => dev, + None => { + return; + } + }; + + let bound_parent = + // SAFETY: The PWM core guarantees the device is bound during callbacks. + unsafe { &*(parent_dev as *const device::Device as *const device::Device) }; + T::free(chip, pwm, bound_parent); + } + + /// # Safety + /// C-callback. Pointers from C must be valid. + unsafe extern "C" fn capture_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + res: *mut bindings::pwm_capture, + timeout: usize, + ) -> i32 { + // SAFETY: Relies on the function's contract that `c` and `p` are valid pointers. + let (chip, pwm, result) = unsafe { (Chip::from_ptr(c), Device::from_ptr(p), &mut *res) }; + let parent_dev = match chip.parent_device() { + Some(dev) => dev, + None => { + return EINVAL.to_errno(); + } + }; + + let bound_parent = + // SAFETY: The PWM core guarantees the device is bound during callbacks. + unsafe { &*(parent_dev as *const device::Device as *const device::Device) }; + match T::capture(chip, pwm, result, timeout, bound_parent) { + Ok(()) => 0, + Err(e) => e.to_errno(), + } + } + + /// # Safety + /// C-callback. Pointers from C must be valid. + unsafe extern "C" fn get_state_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + s: *mut bindings::pwm_state, + ) -> i32 { + // SAFETY: Relies on the function's contract that `c` and `p` are valid pointers. + let (chip, pwm) = unsafe { (Chip::from_ptr(c), Device::from_ptr(p)) }; + let parent_dev = match chip.parent_device() { + Some(dev) => dev, + None => { + return EINVAL.to_errno(); + } + }; + let bound_parent = + // SAFETY: The PWM core guarantees the device is bound during callbacks. + unsafe { &*(parent_dev as *const device::Device as *const device::Device) }; + let mut rust_state = State::new(); + match T::get_state(chip, pwm, &mut rust_state, bound_parent) { + Ok(()) => { + // SAFETY: `s` is guaranteed valid by the C caller. + unsafe { + *s = rust_state.0; + }; + 0 + } + Err(e) => e.to_errno(), + } + } + + /// # Safety + /// C-callback. Pointers from C must be valid. + unsafe extern "C" fn round_waveform_tohw_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + w: *const bindings::pwm_waveform, + wh: *mut core::ffi::c_void, + ) -> i32 { + // SAFETY: Relies on the function's contract that `c` and `p` are valid pointers. + let (chip, pwm, wf) = + unsafe { (Chip::from_ptr(c), Device::from_ptr(p), Waveform::from(*w)) }; + match T::round_waveform_tohw(chip, pwm, &wf) { + Ok((status, wfhw)) => { + // SAFETY: `wh` is valid per this function's safety contract. + if unsafe { Self::serialize_wfhw(&wfhw, wh) }.is_err() { + return EINVAL.to_errno(); + } + status + } + Err(e) => e.to_errno(), + } + } + + /// # Safety + /// C-callback. Pointers from C must be valid. + unsafe extern "C" fn round_waveform_fromhw_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + wh: *const core::ffi::c_void, + w: *mut bindings::pwm_waveform, + ) -> i32 { + // SAFETY: Relies on the function's contract that `c` and `p` are valid pointers. + let (chip, pwm) = unsafe { (Chip::from_ptr(c), Device::from_ptr(p)) }; + // SAFETY: `deserialize_wfhw`'s safety contract is met by this function's contract. + let wfhw = match unsafe { Self::deserialize_wfhw(wh) } { + Ok(v) => v, + Err(e) => return e.to_errno(), + }; + + let mut rust_wf = Waveform::default(); + match T::round_waveform_fromhw(chip, pwm, &wfhw, &mut rust_wf) { + Ok(ret) => { + // SAFETY: `w` is guaranteed valid by the C caller. + unsafe { + *w = rust_wf.into(); + }; + ret + } + Err(e) => e.to_errno(), + } + } + + /// # Safety + /// C-callback. Pointers from C must be valid. + unsafe extern "C" fn read_waveform_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + wh: *mut core::ffi::c_void, + ) -> i32 { + // SAFETY: Relies on the function's contract that `c` and `p` are valid pointers. + let (chip, pwm) = unsafe { (Chip::from_ptr(c), Device::from_ptr(p)) }; + let parent_dev = match chip.parent_device() { + Some(dev) => dev, + None => { + return EINVAL.to_errno(); + } + }; + + let bound_parent = + // SAFETY: The PWM core guarantees the device is bound during callbacks. + unsafe { &*(parent_dev as *const device::Device as *const device::Device) }; + match T::read_waveform(chip, pwm, bound_parent) { + // SAFETY: `wh` is valid per this function's safety contract. + Ok(wfhw) => match unsafe { Self::serialize_wfhw(&wfhw, wh) } { + Ok(()) => 0, + Err(e) => e.to_errno(), + }, + Err(e) => e.to_errno(), + } + } + + /// # Safety + /// C-callback. Pointers from C must be valid. + unsafe extern "C" fn write_waveform_callback( + c: *mut bindings::pwm_chip, + p: *mut bindings::pwm_device, + wh: *const core::ffi::c_void, + ) -> i32 { + // SAFETY: Relies on the function's contract that `c` and `p` are valid pointers. + let (chip, pwm) = unsafe { (Chip::from_ptr(c), Device::from_ptr(p)) }; + let parent_dev = match chip.parent_device() { + Some(dev) => dev, + None => { + return EINVAL.to_errno(); + } + }; + + let bound_parent = + // SAFETY: The PWM core guarantees the device is bound during callbacks. + unsafe { &*(parent_dev as *const device::Device as *const device::Device) }; + // SAFETY: `wh` is valid per this function's safety contract. + let wfhw = match unsafe { Self::deserialize_wfhw(wh) } { + Ok(v) => v, + Err(e) => return e.to_errno(), + }; + match T::write_waveform(chip, pwm, &wfhw, bound_parent) { + Ok(()) => 0, + Err(e) => e.to_errno(), + } + } +} +/// VTable structure wrapper for PWM operations. Mirrors `struct pwm_ops`. +#[repr(transparent)] +pub struct PwmOpsVTable(Opaque); + +// SAFETY: PwmOpsVTable is Send. The vtable contains only function pointers +// and a size, which are simple data types that can be safely moved across +// threads. The thread-safety of calling these functions is handled by the +// kernel's locking mechanisms. +unsafe impl Send for PwmOpsVTable {} +// SAFETY: PwmOpsVTable is Sync. The vtable is immutable after it is created, +// so it can be safely referenced and accessed concurrently by multiple threads +// e.g. to read the function pointers. +unsafe impl Sync for PwmOpsVTable {} + +impl PwmOpsVTable { + /// Returns a raw pointer to the underlying `pwm_ops` struct. + pub(crate) fn as_raw(&self) -> *const bindings::pwm_ops { + self.0.get() + } +} + +/// Creates a PWM operations vtable for a type `T` that implements `PwmOps`. +/// +/// This is used to bridge Rust trait implementations to the C `struct pwm_ops` +/// expected by the kernel. +pub const fn create_pwm_ops() -> PwmOpsVTable { + // SAFETY: `core::mem::zeroed()` is unsafe. For `pwm_ops`, all fields are + // `Option` or data, so a zeroed pattern (None/0) is valid initially. + let mut ops: bindings::pwm_ops = unsafe { core::mem::zeroed() }; + + ops.apply = Some(Adapter::::apply_callback); + ops.request = Some(Adapter::::request_callback); + ops.free = Some(Adapter::::free_callback); + ops.capture = Some(Adapter::::capture_callback); + ops.get_state = Some(Adapter::::get_state_callback); + + ops.round_waveform_tohw = Some(Adapter::::round_waveform_tohw_callback); + ops.round_waveform_fromhw = Some(Adapter::::round_waveform_fromhw_callback); + ops.read_waveform = Some(Adapter::::read_waveform_callback); + ops.write_waveform = Some(Adapter::::write_waveform_callback); + ops.sizeof_wfhw = core::mem::size_of::(); + + PwmOpsVTable(Opaque::new(ops)) +}