Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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