1 // SPDX-License-Identifier: GPL-2.0 2 // Copyright (c) 2025 Samsung Electronics Co., Ltd. 3 // Author: Michal Wilczynski <m.wilczynski@samsung.com> 4 5 //! Rust T-HEAD TH1520 PWM driver 6 //! 7 //! Limitations: 8 //! - The period and duty cycle are controlled by 32-bit hardware registers, 9 //! limiting the maximum resolution. 10 //! - The driver supports continuous output mode only; one-shot mode is not 11 //! implemented. 12 //! - The controller hardware provides up to 6 PWM channels. 13 //! - Reconfiguration is glitch free - new period and duty cycle values are 14 //! latched and take effect at the start of the next period. 15 //! - Polarity is handled via a simple hardware inversion bit; arbitrary 16 //! duty cycle offsets are not supported. 17 //! - Disabling a channel is achieved by configuring its duty cycle to zero to 18 //! produce a static low output. Clearing the `start` does not reliably 19 //! force the static inactive level defined by the `INACTOUT` bit. Hence 20 //! this method is not used in this driver. 21 //! 22 23 use core::ops::Deref; 24 use kernel::{ 25 clk::Clk, 26 device::{Bound, Core, Device}, 27 devres, 28 io::{ 29 mem::IoMem, 30 Io, // 31 }, 32 of, platform, 33 prelude::*, 34 pwm, time, 35 }; 36 37 const TH1520_MAX_PWM_NUM: u32 = 6; 38 39 // Register offsets 40 const fn th1520_pwm_chn_base(n: u32) -> usize { 41 (n * 0x20) as usize 42 } 43 44 const fn th1520_pwm_ctrl(n: u32) -> usize { 45 th1520_pwm_chn_base(n) 46 } 47 48 const fn th1520_pwm_per(n: u32) -> usize { 49 th1520_pwm_chn_base(n) + 0x08 50 } 51 52 const fn th1520_pwm_fp(n: u32) -> usize { 53 th1520_pwm_chn_base(n) + 0x0c 54 } 55 56 // Control register bits 57 const TH1520_PWM_START: u32 = 1 << 0; 58 const TH1520_PWM_CFG_UPDATE: u32 = 1 << 2; 59 const TH1520_PWM_CONTINUOUS_MODE: u32 = 1 << 5; 60 const TH1520_PWM_FPOUT: u32 = 1 << 8; 61 62 const TH1520_PWM_REG_SIZE: usize = 0xB0; 63 64 fn ns_to_cycles(ns: u64, rate_hz: u64) -> u64 { 65 const NSEC_PER_SEC_U64: u64 = time::NSEC_PER_SEC as u64; 66 67 (match ns.checked_mul(rate_hz) { 68 Some(product) => product, 69 None => u64::MAX, 70 }) / NSEC_PER_SEC_U64 71 } 72 73 fn cycles_to_ns(cycles: u64, rate_hz: u64) -> u64 { 74 const NSEC_PER_SEC_U64: u64 = time::NSEC_PER_SEC as u64; 75 76 // TODO: Replace with a kernel helper like `mul_u64_u64_div_u64_roundup` 77 // once available in Rust. 78 let numerator = cycles 79 .saturating_mul(NSEC_PER_SEC_U64) 80 .saturating_add(rate_hz - 1); 81 82 numerator / rate_hz 83 } 84 85 /// Hardware-specific waveform representation for TH1520. 86 #[derive(Copy, Clone, Debug, Default)] 87 struct Th1520WfHw { 88 period_cycles: u32, 89 duty_cycles: u32, 90 ctrl_val: u32, 91 enabled: bool, 92 } 93 94 /// The driver's private data struct. It holds all necessary devres managed resources. 95 #[pin_data(PinnedDrop)] 96 struct Th1520PwmDriverData { 97 #[pin] 98 iomem: devres::Devres<IoMem<TH1520_PWM_REG_SIZE>>, 99 clk: Clk, 100 } 101 102 // This `unsafe` implementation is a temporary necessity because the underlying `kernel::clk::Clk` 103 // type does not yet expose `Send` and `Sync` implementations. This block should be removed 104 // as soon as the clock abstraction provides these guarantees directly. 105 // TODO: Remove those unsafe impl's when Clk will support them itself. 106 107 // SAFETY: The `devres` framework requires the driver's private data to be `Send` and `Sync`. 108 // We can guarantee this because the PWM core synchronizes all callbacks, preventing concurrent 109 // access to the contained `iomem` and `clk` resources. 110 unsafe impl Send for Th1520PwmDriverData {} 111 112 // SAFETY: The same reasoning applies as for `Send`. The PWM core's synchronization 113 // guarantees that it is safe for multiple threads to have shared access (`&self`) 114 // to the driver data during callbacks. 115 unsafe impl Sync for Th1520PwmDriverData {} 116 117 impl pwm::PwmOps for Th1520PwmDriverData { 118 type WfHw = Th1520WfHw; 119 120 fn round_waveform_tohw( 121 chip: &pwm::Chip<Self>, 122 _pwm: &pwm::Device, 123 wf: &pwm::Waveform, 124 ) -> Result<pwm::RoundedWaveform<Self::WfHw>> { 125 let data = chip.drvdata(); 126 let mut status = 0; 127 128 if wf.period_length_ns == 0 { 129 dev_dbg!(chip.device(), "Requested period is 0, disabling PWM.\n"); 130 131 return Ok(pwm::RoundedWaveform { 132 status: 0, 133 hardware_waveform: Th1520WfHw { 134 enabled: false, 135 ..Default::default() 136 }, 137 }); 138 } 139 140 let rate_hz = data.clk.rate().as_hz() as u64; 141 142 let mut period_cycles = ns_to_cycles(wf.period_length_ns, rate_hz).min(u64::from(u32::MAX)); 143 144 if period_cycles == 0 { 145 dev_dbg!( 146 chip.device(), 147 "Requested period {} ns is too small for clock rate {} Hz, rounding up.\n", 148 wf.period_length_ns, 149 rate_hz 150 ); 151 152 period_cycles = 1; 153 status = 1; 154 } 155 156 let mut duty_cycles = ns_to_cycles(wf.duty_length_ns, rate_hz).min(u64::from(u32::MAX)); 157 158 let mut ctrl_val = TH1520_PWM_CONTINUOUS_MODE; 159 160 let is_inversed = wf.duty_length_ns > 0 161 && wf.duty_offset_ns > 0 162 && wf.duty_offset_ns >= wf.period_length_ns.saturating_sub(wf.duty_length_ns); 163 if is_inversed { 164 duty_cycles = period_cycles - duty_cycles; 165 } else { 166 ctrl_val |= TH1520_PWM_FPOUT; 167 } 168 169 let wfhw = Th1520WfHw { 170 // The cast is safe because the value was clamped with `.min(u64::from(u32::MAX))`. 171 period_cycles: period_cycles as u32, 172 duty_cycles: duty_cycles as u32, 173 ctrl_val, 174 enabled: true, 175 }; 176 177 dev_dbg!( 178 chip.device(), 179 "Requested: {}/{} ns [+{} ns] -> HW: {}/{} cycles, ctrl 0x{:x}, rate {} Hz\n", 180 wf.duty_length_ns, 181 wf.period_length_ns, 182 wf.duty_offset_ns, 183 wfhw.duty_cycles, 184 wfhw.period_cycles, 185 wfhw.ctrl_val, 186 rate_hz 187 ); 188 189 Ok(pwm::RoundedWaveform { 190 status, 191 hardware_waveform: wfhw, 192 }) 193 } 194 195 fn round_waveform_fromhw( 196 chip: &pwm::Chip<Self>, 197 _pwm: &pwm::Device, 198 wfhw: &Self::WfHw, 199 wf: &mut pwm::Waveform, 200 ) -> Result { 201 let data = chip.drvdata(); 202 let rate_hz = data.clk.rate().as_hz() as u64; 203 204 if wfhw.period_cycles == 0 { 205 dev_dbg!( 206 chip.device(), 207 "HW state has zero period, reporting as disabled.\n" 208 ); 209 *wf = pwm::Waveform::default(); 210 return Ok(()); 211 } 212 213 wf.period_length_ns = cycles_to_ns(u64::from(wfhw.period_cycles), rate_hz); 214 215 let duty_cycles = u64::from(wfhw.duty_cycles); 216 217 if (wfhw.ctrl_val & TH1520_PWM_FPOUT) != 0 { 218 wf.duty_length_ns = cycles_to_ns(duty_cycles, rate_hz); 219 wf.duty_offset_ns = 0; 220 } else { 221 let period_cycles = u64::from(wfhw.period_cycles); 222 let original_duty_cycles = period_cycles.saturating_sub(duty_cycles); 223 224 // For an inverted signal, `duty_length_ns` is the high time (period - low_time). 225 wf.duty_length_ns = cycles_to_ns(original_duty_cycles, rate_hz); 226 // The offset is the initial low time, which is what the hardware register provides. 227 wf.duty_offset_ns = cycles_to_ns(duty_cycles, rate_hz); 228 } 229 230 Ok(()) 231 } 232 233 fn read_waveform( 234 chip: &pwm::Chip<Self>, 235 pwm: &pwm::Device, 236 parent_dev: &Device<Bound>, 237 ) -> Result<Self::WfHw> { 238 let data = chip.drvdata(); 239 let hwpwm = pwm.hwpwm(); 240 let iomem_accessor = data.iomem.access(parent_dev)?; 241 let iomap = iomem_accessor.deref(); 242 243 let ctrl = iomap.try_read32(th1520_pwm_ctrl(hwpwm))?; 244 let period_cycles = iomap.try_read32(th1520_pwm_per(hwpwm))?; 245 let duty_cycles = iomap.try_read32(th1520_pwm_fp(hwpwm))?; 246 247 let wfhw = Th1520WfHw { 248 period_cycles, 249 duty_cycles, 250 ctrl_val: ctrl, 251 enabled: duty_cycles != 0, 252 }; 253 254 dev_dbg!( 255 chip.device(), 256 "PWM-{}: read_waveform: Read hw state - period: {}, duty: {}, ctrl: 0x{:x}, enabled: {}", 257 hwpwm, 258 wfhw.period_cycles, 259 wfhw.duty_cycles, 260 wfhw.ctrl_val, 261 wfhw.enabled 262 ); 263 264 Ok(wfhw) 265 } 266 267 fn write_waveform( 268 chip: &pwm::Chip<Self>, 269 pwm: &pwm::Device, 270 wfhw: &Self::WfHw, 271 parent_dev: &Device<Bound>, 272 ) -> Result { 273 let data = chip.drvdata(); 274 let hwpwm = pwm.hwpwm(); 275 let iomem_accessor = data.iomem.access(parent_dev)?; 276 let iomap = iomem_accessor.deref(); 277 let duty_cycles = iomap.try_read32(th1520_pwm_fp(hwpwm))?; 278 let was_enabled = duty_cycles != 0; 279 280 if !wfhw.enabled { 281 dev_dbg!(chip.device(), "PWM-{}: Disabling channel.\n", hwpwm); 282 if was_enabled { 283 iomap.try_write32(wfhw.ctrl_val, th1520_pwm_ctrl(hwpwm))?; 284 iomap.try_write32(0, th1520_pwm_fp(hwpwm))?; 285 iomap.try_write32( 286 wfhw.ctrl_val | TH1520_PWM_CFG_UPDATE, 287 th1520_pwm_ctrl(hwpwm), 288 )?; 289 } 290 return Ok(()); 291 } 292 293 iomap.try_write32(wfhw.ctrl_val, th1520_pwm_ctrl(hwpwm))?; 294 iomap.try_write32(wfhw.period_cycles, th1520_pwm_per(hwpwm))?; 295 iomap.try_write32(wfhw.duty_cycles, th1520_pwm_fp(hwpwm))?; 296 iomap.try_write32( 297 wfhw.ctrl_val | TH1520_PWM_CFG_UPDATE, 298 th1520_pwm_ctrl(hwpwm), 299 )?; 300 301 // The `TH1520_PWM_START` bit must be written in a separate, final transaction, and 302 // only when enabling the channel from a disabled state. 303 if !was_enabled { 304 iomap.try_write32(wfhw.ctrl_val | TH1520_PWM_START, th1520_pwm_ctrl(hwpwm))?; 305 } 306 307 dev_dbg!( 308 chip.device(), 309 "PWM-{}: Wrote {}/{} cycles", 310 hwpwm, 311 wfhw.duty_cycles, 312 wfhw.period_cycles, 313 ); 314 315 Ok(()) 316 } 317 } 318 319 #[pinned_drop] 320 impl PinnedDrop for Th1520PwmDriverData { 321 fn drop(self: Pin<&mut Self>) { 322 self.clk.disable_unprepare(); 323 } 324 } 325 326 struct Th1520PwmPlatformDriver; 327 328 kernel::of_device_table!( 329 OF_TABLE, 330 MODULE_OF_TABLE, 331 <Th1520PwmPlatformDriver as platform::Driver>::IdInfo, 332 [(of::DeviceId::new(c"thead,th1520-pwm"), ())] 333 ); 334 335 impl platform::Driver for Th1520PwmPlatformDriver { 336 type IdInfo = (); 337 const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE); 338 339 fn probe( 340 pdev: &platform::Device<Core>, 341 _id_info: Option<&Self::IdInfo>, 342 ) -> impl PinInit<Self, Error> { 343 let dev = pdev.as_ref(); 344 let request = pdev.io_request_by_index(0).ok_or(ENODEV)?; 345 346 let clk = Clk::get(dev, None)?; 347 348 clk.prepare_enable()?; 349 350 // TODO: Get exclusive ownership of the clock to prevent rate changes. 351 // The Rust equivalent of `clk_rate_exclusive_get()` is not yet available. 352 // This should be updated once it is implemented. 353 let rate_hz = clk.rate().as_hz(); 354 if rate_hz == 0 { 355 dev_err!(dev, "Clock rate is zero\n"); 356 return Err(EINVAL); 357 } 358 359 if rate_hz > time::NSEC_PER_SEC as usize { 360 dev_err!( 361 dev, 362 "Clock rate {} Hz is too high, not supported.\n", 363 rate_hz 364 ); 365 return Err(EINVAL); 366 } 367 368 let chip = pwm::Chip::new( 369 dev, 370 TH1520_MAX_PWM_NUM, 371 try_pin_init!(Th1520PwmDriverData { 372 iomem <- request.iomap_sized::<TH1520_PWM_REG_SIZE>(), 373 clk <- clk, 374 }), 375 )?; 376 377 chip.register()?; 378 379 Ok(Th1520PwmPlatformDriver) 380 } 381 } 382 383 kernel::module_pwm_platform_driver! { 384 type: Th1520PwmPlatformDriver, 385 name: "pwm-th1520", 386 authors: ["Michal Wilczynski <m.wilczynski@samsung.com>"], 387 description: "T-HEAD TH1520 PWM driver", 388 license: "GPL v2", 389 } 390