| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-only |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * Driver for Atmel Pulse Width Modulation Controller |
|---|
| 3 | 4 | * |
|---|
| 4 | 5 | * Copyright (C) 2013 Atmel Corporation |
|---|
| 5 | 6 | * Bo Shen <voice.shen@atmel.com> |
|---|
| 6 | 7 | * |
|---|
| 7 | | - * Licensed under GPLv2. |
|---|
| 8 | + * Links to reference manuals for the supported PWM chips can be found in |
|---|
| 9 | + * Documentation/arm/microchip.rst. |
|---|
| 10 | + * |
|---|
| 11 | + * Limitations: |
|---|
| 12 | + * - Periods start with the inactive level. |
|---|
| 13 | + * - Hardware has to be stopped in general to update settings. |
|---|
| 14 | + * |
|---|
| 15 | + * Software bugs/possible improvements: |
|---|
| 16 | + * - When atmel_pwm_apply() is called with state->enabled=false a change in |
|---|
| 17 | + * state->polarity isn't honored. |
|---|
| 18 | + * - Instead of sleeping to wait for a completed period, the interrupt |
|---|
| 19 | + * functionality could be used. |
|---|
| 8 | 20 | */ |
|---|
| 9 | 21 | |
|---|
| 10 | 22 | #include <linux/clk.h> |
|---|
| .. | .. |
|---|
| 48 | 60 | #define PWMV2_CPRD 0x0C |
|---|
| 49 | 61 | #define PWMV2_CPRDUPD 0x10 |
|---|
| 50 | 62 | |
|---|
| 51 | | -/* |
|---|
| 52 | | - * Max value for duty and period |
|---|
| 53 | | - * |
|---|
| 54 | | - * Although the duty and period register is 32 bit, |
|---|
| 55 | | - * however only the LSB 16 bits are significant. |
|---|
| 56 | | - */ |
|---|
| 57 | | -#define PWM_MAX_DTY 0xFFFF |
|---|
| 58 | | -#define PWM_MAX_PRD 0xFFFF |
|---|
| 59 | | -#define PRD_MAX_PRES 10 |
|---|
| 63 | +#define PWM_MAX_PRES 10 |
|---|
| 60 | 64 | |
|---|
| 61 | 65 | struct atmel_pwm_registers { |
|---|
| 62 | 66 | u8 period; |
|---|
| .. | .. |
|---|
| 65 | 69 | u8 duty_upd; |
|---|
| 66 | 70 | }; |
|---|
| 67 | 71 | |
|---|
| 72 | +struct atmel_pwm_config { |
|---|
| 73 | + u32 period_bits; |
|---|
| 74 | +}; |
|---|
| 75 | + |
|---|
| 76 | +struct atmel_pwm_data { |
|---|
| 77 | + struct atmel_pwm_registers regs; |
|---|
| 78 | + struct atmel_pwm_config cfg; |
|---|
| 79 | +}; |
|---|
| 80 | + |
|---|
| 68 | 81 | struct atmel_pwm_chip { |
|---|
| 69 | 82 | struct pwm_chip chip; |
|---|
| 70 | 83 | struct clk *clk; |
|---|
| 71 | 84 | void __iomem *base; |
|---|
| 72 | | - const struct atmel_pwm_registers *regs; |
|---|
| 85 | + const struct atmel_pwm_data *data; |
|---|
| 73 | 86 | |
|---|
| 74 | 87 | unsigned int updated_pwms; |
|---|
| 75 | 88 | /* ISR is cleared when read, ensure only one thread does that */ |
|---|
| .. | .. |
|---|
| 98 | 111 | { |
|---|
| 99 | 112 | unsigned long base = PWM_CH_REG_OFFSET + ch * PWM_CH_REG_SIZE; |
|---|
| 100 | 113 | |
|---|
| 101 | | - return readl_relaxed(chip->base + base + offset); |
|---|
| 114 | + return atmel_pwm_readl(chip, base + offset); |
|---|
| 102 | 115 | } |
|---|
| 103 | 116 | |
|---|
| 104 | 117 | static inline void atmel_pwm_ch_writel(struct atmel_pwm_chip *chip, |
|---|
| .. | .. |
|---|
| 107 | 120 | { |
|---|
| 108 | 121 | unsigned long base = PWM_CH_REG_OFFSET + ch * PWM_CH_REG_SIZE; |
|---|
| 109 | 122 | |
|---|
| 110 | | - writel_relaxed(val, chip->base + base + offset); |
|---|
| 123 | + atmel_pwm_writel(chip, base + offset, val); |
|---|
| 111 | 124 | } |
|---|
| 112 | 125 | |
|---|
| 113 | 126 | static int atmel_pwm_calculate_cprd_and_pres(struct pwm_chip *chip, |
|---|
| .. | .. |
|---|
| 116 | 129 | { |
|---|
| 117 | 130 | struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); |
|---|
| 118 | 131 | unsigned long long cycles = state->period; |
|---|
| 132 | + int shift; |
|---|
| 119 | 133 | |
|---|
| 120 | 134 | /* Calculate the period cycles and prescale value */ |
|---|
| 121 | 135 | cycles *= clk_get_rate(atmel_pwm->clk); |
|---|
| 122 | 136 | do_div(cycles, NSEC_PER_SEC); |
|---|
| 123 | 137 | |
|---|
| 124 | | - for (*pres = 0; cycles > PWM_MAX_PRD; cycles >>= 1) |
|---|
| 125 | | - (*pres)++; |
|---|
| 138 | + /* |
|---|
| 139 | + * The register for the period length is cfg.period_bits bits wide. |
|---|
| 140 | + * So for each bit the number of clock cycles is wider divide the input |
|---|
| 141 | + * clock frequency by two using pres and shift cprd accordingly. |
|---|
| 142 | + */ |
|---|
| 143 | + shift = fls(cycles) - atmel_pwm->data->cfg.period_bits; |
|---|
| 126 | 144 | |
|---|
| 127 | | - if (*pres > PRD_MAX_PRES) { |
|---|
| 145 | + if (shift > PWM_MAX_PRES) { |
|---|
| 128 | 146 | dev_err(chip->dev, "pres exceeds the maximum value\n"); |
|---|
| 129 | 147 | return -EINVAL; |
|---|
| 148 | + } else if (shift > 0) { |
|---|
| 149 | + *pres = shift; |
|---|
| 150 | + cycles >>= *pres; |
|---|
| 151 | + } else { |
|---|
| 152 | + *pres = 0; |
|---|
| 130 | 153 | } |
|---|
| 131 | 154 | |
|---|
| 132 | 155 | *cprd = cycles; |
|---|
| .. | .. |
|---|
| 150 | 173 | struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); |
|---|
| 151 | 174 | u32 val; |
|---|
| 152 | 175 | |
|---|
| 153 | | - if (atmel_pwm->regs->duty_upd == |
|---|
| 154 | | - atmel_pwm->regs->period_upd) { |
|---|
| 176 | + if (atmel_pwm->data->regs.duty_upd == |
|---|
| 177 | + atmel_pwm->data->regs.period_upd) { |
|---|
| 155 | 178 | val = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR); |
|---|
| 156 | 179 | val &= ~PWM_CMR_UPD_CDTY; |
|---|
| 157 | 180 | atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, PWM_CMR, val); |
|---|
| 158 | 181 | } |
|---|
| 159 | 182 | |
|---|
| 160 | 183 | atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, |
|---|
| 161 | | - atmel_pwm->regs->duty_upd, cdty); |
|---|
| 184 | + atmel_pwm->data->regs.duty_upd, cdty); |
|---|
| 162 | 185 | } |
|---|
| 163 | 186 | |
|---|
| 164 | 187 | static void atmel_pwm_set_cprd_cdty(struct pwm_chip *chip, |
|---|
| .. | .. |
|---|
| 168 | 191 | struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); |
|---|
| 169 | 192 | |
|---|
| 170 | 193 | atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, |
|---|
| 171 | | - atmel_pwm->regs->duty, cdty); |
|---|
| 194 | + atmel_pwm->data->regs.duty, cdty); |
|---|
| 172 | 195 | atmel_pwm_ch_writel(atmel_pwm, pwm->hwpwm, |
|---|
| 173 | | - atmel_pwm->regs->period, cprd); |
|---|
| 196 | + atmel_pwm->data->regs.period, cprd); |
|---|
| 174 | 197 | } |
|---|
| 175 | 198 | |
|---|
| 176 | 199 | static void atmel_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm, |
|---|
| .. | .. |
|---|
| 210 | 233 | } |
|---|
| 211 | 234 | |
|---|
| 212 | 235 | static int atmel_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, |
|---|
| 213 | | - struct pwm_state *state) |
|---|
| 236 | + const struct pwm_state *state) |
|---|
| 214 | 237 | { |
|---|
| 215 | 238 | struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); |
|---|
| 216 | 239 | struct pwm_state cstate; |
|---|
| .. | .. |
|---|
| 225 | 248 | cstate.polarity == state->polarity && |
|---|
| 226 | 249 | cstate.period == state->period) { |
|---|
| 227 | 250 | cprd = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, |
|---|
| 228 | | - atmel_pwm->regs->period); |
|---|
| 251 | + atmel_pwm->data->regs.period); |
|---|
| 229 | 252 | atmel_pwm_calculate_cdty(state, cprd, &cdty); |
|---|
| 230 | 253 | atmel_pwm_update_cdty(chip, pwm, cdty); |
|---|
| 231 | 254 | return 0; |
|---|
| .. | .. |
|---|
| 272 | 295 | return 0; |
|---|
| 273 | 296 | } |
|---|
| 274 | 297 | |
|---|
| 298 | +static void atmel_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, |
|---|
| 299 | + struct pwm_state *state) |
|---|
| 300 | +{ |
|---|
| 301 | + struct atmel_pwm_chip *atmel_pwm = to_atmel_pwm_chip(chip); |
|---|
| 302 | + u32 sr, cmr; |
|---|
| 303 | + |
|---|
| 304 | + sr = atmel_pwm_readl(atmel_pwm, PWM_SR); |
|---|
| 305 | + cmr = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, PWM_CMR); |
|---|
| 306 | + |
|---|
| 307 | + if (sr & (1 << pwm->hwpwm)) { |
|---|
| 308 | + unsigned long rate = clk_get_rate(atmel_pwm->clk); |
|---|
| 309 | + u32 cdty, cprd, pres; |
|---|
| 310 | + u64 tmp; |
|---|
| 311 | + |
|---|
| 312 | + pres = cmr & PWM_CMR_CPRE_MSK; |
|---|
| 313 | + |
|---|
| 314 | + cprd = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, |
|---|
| 315 | + atmel_pwm->data->regs.period); |
|---|
| 316 | + tmp = (u64)cprd * NSEC_PER_SEC; |
|---|
| 317 | + tmp <<= pres; |
|---|
| 318 | + state->period = DIV64_U64_ROUND_UP(tmp, rate); |
|---|
| 319 | + |
|---|
| 320 | + cdty = atmel_pwm_ch_readl(atmel_pwm, pwm->hwpwm, |
|---|
| 321 | + atmel_pwm->data->regs.duty); |
|---|
| 322 | + tmp = (u64)(cprd - cdty) * NSEC_PER_SEC; |
|---|
| 323 | + tmp <<= pres; |
|---|
| 324 | + state->duty_cycle = DIV64_U64_ROUND_UP(tmp, rate); |
|---|
| 325 | + |
|---|
| 326 | + state->enabled = true; |
|---|
| 327 | + } else { |
|---|
| 328 | + state->enabled = false; |
|---|
| 329 | + } |
|---|
| 330 | + |
|---|
| 331 | + if (cmr & PWM_CMR_CPOL) |
|---|
| 332 | + state->polarity = PWM_POLARITY_INVERSED; |
|---|
| 333 | + else |
|---|
| 334 | + state->polarity = PWM_POLARITY_NORMAL; |
|---|
| 335 | +} |
|---|
| 336 | + |
|---|
| 275 | 337 | static const struct pwm_ops atmel_pwm_ops = { |
|---|
| 276 | 338 | .apply = atmel_pwm_apply, |
|---|
| 339 | + .get_state = atmel_pwm_get_state, |
|---|
| 277 | 340 | .owner = THIS_MODULE, |
|---|
| 278 | 341 | }; |
|---|
| 279 | 342 | |
|---|
| 280 | | -static const struct atmel_pwm_registers atmel_pwm_regs_v1 = { |
|---|
| 281 | | - .period = PWMV1_CPRD, |
|---|
| 282 | | - .period_upd = PWMV1_CUPD, |
|---|
| 283 | | - .duty = PWMV1_CDTY, |
|---|
| 284 | | - .duty_upd = PWMV1_CUPD, |
|---|
| 285 | | -}; |
|---|
| 286 | | - |
|---|
| 287 | | -static const struct atmel_pwm_registers atmel_pwm_regs_v2 = { |
|---|
| 288 | | - .period = PWMV2_CPRD, |
|---|
| 289 | | - .period_upd = PWMV2_CPRDUPD, |
|---|
| 290 | | - .duty = PWMV2_CDTY, |
|---|
| 291 | | - .duty_upd = PWMV2_CDTYUPD, |
|---|
| 292 | | -}; |
|---|
| 293 | | - |
|---|
| 294 | | -static const struct platform_device_id atmel_pwm_devtypes[] = { |
|---|
| 295 | | - { |
|---|
| 296 | | - .name = "at91sam9rl-pwm", |
|---|
| 297 | | - .driver_data = (kernel_ulong_t)&atmel_pwm_regs_v1, |
|---|
| 298 | | - }, { |
|---|
| 299 | | - .name = "sama5d3-pwm", |
|---|
| 300 | | - .driver_data = (kernel_ulong_t)&atmel_pwm_regs_v2, |
|---|
| 301 | | - }, { |
|---|
| 302 | | - /* sentinel */ |
|---|
| 343 | +static const struct atmel_pwm_data atmel_sam9rl_pwm_data = { |
|---|
| 344 | + .regs = { |
|---|
| 345 | + .period = PWMV1_CPRD, |
|---|
| 346 | + .period_upd = PWMV1_CUPD, |
|---|
| 347 | + .duty = PWMV1_CDTY, |
|---|
| 348 | + .duty_upd = PWMV1_CUPD, |
|---|
| 349 | + }, |
|---|
| 350 | + .cfg = { |
|---|
| 351 | + /* 16 bits to keep period and duty. */ |
|---|
| 352 | + .period_bits = 16, |
|---|
| 303 | 353 | }, |
|---|
| 304 | 354 | }; |
|---|
| 305 | | -MODULE_DEVICE_TABLE(platform, atmel_pwm_devtypes); |
|---|
| 355 | + |
|---|
| 356 | +static const struct atmel_pwm_data atmel_sama5_pwm_data = { |
|---|
| 357 | + .regs = { |
|---|
| 358 | + .period = PWMV2_CPRD, |
|---|
| 359 | + .period_upd = PWMV2_CPRDUPD, |
|---|
| 360 | + .duty = PWMV2_CDTY, |
|---|
| 361 | + .duty_upd = PWMV2_CDTYUPD, |
|---|
| 362 | + }, |
|---|
| 363 | + .cfg = { |
|---|
| 364 | + /* 16 bits to keep period and duty. */ |
|---|
| 365 | + .period_bits = 16, |
|---|
| 366 | + }, |
|---|
| 367 | +}; |
|---|
| 368 | + |
|---|
| 369 | +static const struct atmel_pwm_data mchp_sam9x60_pwm_data = { |
|---|
| 370 | + .regs = { |
|---|
| 371 | + .period = PWMV1_CPRD, |
|---|
| 372 | + .period_upd = PWMV1_CUPD, |
|---|
| 373 | + .duty = PWMV1_CDTY, |
|---|
| 374 | + .duty_upd = PWMV1_CUPD, |
|---|
| 375 | + }, |
|---|
| 376 | + .cfg = { |
|---|
| 377 | + /* 32 bits to keep period and duty. */ |
|---|
| 378 | + .period_bits = 32, |
|---|
| 379 | + }, |
|---|
| 380 | +}; |
|---|
| 306 | 381 | |
|---|
| 307 | 382 | static const struct of_device_id atmel_pwm_dt_ids[] = { |
|---|
| 308 | 383 | { |
|---|
| 309 | 384 | .compatible = "atmel,at91sam9rl-pwm", |
|---|
| 310 | | - .data = &atmel_pwm_regs_v1, |
|---|
| 385 | + .data = &atmel_sam9rl_pwm_data, |
|---|
| 311 | 386 | }, { |
|---|
| 312 | 387 | .compatible = "atmel,sama5d3-pwm", |
|---|
| 313 | | - .data = &atmel_pwm_regs_v2, |
|---|
| 388 | + .data = &atmel_sama5_pwm_data, |
|---|
| 314 | 389 | }, { |
|---|
| 315 | 390 | .compatible = "atmel,sama5d2-pwm", |
|---|
| 316 | | - .data = &atmel_pwm_regs_v2, |
|---|
| 391 | + .data = &atmel_sama5_pwm_data, |
|---|
| 392 | + }, { |
|---|
| 393 | + .compatible = "microchip,sam9x60-pwm", |
|---|
| 394 | + .data = &mchp_sam9x60_pwm_data, |
|---|
| 317 | 395 | }, { |
|---|
| 318 | 396 | /* sentinel */ |
|---|
| 319 | 397 | }, |
|---|
| 320 | 398 | }; |
|---|
| 321 | 399 | MODULE_DEVICE_TABLE(of, atmel_pwm_dt_ids); |
|---|
| 322 | 400 | |
|---|
| 323 | | -static inline const struct atmel_pwm_registers * |
|---|
| 324 | | -atmel_pwm_get_driver_data(struct platform_device *pdev) |
|---|
| 325 | | -{ |
|---|
| 326 | | - const struct platform_device_id *id; |
|---|
| 327 | | - |
|---|
| 328 | | - if (pdev->dev.of_node) |
|---|
| 329 | | - return of_device_get_match_data(&pdev->dev); |
|---|
| 330 | | - |
|---|
| 331 | | - id = platform_get_device_id(pdev); |
|---|
| 332 | | - |
|---|
| 333 | | - return (struct atmel_pwm_registers *)id->driver_data; |
|---|
| 334 | | -} |
|---|
| 335 | | - |
|---|
| 336 | 401 | static int atmel_pwm_probe(struct platform_device *pdev) |
|---|
| 337 | 402 | { |
|---|
| 338 | | - const struct atmel_pwm_registers *regs; |
|---|
| 339 | 403 | struct atmel_pwm_chip *atmel_pwm; |
|---|
| 340 | 404 | struct resource *res; |
|---|
| 341 | 405 | int ret; |
|---|
| 342 | 406 | |
|---|
| 343 | | - regs = atmel_pwm_get_driver_data(pdev); |
|---|
| 344 | | - if (!regs) |
|---|
| 345 | | - return -ENODEV; |
|---|
| 346 | | - |
|---|
| 347 | 407 | atmel_pwm = devm_kzalloc(&pdev->dev, sizeof(*atmel_pwm), GFP_KERNEL); |
|---|
| 348 | 408 | if (!atmel_pwm) |
|---|
| 349 | 409 | return -ENOMEM; |
|---|
| 410 | + |
|---|
| 411 | + mutex_init(&atmel_pwm->isr_lock); |
|---|
| 412 | + atmel_pwm->data = of_device_get_match_data(&pdev->dev); |
|---|
| 413 | + atmel_pwm->updated_pwms = 0; |
|---|
| 350 | 414 | |
|---|
| 351 | 415 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
|---|
| 352 | 416 | atmel_pwm->base = devm_ioremap_resource(&pdev->dev, res); |
|---|
| .. | .. |
|---|
| 365 | 429 | |
|---|
| 366 | 430 | atmel_pwm->chip.dev = &pdev->dev; |
|---|
| 367 | 431 | atmel_pwm->chip.ops = &atmel_pwm_ops; |
|---|
| 368 | | - |
|---|
| 369 | | - if (pdev->dev.of_node) { |
|---|
| 370 | | - atmel_pwm->chip.of_xlate = of_pwm_xlate_with_flags; |
|---|
| 371 | | - atmel_pwm->chip.of_pwm_n_cells = 3; |
|---|
| 372 | | - } |
|---|
| 373 | | - |
|---|
| 432 | + atmel_pwm->chip.of_xlate = of_pwm_xlate_with_flags; |
|---|
| 433 | + atmel_pwm->chip.of_pwm_n_cells = 3; |
|---|
| 374 | 434 | atmel_pwm->chip.base = -1; |
|---|
| 375 | 435 | atmel_pwm->chip.npwm = 4; |
|---|
| 376 | | - atmel_pwm->regs = regs; |
|---|
| 377 | | - atmel_pwm->updated_pwms = 0; |
|---|
| 378 | | - mutex_init(&atmel_pwm->isr_lock); |
|---|
| 379 | 436 | |
|---|
| 380 | 437 | ret = pwmchip_add(&atmel_pwm->chip); |
|---|
| 381 | 438 | if (ret < 0) { |
|---|
| .. | .. |
|---|
| 407 | 464 | .name = "atmel-pwm", |
|---|
| 408 | 465 | .of_match_table = of_match_ptr(atmel_pwm_dt_ids), |
|---|
| 409 | 466 | }, |
|---|
| 410 | | - .id_table = atmel_pwm_devtypes, |
|---|
| 411 | 467 | .probe = atmel_pwm_probe, |
|---|
| 412 | 468 | .remove = atmel_pwm_remove, |
|---|
| 413 | 469 | }; |
|---|