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