| 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * Copyright 2022 Markus Gothe <markus.gothe@genexis.eu> |
| 4 | * Copyright 2025 Christian Marangi <ansuelsmth@gmail.com> |
| 5 | * |
| 6 | * Limitations: |
| 7 | * - Only 8 concurrent waveform generators are available for 8 combinations of |
| 8 | * duty_cycle and period. Waveform generators are shared between 16 GPIO |
| 9 | * pins and 17 SIPO GPIO pins. |
| 10 | * - Supports only normal polarity. |
| 11 | * - On configuration the currently running period is completed. |
| 12 | * - Minimum supported period is 4 ms |
| 13 | * - Maximum supported period is 1s |
| 14 | */ |
| 15 | |
| 16 | #include <linux/array_size.h> |
| 17 | #include <linux/bitfield.h> |
| 18 | #include <linux/bitmap.h> |
| 19 | #include <linux/err.h> |
| 20 | #include <linux/io.h> |
| 21 | #include <linux/iopoll.h> |
| 22 | #include <linux/math64.h> |
| 23 | #include <linux/mfd/syscon.h> |
| 24 | #include <linux/module.h> |
| 25 | #include <linux/mod_devicetable.h> |
| 26 | #include <linux/platform_device.h> |
| 27 | #include <linux/pwm.h> |
| 28 | #include <linux/regmap.h> |
| 29 | #include <linux/types.h> |
| 30 | |
| 31 | #define AIROHA_PWM_REG_SGPIO_LED_DATA 0x0024 |
| 32 | #define AIROHA_PWM_SGPIO_LED_DATA_SHIFT_FLAG BIT(31) |
| 33 | #define AIROHA_PWM_SGPIO_LED_DATA_DATA GENMASK(16, 0) |
| 34 | |
| 35 | #define AIROHA_PWM_REG_SGPIO_CLK_DIVR 0x0028 |
| 36 | #define AIROHA_PWM_SGPIO_CLK_DIVR GENMASK(1, 0) |
| 37 | #define AIROHA_PWM_SGPIO_CLK_DIVR_32 FIELD_PREP_CONST(AIROHA_PWM_SGPIO_CLK_DIVR, 3) |
| 38 | #define AIROHA_PWM_SGPIO_CLK_DIVR_16 FIELD_PREP_CONST(AIROHA_PWM_SGPIO_CLK_DIVR, 2) |
| 39 | #define AIROHA_PWM_SGPIO_CLK_DIVR_8 FIELD_PREP_CONST(AIROHA_PWM_SGPIO_CLK_DIVR, 1) |
| 40 | #define AIROHA_PWM_SGPIO_CLK_DIVR_4 FIELD_PREP_CONST(AIROHA_PWM_SGPIO_CLK_DIVR, 0) |
| 41 | |
| 42 | #define AIROHA_PWM_REG_SGPIO_CLK_DLY 0x002c |
| 43 | |
| 44 | #define AIROHA_PWM_REG_SIPO_FLASH_MODE_CFG 0x0030 |
| 45 | #define AIROHA_PWM_SERIAL_GPIO_FLASH_MODE BIT(1) |
| 46 | #define AIROHA_PWM_SERIAL_GPIO_MODE_74HC164 BIT(0) |
| 47 | |
| 48 | #define AIROHA_PWM_REG_GPIO_FLASH_PRD_SET(_n) (0x003c + (4 * (_n))) |
| 49 | #define AIROHA_PWM_REG_GPIO_FLASH_PRD_SHIFT(_n) (16 * (_n)) |
| 50 | #define AIROHA_PWM_GPIO_FLASH_PRD_LOW GENMASK(15, 8) |
| 51 | #define AIROHA_PWM_GPIO_FLASH_PRD_HIGH GENMASK(7, 0) |
| 52 | |
| 53 | #define AIROHA_PWM_REG_GPIO_FLASH_MAP(_n) (0x004c + (4 * (_n))) |
| 54 | #define AIROHA_PWM_REG_GPIO_FLASH_MAP_SHIFT(_n) (4 * (_n)) |
| 55 | #define AIROHA_PWM_GPIO_FLASH_EN BIT(3) |
| 56 | #define AIROHA_PWM_GPIO_FLASH_SET_ID GENMASK(2, 0) |
| 57 | |
| 58 | /* Register map is equal to GPIO flash map */ |
| 59 | #define AIROHA_PWM_REG_SIPO_FLASH_MAP(_n) (0x0054 + (4 * (_n))) |
| 60 | |
| 61 | #define AIROHA_PWM_REG_CYCLE_CFG_VALUE(_n) (0x0098 + (4 * (_n))) |
| 62 | #define AIROHA_PWM_REG_CYCLE_CFG_SHIFT(_n) (8 * (_n)) |
| 63 | #define AIROHA_PWM_WAVE_GEN_CYCLE GENMASK(7, 0) |
| 64 | |
| 65 | /* GPIO/SIPO flash map handles 8 pins in one register */ |
| 66 | #define AIROHA_PWM_PINS_PER_FLASH_MAP 8 |
| 67 | /* Cycle(Period) registers handles 4 generators in one 32-bit register */ |
| 68 | #define AIROHA_PWM_BUCKET_PER_CYCLE_CFG 4 |
| 69 | /* Flash(Duty) producer handles 2 generators in one 32-bit register */ |
| 70 | #define AIROHA_PWM_BUCKET_PER_FLASH_PROD 2 |
| 71 | |
| 72 | #define AIROHA_PWM_NUM_BUCKETS 8 |
| 73 | /* |
| 74 | * The first 16 GPIO pins, GPIO0-GPIO15, are mapped into 16 PWM channels, 0-15. |
| 75 | * The SIPO GPIO pins are 17 pins which are mapped into 17 PWM channels, 16-32. |
| 76 | * However, we've only got 8 concurrent waveform generators and can therefore |
| 77 | * only use up to 8 different combinations of duty cycle and period at a time. |
| 78 | */ |
| 79 | #define AIROHA_PWM_NUM_GPIO 16 |
| 80 | #define AIROHA_PWM_NUM_SIPO 17 |
| 81 | #define AIROHA_PWM_MAX_CHANNELS (AIROHA_PWM_NUM_GPIO + AIROHA_PWM_NUM_SIPO) |
| 82 | |
| 83 | struct airoha_pwm_bucket { |
| 84 | /* Concurrent access protected by PWM core */ |
| 85 | int used; |
| 86 | u32 period_ticks; |
| 87 | u32 duty_ticks; |
| 88 | }; |
| 89 | |
| 90 | struct airoha_pwm { |
| 91 | struct regmap *regmap; |
| 92 | |
| 93 | DECLARE_BITMAP(initialized, AIROHA_PWM_MAX_CHANNELS); |
| 94 | |
| 95 | struct airoha_pwm_bucket buckets[AIROHA_PWM_NUM_BUCKETS]; |
| 96 | |
| 97 | /* Cache bucket used by each pwm channel */ |
| 98 | u8 channel_bucket[AIROHA_PWM_MAX_CHANNELS]; |
| 99 | }; |
| 100 | |
| 101 | /* The PWM hardware supports periods between 4 ms and 1 s */ |
| 102 | #define AIROHA_PWM_PERIOD_TICK_NS (4 * NSEC_PER_MSEC) |
| 103 | #define AIROHA_PWM_PERIOD_MAX_NS (1 * NSEC_PER_SEC) |
| 104 | /* It is represented internally as 1/250 s between 1 and 250. Unit is ticks. */ |
| 105 | #define AIROHA_PWM_PERIOD_MIN 1 |
| 106 | #define AIROHA_PWM_PERIOD_MAX 250 |
| 107 | /* Duty cycle is relative with 255 corresponding to 100% */ |
| 108 | #define AIROHA_PWM_DUTY_FULL 255 |
| 109 | |
| 110 | static void airoha_pwm_get_flash_map_addr_and_shift(unsigned int hwpwm, |
| 111 | u32 *addr, u32 *shift) |
| 112 | { |
| 113 | unsigned int offset, hwpwm_bit; |
| 114 | |
| 115 | if (hwpwm >= AIROHA_PWM_NUM_GPIO) { |
| 116 | unsigned int sipohwpwm = hwpwm - AIROHA_PWM_NUM_GPIO; |
| 117 | |
| 118 | offset = sipohwpwm / AIROHA_PWM_PINS_PER_FLASH_MAP; |
| 119 | hwpwm_bit = sipohwpwm % AIROHA_PWM_PINS_PER_FLASH_MAP; |
| 120 | |
| 121 | /* One FLASH_MAP register handles 8 pins */ |
| 122 | *shift = AIROHA_PWM_REG_GPIO_FLASH_MAP_SHIFT(hwpwm_bit); |
| 123 | *addr = AIROHA_PWM_REG_SIPO_FLASH_MAP(offset); |
| 124 | } else { |
| 125 | offset = hwpwm / AIROHA_PWM_PINS_PER_FLASH_MAP; |
| 126 | hwpwm_bit = hwpwm % AIROHA_PWM_PINS_PER_FLASH_MAP; |
| 127 | |
| 128 | /* One FLASH_MAP register handles 8 pins */ |
| 129 | *shift = AIROHA_PWM_REG_GPIO_FLASH_MAP_SHIFT(hwpwm_bit); |
| 130 | *addr = AIROHA_PWM_REG_GPIO_FLASH_MAP(offset); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | static u32 airoha_pwm_get_period_ticks_from_ns(u32 period_ns) |
| 135 | { |
| 136 | return period_ns / AIROHA_PWM_PERIOD_TICK_NS; |
| 137 | } |
| 138 | |
| 139 | static u32 airoha_pwm_get_duty_ticks_from_ns(u32 period_ns, u32 duty_ns) |
| 140 | { |
| 141 | return mul_u64_u32_div(a: duty_ns, AIROHA_PWM_DUTY_FULL, div: period_ns); |
| 142 | } |
| 143 | |
| 144 | static u32 airoha_pwm_get_period_ns_from_ticks(u32 period_tick) |
| 145 | { |
| 146 | return period_tick * AIROHA_PWM_PERIOD_TICK_NS; |
| 147 | } |
| 148 | |
| 149 | static u32 airoha_pwm_get_duty_ns_from_ticks(u32 period_tick, u32 duty_tick) |
| 150 | { |
| 151 | u32 period_ns = period_tick * AIROHA_PWM_PERIOD_TICK_NS; |
| 152 | |
| 153 | /* |
| 154 | * Overflow can't occur in multiplication as duty_tick is just 8 bit |
| 155 | * and period_ns is clamped to AIROHA_PWM_PERIOD_MAX_NS and fit in a |
| 156 | * u64. |
| 157 | */ |
| 158 | return DIV_U64_ROUND_UP(duty_tick * period_ns, AIROHA_PWM_DUTY_FULL); |
| 159 | } |
| 160 | |
| 161 | static int airoha_pwm_get_bucket(struct airoha_pwm *pc, int bucket, |
| 162 | u64 *period_ns, u64 *duty_ns) |
| 163 | { |
| 164 | struct regmap *map = pc->regmap; |
| 165 | u32 period_tick, duty_tick; |
| 166 | unsigned int offset; |
| 167 | u32 shift, val; |
| 168 | int ret; |
| 169 | |
| 170 | offset = bucket / AIROHA_PWM_BUCKET_PER_CYCLE_CFG; |
| 171 | shift = bucket % AIROHA_PWM_BUCKET_PER_CYCLE_CFG; |
| 172 | shift = AIROHA_PWM_REG_CYCLE_CFG_SHIFT(shift); |
| 173 | |
| 174 | ret = regmap_read(map, AIROHA_PWM_REG_CYCLE_CFG_VALUE(offset), val: &val); |
| 175 | if (ret) |
| 176 | return ret; |
| 177 | |
| 178 | period_tick = FIELD_GET(AIROHA_PWM_WAVE_GEN_CYCLE, val >> shift); |
| 179 | *period_ns = airoha_pwm_get_period_ns_from_ticks(period_tick); |
| 180 | |
| 181 | offset = bucket / AIROHA_PWM_BUCKET_PER_FLASH_PROD; |
| 182 | shift = bucket % AIROHA_PWM_BUCKET_PER_FLASH_PROD; |
| 183 | shift = AIROHA_PWM_REG_GPIO_FLASH_PRD_SHIFT(shift); |
| 184 | |
| 185 | ret = regmap_read(map, AIROHA_PWM_REG_GPIO_FLASH_PRD_SET(offset), |
| 186 | val: &val); |
| 187 | if (ret) |
| 188 | return ret; |
| 189 | |
| 190 | duty_tick = FIELD_GET(AIROHA_PWM_GPIO_FLASH_PRD_HIGH, val >> shift); |
| 191 | *duty_ns = airoha_pwm_get_duty_ns_from_ticks(period_tick, duty_tick); |
| 192 | |
| 193 | return 0; |
| 194 | } |
| 195 | |
| 196 | static int airoha_pwm_get_generator(struct airoha_pwm *pc, u32 duty_ticks, |
| 197 | u32 period_ticks) |
| 198 | { |
| 199 | int best = -ENOENT, unused = -ENOENT; |
| 200 | u32 duty_ns, best_duty_ns = 0; |
| 201 | u32 best_period_ticks = 0; |
| 202 | unsigned int i; |
| 203 | |
| 204 | duty_ns = airoha_pwm_get_duty_ns_from_ticks(period_tick: period_ticks, duty_tick: duty_ticks); |
| 205 | |
| 206 | for (i = 0; i < ARRAY_SIZE(pc->buckets); i++) { |
| 207 | struct airoha_pwm_bucket *bucket = &pc->buckets[i]; |
| 208 | u32 bucket_period_ticks = bucket->period_ticks; |
| 209 | u32 bucket_duty_ticks = bucket->duty_ticks; |
| 210 | |
| 211 | /* If found, save an unused bucket to return it later */ |
| 212 | if (!bucket->used) { |
| 213 | unused = i; |
| 214 | continue; |
| 215 | } |
| 216 | |
| 217 | /* We found a matching bucket, exit early */ |
| 218 | if (duty_ticks == bucket_duty_ticks && |
| 219 | period_ticks == bucket_period_ticks) |
| 220 | return i; |
| 221 | |
| 222 | /* |
| 223 | * Unlike duty cycle zero, which can be handled by |
| 224 | * disabling PWM, a generator is needed for full duty |
| 225 | * cycle but it can be reused regardless of period |
| 226 | */ |
| 227 | if (duty_ticks == AIROHA_PWM_DUTY_FULL && |
| 228 | bucket_duty_ticks == AIROHA_PWM_DUTY_FULL) |
| 229 | return i; |
| 230 | |
| 231 | /* |
| 232 | * With an unused bucket available, skip searching for |
| 233 | * a bucket to recycle (closer to the requested period/duty) |
| 234 | */ |
| 235 | if (unused >= 0) |
| 236 | continue; |
| 237 | |
| 238 | /* Ignore bucket with invalid period */ |
| 239 | if (bucket_period_ticks > period_ticks) |
| 240 | continue; |
| 241 | |
| 242 | /* |
| 243 | * Search for a bucket closer to the requested period |
| 244 | * that has the maximal possible period that isn't bigger |
| 245 | * than the requested period. For that period pick the maximal |
| 246 | * duty cycle that isn't bigger than the requested duty_cycle. |
| 247 | */ |
| 248 | if (bucket_period_ticks >= best_period_ticks) { |
| 249 | u32 bucket_duty_ns = airoha_pwm_get_duty_ns_from_ticks(period_tick: bucket_period_ticks, |
| 250 | duty_tick: bucket_duty_ticks); |
| 251 | |
| 252 | /* Skip bucket that goes over the requested duty */ |
| 253 | if (bucket_duty_ns > duty_ns) |
| 254 | continue; |
| 255 | |
| 256 | if (bucket_duty_ns > best_duty_ns) { |
| 257 | best_period_ticks = bucket_period_ticks; |
| 258 | best_duty_ns = bucket_duty_ns; |
| 259 | best = i; |
| 260 | } |
| 261 | } |
| 262 | } |
| 263 | |
| 264 | /* Return an unused bucket or the best one found (if ever) */ |
| 265 | return unused >= 0 ? unused : best; |
| 266 | } |
| 267 | |
| 268 | static void airoha_pwm_release_bucket_config(struct airoha_pwm *pc, |
| 269 | unsigned int hwpwm) |
| 270 | { |
| 271 | int bucket; |
| 272 | |
| 273 | /* Nothing to clear, PWM channel never used */ |
| 274 | if (!test_bit(hwpwm, pc->initialized)) |
| 275 | return; |
| 276 | |
| 277 | bucket = pc->channel_bucket[hwpwm]; |
| 278 | pc->buckets[bucket].used--; |
| 279 | } |
| 280 | |
| 281 | static int airoha_pwm_apply_bucket_config(struct airoha_pwm *pc, unsigned int bucket, |
| 282 | u32 duty_ticks, u32 period_ticks) |
| 283 | { |
| 284 | u32 mask, shift, val; |
| 285 | u32 offset; |
| 286 | int ret; |
| 287 | |
| 288 | offset = bucket / AIROHA_PWM_BUCKET_PER_CYCLE_CFG; |
| 289 | shift = bucket % AIROHA_PWM_BUCKET_PER_CYCLE_CFG; |
| 290 | shift = AIROHA_PWM_REG_CYCLE_CFG_SHIFT(shift); |
| 291 | |
| 292 | /* Configure frequency divisor */ |
| 293 | mask = AIROHA_PWM_WAVE_GEN_CYCLE << shift; |
| 294 | val = FIELD_PREP(AIROHA_PWM_WAVE_GEN_CYCLE, period_ticks) << shift; |
| 295 | ret = regmap_update_bits(map: pc->regmap, AIROHA_PWM_REG_CYCLE_CFG_VALUE(offset), |
| 296 | mask, val); |
| 297 | if (ret) |
| 298 | return ret; |
| 299 | |
| 300 | offset = bucket / AIROHA_PWM_BUCKET_PER_FLASH_PROD; |
| 301 | shift = bucket % AIROHA_PWM_BUCKET_PER_FLASH_PROD; |
| 302 | shift = AIROHA_PWM_REG_GPIO_FLASH_PRD_SHIFT(shift); |
| 303 | |
| 304 | /* Configure duty cycle */ |
| 305 | mask = AIROHA_PWM_GPIO_FLASH_PRD_HIGH << shift; |
| 306 | val = FIELD_PREP(AIROHA_PWM_GPIO_FLASH_PRD_HIGH, duty_ticks) << shift; |
| 307 | ret = regmap_update_bits(map: pc->regmap, AIROHA_PWM_REG_GPIO_FLASH_PRD_SET(offset), |
| 308 | mask, val); |
| 309 | if (ret) |
| 310 | return ret; |
| 311 | |
| 312 | mask = AIROHA_PWM_GPIO_FLASH_PRD_LOW << shift; |
| 313 | val = FIELD_PREP(AIROHA_PWM_GPIO_FLASH_PRD_LOW, |
| 314 | AIROHA_PWM_DUTY_FULL - duty_ticks) << shift; |
| 315 | return regmap_update_bits(map: pc->regmap, AIROHA_PWM_REG_GPIO_FLASH_PRD_SET(offset), |
| 316 | mask, val); |
| 317 | } |
| 318 | |
| 319 | static int airoha_pwm_consume_generator(struct airoha_pwm *pc, |
| 320 | u32 duty_ticks, u32 period_ticks, |
| 321 | unsigned int hwpwm) |
| 322 | { |
| 323 | bool config_bucket = false; |
| 324 | int bucket, ret; |
| 325 | |
| 326 | /* |
| 327 | * Search for a bucket that already satisfies duty and period |
| 328 | * or an unused one. |
| 329 | * If not found, -ENOENT is returned. |
| 330 | */ |
| 331 | bucket = airoha_pwm_get_generator(pc, duty_ticks, period_ticks); |
| 332 | if (bucket < 0) |
| 333 | return bucket; |
| 334 | |
| 335 | /* Release previous used bucket (if any) */ |
| 336 | airoha_pwm_release_bucket_config(pc, hwpwm); |
| 337 | |
| 338 | if (!pc->buckets[bucket].used) |
| 339 | config_bucket = true; |
| 340 | pc->buckets[bucket].used++; |
| 341 | |
| 342 | if (config_bucket) { |
| 343 | pc->buckets[bucket].period_ticks = period_ticks; |
| 344 | pc->buckets[bucket].duty_ticks = duty_ticks; |
| 345 | ret = airoha_pwm_apply_bucket_config(pc, bucket, |
| 346 | duty_ticks, |
| 347 | period_ticks); |
| 348 | if (ret) { |
| 349 | pc->buckets[bucket].used--; |
| 350 | return ret; |
| 351 | } |
| 352 | } |
| 353 | |
| 354 | return bucket; |
| 355 | } |
| 356 | |
| 357 | static int airoha_pwm_sipo_init(struct airoha_pwm *pc) |
| 358 | { |
| 359 | u32 val; |
| 360 | int ret; |
| 361 | |
| 362 | ret = regmap_clear_bits(map: pc->regmap, AIROHA_PWM_REG_SIPO_FLASH_MODE_CFG, |
| 363 | AIROHA_PWM_SERIAL_GPIO_MODE_74HC164); |
| 364 | if (ret) |
| 365 | return ret; |
| 366 | |
| 367 | /* Configure shift register chip clock timings, use 32x divisor */ |
| 368 | ret = regmap_write(map: pc->regmap, AIROHA_PWM_REG_SGPIO_CLK_DIVR, |
| 369 | AIROHA_PWM_SGPIO_CLK_DIVR_32); |
| 370 | if (ret) |
| 371 | return ret; |
| 372 | |
| 373 | /* |
| 374 | * Configure the shift register chip clock delay. This needs |
| 375 | * to be configured based on the chip characteristics when the SoC |
| 376 | * apply the shift register configuration. |
| 377 | * This doesn't affect actual PWM operation and is only specific to |
| 378 | * the shift register chip. |
| 379 | * |
| 380 | * For 74HC164 we set it to 0. |
| 381 | * |
| 382 | * For reference, the actual delay applied is the internal clock |
| 383 | * feed to the SGPIO chip + 1. |
| 384 | * |
| 385 | * From documentation is specified that clock delay should not be |
| 386 | * greater than (AIROHA_PWM_REG_SGPIO_CLK_DIVR / 2) - 1. |
| 387 | */ |
| 388 | ret = regmap_write(map: pc->regmap, AIROHA_PWM_REG_SGPIO_CLK_DLY, val: 0); |
| 389 | if (ret) |
| 390 | return ret; |
| 391 | |
| 392 | /* |
| 393 | * It is necessary to explicitly shift out all zeros after muxing |
| 394 | * to initialize the shift register before enabling PWM |
| 395 | * mode because in PWM mode SIPO will not start shifting until |
| 396 | * it needs to output a non-zero value (bit 31 of led_data |
| 397 | * indicates shifting in progress and it must return to zero |
| 398 | * before led_data can be written or PWM mode can be set). |
| 399 | */ |
| 400 | ret = regmap_read_poll_timeout(pc->regmap, AIROHA_PWM_REG_SGPIO_LED_DATA, val, |
| 401 | !(val & AIROHA_PWM_SGPIO_LED_DATA_SHIFT_FLAG), |
| 402 | 10, 200 * USEC_PER_MSEC); |
| 403 | if (ret) |
| 404 | return ret; |
| 405 | |
| 406 | ret = regmap_clear_bits(map: pc->regmap, AIROHA_PWM_REG_SGPIO_LED_DATA, |
| 407 | AIROHA_PWM_SGPIO_LED_DATA_DATA); |
| 408 | if (ret) |
| 409 | return ret; |
| 410 | ret = regmap_read_poll_timeout(pc->regmap, AIROHA_PWM_REG_SGPIO_LED_DATA, val, |
| 411 | !(val & AIROHA_PWM_SGPIO_LED_DATA_SHIFT_FLAG), |
| 412 | 10, 200 * USEC_PER_MSEC); |
| 413 | if (ret) |
| 414 | return ret; |
| 415 | |
| 416 | /* Set SIPO in PWM mode */ |
| 417 | return regmap_set_bits(map: pc->regmap, AIROHA_PWM_REG_SIPO_FLASH_MODE_CFG, |
| 418 | AIROHA_PWM_SERIAL_GPIO_FLASH_MODE); |
| 419 | } |
| 420 | |
| 421 | static int airoha_pwm_config_flash_map(struct airoha_pwm *pc, |
| 422 | unsigned int hwpwm, int index) |
| 423 | { |
| 424 | unsigned int addr; |
| 425 | u32 shift; |
| 426 | int ret; |
| 427 | |
| 428 | airoha_pwm_get_flash_map_addr_and_shift(hwpwm, addr: &addr, shift: &shift); |
| 429 | |
| 430 | /* negative index means disable PWM channel */ |
| 431 | if (index < 0) { |
| 432 | /* |
| 433 | * If we need to disable the PWM, we just put low the |
| 434 | * GPIO. No need to setup buckets. |
| 435 | */ |
| 436 | return regmap_clear_bits(map: pc->regmap, reg: addr, |
| 437 | AIROHA_PWM_GPIO_FLASH_EN << shift); |
| 438 | } |
| 439 | |
| 440 | ret = regmap_update_bits(map: pc->regmap, reg: addr, |
| 441 | AIROHA_PWM_GPIO_FLASH_SET_ID << shift, |
| 442 | FIELD_PREP(AIROHA_PWM_GPIO_FLASH_SET_ID, index) << shift); |
| 443 | if (ret) |
| 444 | return ret; |
| 445 | |
| 446 | return regmap_set_bits(map: pc->regmap, reg: addr, AIROHA_PWM_GPIO_FLASH_EN << shift); |
| 447 | } |
| 448 | |
| 449 | static int airoha_pwm_config(struct airoha_pwm *pc, struct pwm_device *pwm, |
| 450 | u32 period_ticks, u32 duty_ticks) |
| 451 | { |
| 452 | unsigned int hwpwm = pwm->hwpwm; |
| 453 | int bucket, ret; |
| 454 | |
| 455 | bucket = airoha_pwm_consume_generator(pc, duty_ticks, period_ticks, |
| 456 | hwpwm); |
| 457 | if (bucket < 0) |
| 458 | return bucket; |
| 459 | |
| 460 | ret = airoha_pwm_config_flash_map(pc, hwpwm, index: bucket); |
| 461 | if (ret) { |
| 462 | pc->buckets[bucket].used--; |
| 463 | return ret; |
| 464 | } |
| 465 | |
| 466 | __set_bit(hwpwm, pc->initialized); |
| 467 | pc->channel_bucket[hwpwm] = bucket; |
| 468 | |
| 469 | /* |
| 470 | * SIPO are special GPIO attached to a shift register chip. The handling |
| 471 | * of this chip is internal to the SoC that takes care of applying the |
| 472 | * values based on the flash map. To apply a new flash map, it's needed |
| 473 | * to trigger a refresh on the shift register chip. |
| 474 | * If a SIPO is getting configuring , always reinit the shift register |
| 475 | * chip to make sure the correct flash map is applied. |
| 476 | * Skip reconfiguring the shift register if the related hwpwm |
| 477 | * is disabled (as it doesn't need to be mapped). |
| 478 | */ |
| 479 | if (hwpwm >= AIROHA_PWM_NUM_GPIO) { |
| 480 | ret = airoha_pwm_sipo_init(pc); |
| 481 | if (ret) { |
| 482 | airoha_pwm_release_bucket_config(pc, hwpwm); |
| 483 | return ret; |
| 484 | } |
| 485 | } |
| 486 | |
| 487 | return 0; |
| 488 | } |
| 489 | |
| 490 | static void airoha_pwm_disable(struct airoha_pwm *pc, struct pwm_device *pwm) |
| 491 | { |
| 492 | /* Disable PWM and release the bucket */ |
| 493 | airoha_pwm_config_flash_map(pc, hwpwm: pwm->hwpwm, index: -1); |
| 494 | airoha_pwm_release_bucket_config(pc, hwpwm: pwm->hwpwm); |
| 495 | |
| 496 | __clear_bit(pwm->hwpwm, pc->initialized); |
| 497 | |
| 498 | /* If no SIPO is used, disable the shift register chip */ |
| 499 | if (!bitmap_read(map: pc->initialized, |
| 500 | AIROHA_PWM_NUM_GPIO, AIROHA_PWM_NUM_SIPO)) |
| 501 | regmap_clear_bits(map: pc->regmap, AIROHA_PWM_REG_SIPO_FLASH_MODE_CFG, |
| 502 | AIROHA_PWM_SERIAL_GPIO_FLASH_MODE); |
| 503 | } |
| 504 | |
| 505 | static int airoha_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, |
| 506 | const struct pwm_state *state) |
| 507 | { |
| 508 | struct airoha_pwm *pc = pwmchip_get_drvdata(chip); |
| 509 | u32 period_ticks, duty_ticks; |
| 510 | u32 period_ns, duty_ns; |
| 511 | |
| 512 | if (!state->enabled) { |
| 513 | airoha_pwm_disable(pc, pwm); |
| 514 | return 0; |
| 515 | } |
| 516 | |
| 517 | /* Only normal polarity is supported */ |
| 518 | if (state->polarity == PWM_POLARITY_INVERSED) |
| 519 | return -EINVAL; |
| 520 | |
| 521 | /* Exit early if period is less than minimum supported */ |
| 522 | if (state->period < AIROHA_PWM_PERIOD_TICK_NS) |
| 523 | return -EINVAL; |
| 524 | |
| 525 | /* Clamp period to MAX supported value */ |
| 526 | if (state->period > AIROHA_PWM_PERIOD_MAX_NS) |
| 527 | period_ns = AIROHA_PWM_PERIOD_MAX_NS; |
| 528 | else |
| 529 | period_ns = state->period; |
| 530 | |
| 531 | /* Validate duty to configured period */ |
| 532 | if (state->duty_cycle > period_ns) |
| 533 | duty_ns = period_ns; |
| 534 | else |
| 535 | duty_ns = state->duty_cycle; |
| 536 | |
| 537 | /* Convert period ns to ticks */ |
| 538 | period_ticks = airoha_pwm_get_period_ticks_from_ns(period_ns); |
| 539 | /* Convert period ticks to ns again for cosistent duty tick calculation */ |
| 540 | period_ns = airoha_pwm_get_period_ns_from_ticks(period_tick: period_ticks); |
| 541 | duty_ticks = airoha_pwm_get_duty_ticks_from_ns(period_ns, duty_ns); |
| 542 | |
| 543 | return airoha_pwm_config(pc, pwm, period_ticks, duty_ticks); |
| 544 | } |
| 545 | |
| 546 | static int airoha_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, |
| 547 | struct pwm_state *state) |
| 548 | { |
| 549 | struct airoha_pwm *pc = pwmchip_get_drvdata(chip); |
| 550 | int ret, hwpwm = pwm->hwpwm; |
| 551 | u32 addr, shift, val; |
| 552 | u8 bucket; |
| 553 | |
| 554 | airoha_pwm_get_flash_map_addr_and_shift(hwpwm, addr: &addr, shift: &shift); |
| 555 | |
| 556 | ret = regmap_read(map: pc->regmap, reg: addr, val: &val); |
| 557 | if (ret) |
| 558 | return ret; |
| 559 | |
| 560 | state->enabled = FIELD_GET(AIROHA_PWM_GPIO_FLASH_EN, val >> shift); |
| 561 | if (!state->enabled) |
| 562 | return 0; |
| 563 | |
| 564 | state->polarity = PWM_POLARITY_NORMAL; |
| 565 | |
| 566 | bucket = FIELD_GET(AIROHA_PWM_GPIO_FLASH_SET_ID, val >> shift); |
| 567 | return airoha_pwm_get_bucket(pc, bucket, period_ns: &state->period, |
| 568 | duty_ns: &state->duty_cycle); |
| 569 | } |
| 570 | |
| 571 | static const struct pwm_ops airoha_pwm_ops = { |
| 572 | .apply = airoha_pwm_apply, |
| 573 | .get_state = airoha_pwm_get_state, |
| 574 | }; |
| 575 | |
| 576 | static int airoha_pwm_probe(struct platform_device *pdev) |
| 577 | { |
| 578 | struct device *dev = &pdev->dev; |
| 579 | struct airoha_pwm *pc; |
| 580 | struct pwm_chip *chip; |
| 581 | int ret; |
| 582 | |
| 583 | chip = devm_pwmchip_alloc(parent: dev, AIROHA_PWM_MAX_CHANNELS, sizeof_priv: sizeof(*pc)); |
| 584 | if (IS_ERR(ptr: chip)) |
| 585 | return PTR_ERR(ptr: chip); |
| 586 | |
| 587 | chip->ops = &airoha_pwm_ops; |
| 588 | pc = pwmchip_get_drvdata(chip); |
| 589 | |
| 590 | pc->regmap = device_node_to_regmap(np: dev_of_node(dev: dev->parent)); |
| 591 | if (IS_ERR(ptr: pc->regmap)) |
| 592 | return dev_err_probe(dev, err: PTR_ERR(ptr: pc->regmap), fmt: "Failed to get PWM regmap\n" ); |
| 593 | |
| 594 | ret = devm_pwmchip_add(dev, chip); |
| 595 | if (ret) |
| 596 | return dev_err_probe(dev, err: ret, fmt: "Failed to add PWM chip\n" ); |
| 597 | |
| 598 | return 0; |
| 599 | } |
| 600 | |
| 601 | static const struct of_device_id airoha_pwm_of_match[] = { |
| 602 | { .compatible = "airoha,en7581-pwm" }, |
| 603 | { /* sentinel */ } |
| 604 | }; |
| 605 | MODULE_DEVICE_TABLE(of, airoha_pwm_of_match); |
| 606 | |
| 607 | static struct platform_driver airoha_pwm_driver = { |
| 608 | .driver = { |
| 609 | .name = "pwm-airoha" , |
| 610 | .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
| 611 | .of_match_table = airoha_pwm_of_match, |
| 612 | }, |
| 613 | .probe = airoha_pwm_probe, |
| 614 | }; |
| 615 | module_platform_driver(airoha_pwm_driver); |
| 616 | |
| 617 | MODULE_AUTHOR("Lorenzo Bianconi <lorenzo@kernel.org>" ); |
| 618 | MODULE_AUTHOR("Markus Gothe <markus.gothe@genexis.eu>" ); |
| 619 | MODULE_AUTHOR("Benjamin Larsson <benjamin.larsson@genexis.eu>" ); |
| 620 | MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>" ); |
| 621 | MODULE_DESCRIPTION("Airoha EN7581 PWM driver" ); |
| 622 | MODULE_LICENSE("GPL" ); |
| 623 | |