| 1 | |
| 2 | |
| 3 | |
| 4 |
|
| 5 | |
| 6 | |
| 7 | |
| 8 | |
| 9 | |
| 10 | |
| 11 | |
| 12 | |
| 13 | |
| 14 | |
| 15 | |
| 16 | |
| 17 | |
| 18 | |
| 19 | |
| 20 | |
| 21 | |
| 22 |
|
| 23 | use kernel::{
|
| 24 | clk::Clk,
|
| 25 | device::{
|
| 26 | Bound,
|
| 27 | Core,
|
| 28 | Device, |
| 29 | },
|
| 30 | io::{
|
| 31 | mem::DevresIoMem,
|
| 32 | Io, |
| 33 | },
|
| 34 | of,
|
| 35 | platform,
|
| 36 | prelude::*,
|
| 37 | pwm,
|
| 38 | time, |
| 39 | };
|
| 40 |
|
| 41 | const TH1520_MAX_PWM_NUM: u32 = 6;
|
| 42 |
|
| 43 | |
| 44 | const fn th1520_pwm_chn_base(n: u32) -> usize {
|
| 45 | (n * 0x20) as usize
|
| 46 | }
|
| 47 |
|
| 48 | const fn th1520_pwm_ctrl(n: u32) -> usize {
|
| 49 | th1520_pwm_chn_base(n)
|
| 50 | }
|
| 51 |
|
| 52 | const fn th1520_pwm_per(n: u32) -> usize {
|
| 53 | th1520_pwm_chn_base(n) + 0x08
|
| 54 | }
|
| 55 |
|
| 56 | const fn th1520_pwm_fp(n: u32) -> usize {
|
| 57 | th1520_pwm_chn_base(n) + 0x0c
|
| 58 | }
|
| 59 |
|
| 60 | |
| 61 | const TH1520_PWM_START: u32 = 1 << 0;
|
| 62 | const TH1520_PWM_CFG_UPDATE: u32 = 1 << 2;
|
| 63 | const TH1520_PWM_CONTINUOUS_MODE: u32 = 1 << 5;
|
| 64 | const TH1520_PWM_FPOUT: u32 = 1 << 8;
|
| 65 |
|
| 66 | const TH1520_PWM_REG_SIZE: usize = 0xB0;
|
| 67 |
|
| 68 | fn ns_to_cycles(ns: u64, rate_hz: u64) -> u64 {
|
| 69 | const NSEC_PER_SEC_U64: u64 = time::NSEC_PER_SEC as u64;
|
| 70 |
|
| 71 | ns.saturating_mul(rate_hz) / NSEC_PER_SEC_U64
|
| 72 | }
|
| 73 |
|
| 74 | fn cycles_to_ns(cycles: u32, rate_hz: u64) -> u64 {
|
| 75 | const NSEC_PER_SEC_U64: u64 = time::NSEC_PER_SEC as u64;
|
| 76 |
|
| 77 | (u64::from(cycles) * NSEC_PER_SEC_U64).div_ceil(rate_hz)
|
| 78 | }
|
| 79 |
|
| 80 | |
| 81 | #[derive(Copy, Clone, Debug, Default)]
|
| 82 | struct Th1520WfHw {
|
| 83 | period_cycles: u32,
|
| 84 | duty_cycles: u32,
|
| 85 | ctrl_val: u32,
|
| 86 | enabled: bool,
|
| 87 | }
|
| 88 |
|
| 89 | |
| 90 | #[pin_data(PinnedDrop)]
|
| 91 | struct Th1520PwmDriverData {
|
| 92 | #[pin]
|
| 93 | iomem: DevresIoMem<TH1520_PWM_REG_SIZE>,
|
| 94 | clk: Clk,
|
| 95 | }
|
| 96 |
|
| 97 | impl pwm::PwmOps for Th1520PwmDriverData {
|
| 98 | type WfHw = Th1520WfHw;
|
| 99 |
|
| 100 | fn round_waveform_tohw(
|
| 101 | chip: &pwm::Chip<Self>,
|
| 102 | _pwm: &pwm::Device,
|
| 103 | wf: &pwm::Waveform,
|
| 104 | ) -> Result<pwm::RoundedWaveform<Self::WfHw>> {
|
| 105 | let data = chip.drvdata();
|
| 106 | let mut status = 0;
|
| 107 |
|
| 108 | if wf.period_length_ns == 0 {
|
| 109 | dev_dbg!(chip.device(), "Requested period is 0, disabling PWM.\n");
|
| 110 |
|
| 111 | return Ok(pwm::RoundedWaveform {
|
| 112 | status: 0,
|
| 113 | hardware_waveform: Th1520WfHw {
|
| 114 | enabled: false,
|
| 115 | ..Default::default()
|
| 116 | },
|
| 117 | });
|
| 118 | }
|
| 119 |
|
| 120 | let rate_hz = data.clk.rate().as_hz() as u64;
|
| 121 |
|
| 122 | let mut period_cycles = ns_to_cycles(wf.period_length_ns, rate_hz).min(u64::from(u32::MAX));
|
| 123 |
|
| 124 | if period_cycles == 0 {
|
| 125 | dev_dbg!(
|
| 126 | chip.device(),
|
| 127 | "Requested period {} ns is too small for clock rate {} Hz, rounding up.\n",
|
| 128 | wf.period_length_ns,
|
| 129 | rate_hz
|
| 130 | );
|
| 131 |
|
| 132 | period_cycles = 1;
|
| 133 | status = 1;
|
| 134 | }
|
| 135 |
|
| 136 | let mut duty_cycles = ns_to_cycles(wf.duty_length_ns, rate_hz).min(u64::from(u32::MAX));
|
| 137 |
|
| 138 | let mut ctrl_val = TH1520_PWM_CONTINUOUS_MODE;
|
| 139 |
|
| 140 | let is_inversed = wf.duty_length_ns > 0
|
| 141 | && wf.duty_offset_ns > 0
|
| 142 | && wf.duty_offset_ns >= wf.period_length_ns.saturating_sub(wf.duty_length_ns);
|
| 143 | if is_inversed {
|
| 144 | duty_cycles = period_cycles - duty_cycles;
|
| 145 | } else {
|
| 146 | ctrl_val |= TH1520_PWM_FPOUT;
|
| 147 | }
|
| 148 |
|
| 149 | let wfhw = Th1520WfHw {
|
| 150 | |
| 151 | period_cycles: period_cycles as u32,
|
| 152 | duty_cycles: duty_cycles as u32,
|
| 153 | ctrl_val,
|
| 154 | enabled: true,
|
| 155 | };
|
| 156 |
|
| 157 | dev_dbg!(
|
| 158 | chip.device(),
|
| 159 | "Requested: {}/{} ns [+{} ns] -> HW: {}/{} cycles, ctrl 0x{:x}, rate {} Hz\n",
|
| 160 | wf.duty_length_ns,
|
| 161 | wf.period_length_ns,
|
| 162 | wf.duty_offset_ns,
|
| 163 | wfhw.duty_cycles,
|
| 164 | wfhw.period_cycles,
|
| 165 | wfhw.ctrl_val,
|
| 166 | rate_hz
|
| 167 | );
|
| 168 |
|
| 169 | Ok(pwm::RoundedWaveform {
|
| 170 | status,
|
| 171 | hardware_waveform: wfhw,
|
| 172 | })
|
| 173 | }
|
| 174 |
|
| 175 | fn round_waveform_fromhw(
|
| 176 | chip: &pwm::Chip<Self>,
|
| 177 | _pwm: &pwm::Device,
|
| 178 | wfhw: &Self::WfHw,
|
| 179 | wf: &mut pwm::Waveform,
|
| 180 | ) -> Result {
|
| 181 | let data = chip.drvdata();
|
| 182 | let rate_hz = data.clk.rate().as_hz() as u64;
|
| 183 |
|
| 184 | if wfhw.period_cycles == 0 {
|
| 185 | dev_dbg!(
|
| 186 | chip.device(),
|
| 187 | "HW state has zero period, reporting as disabled.\n"
|
| 188 | );
|
| 189 | *wf = pwm::Waveform::default();
|
| 190 | return Ok(());
|
| 191 | }
|
| 192 |
|
| 193 | wf.period_length_ns = cycles_to_ns(wfhw.period_cycles, rate_hz);
|
| 194 |
|
| 195 | let duty_cycles = wfhw.duty_cycles;
|
| 196 |
|
| 197 | if (wfhw.ctrl_val & TH1520_PWM_FPOUT) != 0 {
|
| 198 | wf.duty_length_ns = cycles_to_ns(duty_cycles, rate_hz);
|
| 199 | wf.duty_offset_ns = 0;
|
| 200 | } else {
|
| 201 | let period_cycles = wfhw.period_cycles;
|
| 202 | let original_duty_cycles = period_cycles.saturating_sub(duty_cycles);
|
| 203 |
|
| 204 | |
| 205 | wf.duty_length_ns = cycles_to_ns(original_duty_cycles, rate_hz);
|
| 206 | |
| 207 | wf.duty_offset_ns = cycles_to_ns(duty_cycles, rate_hz);
|
| 208 | }
|
| 209 |
|
| 210 | Ok(())
|
| 211 | }
|
| 212 |
|
| 213 | fn read_waveform(
|
| 214 | chip: &pwm::Chip<Self>,
|
| 215 | pwm: &pwm::Device,
|
| 216 | parent_dev: &Device<Bound>,
|
| 217 | ) -> Result<Self::WfHw> {
|
| 218 | let data = chip.drvdata();
|
| 219 | let hwpwm = pwm.hwpwm();
|
| 220 | let iomap = data.iomem.access(parent_dev)?;
|
| 221 |
|
| 222 | let ctrl = iomap.try_read32(th1520_pwm_ctrl(hwpwm))?;
|
| 223 | let period_cycles = iomap.try_read32(th1520_pwm_per(hwpwm))?;
|
| 224 | let duty_cycles = iomap.try_read32(th1520_pwm_fp(hwpwm))?;
|
| 225 |
|
| 226 | let wfhw = Th1520WfHw {
|
| 227 | period_cycles,
|
| 228 | duty_cycles,
|
| 229 | ctrl_val: ctrl,
|
| 230 | enabled: duty_cycles != 0,
|
| 231 | };
|
| 232 |
|
| 233 | dev_dbg!(
|
| 234 | chip.device(),
|
| 235 | "PWM-{}: read_waveform: Read hw state - period: {}, duty: {}, ctrl: 0x{:x}, enabled: {}",
|
| 236 | hwpwm,
|
| 237 | wfhw.period_cycles,
|
| 238 | wfhw.duty_cycles,
|
| 239 | wfhw.ctrl_val,
|
| 240 | wfhw.enabled
|
| 241 | );
|
| 242 |
|
| 243 | Ok(wfhw)
|
| 244 | }
|
| 245 |
|
| 246 | fn write_waveform(
|
| 247 | chip: &pwm::Chip<Self>,
|
| 248 | pwm: &pwm::Device,
|
| 249 | wfhw: &Self::WfHw,
|
| 250 | parent_dev: &Device<Bound>,
|
| 251 | ) -> Result {
|
| 252 | let data = chip.drvdata();
|
| 253 | let hwpwm = pwm.hwpwm();
|
| 254 | let iomap = data.iomem.access(parent_dev)?;
|
| 255 | let duty_cycles = iomap.try_read32(th1520_pwm_fp(hwpwm))?;
|
| 256 | let was_enabled = duty_cycles != 0;
|
| 257 |
|
| 258 | if !wfhw.enabled {
|
| 259 | dev_dbg!(chip.device(), "PWM-{}: Disabling channel.\n", hwpwm);
|
| 260 | if was_enabled {
|
| 261 | iomap.try_write32(wfhw.ctrl_val, th1520_pwm_ctrl(hwpwm))?;
|
| 262 | iomap.try_write32(0, th1520_pwm_fp(hwpwm))?;
|
| 263 | iomap.try_write32(
|
| 264 | wfhw.ctrl_val | TH1520_PWM_CFG_UPDATE,
|
| 265 | th1520_pwm_ctrl(hwpwm),
|
| 266 | )?;
|
| 267 | }
|
| 268 | return Ok(());
|
| 269 | }
|
| 270 |
|
| 271 | iomap.try_write32(wfhw.ctrl_val, th1520_pwm_ctrl(hwpwm))?;
|
| 272 | iomap.try_write32(wfhw.period_cycles, th1520_pwm_per(hwpwm))?;
|
| 273 | iomap.try_write32(wfhw.duty_cycles, th1520_pwm_fp(hwpwm))?;
|
| 274 | iomap.try_write32(
|
| 275 | wfhw.ctrl_val | TH1520_PWM_CFG_UPDATE,
|
| 276 | th1520_pwm_ctrl(hwpwm),
|
| 277 | )?;
|
| 278 |
|
| 279 | |
| 280 | |
| 281 | if !was_enabled {
|
| 282 | iomap.try_write32(wfhw.ctrl_val | TH1520_PWM_START, th1520_pwm_ctrl(hwpwm))?;
|
| 283 | }
|
| 284 |
|
| 285 | dev_dbg!(
|
| 286 | chip.device(),
|
| 287 | "PWM-{}: Wrote {}/{} cycles",
|
| 288 | hwpwm,
|
| 289 | wfhw.duty_cycles,
|
| 290 | wfhw.period_cycles,
|
| 291 | );
|
| 292 |
|
| 293 | Ok(())
|
| 294 | }
|
| 295 | }
|
| 296 |
|
| 297 | #[pinned_drop]
|
| 298 | impl PinnedDrop for Th1520PwmDriverData {
|
| 299 | fn drop(self: Pin<&mut Self>) {
|
| 300 | self.clk.disable_unprepare();
|
| 301 | }
|
| 302 | }
|
| 303 |
|
| 304 | struct Th1520PwmPlatformDriver;
|
| 305 |
|
| 306 | kernel::of_device_table!(
|
| 307 | OF_TABLE,
|
| 308 | <Th1520PwmPlatformDriver as platform::Driver>::IdInfo,
|
| 309 | [(of::DeviceId::new(c"thead,th1520-pwm"), ())]
|
| 310 | );
|
| 311 |
|
| 312 | impl platform::Driver for Th1520PwmPlatformDriver {
|
| 313 | type IdInfo = ();
|
| 314 | type Data<'bound> = Self;
|
| 315 | const OF_ID_TABLE: Option<of::IdTable<Self::IdInfo>> = Some(&OF_TABLE);
|
| 316 |
|
| 317 | fn probe<'bound>(
|
| 318 | pdev: &'bound platform::Device<Core<'_>>,
|
| 319 | _id_info: Option<&'bound Self::IdInfo>,
|
| 320 | ) -> impl PinInit<Self, Error> + 'bound {
|
| 321 | let dev = pdev.as_ref();
|
| 322 | let request = pdev.io_request_by_index(0).ok_or(ENODEV)?;
|
| 323 |
|
| 324 | let clk = Clk::get(dev, None)?;
|
| 325 |
|
| 326 | clk.prepare_enable()?;
|
| 327 |
|
| 328 | |
| 329 | |
| 330 | |
| 331 | let rate_hz = clk.rate().as_hz();
|
| 332 | if rate_hz == 0 {
|
| 333 | dev_err!(dev, "Clock rate is zero\n");
|
| 334 | return Err(EINVAL);
|
| 335 | }
|
| 336 |
|
| 337 | if rate_hz > time::NSEC_PER_SEC as usize {
|
| 338 | dev_err!(
|
| 339 | dev,
|
| 340 | "Clock rate {} Hz is too high, not supported.\n",
|
| 341 | rate_hz
|
| 342 | );
|
| 343 | return Err(EINVAL);
|
| 344 | }
|
| 345 |
|
| 346 | let chip = pwm::Chip::new(
|
| 347 | dev,
|
| 348 | TH1520_MAX_PWM_NUM,
|
| 349 | try_pin_init!(Th1520PwmDriverData {
|
| 350 | iomem <- request.iomap_sized::<TH1520_PWM_REG_SIZE>()?.into_devres(),
|
| 351 | clk <- clk,
|
| 352 | }),
|
| 353 | )?;
|
| 354 |
|
| 355 | chip.register()?;
|
| 356 |
|
| 357 | Ok(Th1520PwmPlatformDriver)
|
| 358 | }
|
| 359 | }
|
| 360 |
|
| 361 | kernel::module_pwm_platform_driver! {
|
| 362 | type: Th1520PwmPlatformDriver,
|
| 363 | name: "pwm-th1520",
|
| 364 | authors: ["Michal Wilczynski <[email protected]>"],
|
| 365 | description: "T-HEAD TH1520 PWM driver",
|
| 366 | license: "GPL v2",
|
| 367 | }
|