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
83struct airoha_pwm_bucket {
84 /* Concurrent access protected by PWM core */
85 int used;
86 u32 period_ticks;
87 u32 duty_ticks;
88};
89
90struct 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
110static 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
134static u32 airoha_pwm_get_period_ticks_from_ns(u32 period_ns)
135{
136 return period_ns / AIROHA_PWM_PERIOD_TICK_NS;
137}
138
139static 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
144static u32 airoha_pwm_get_period_ns_from_ticks(u32 period_tick)
145{
146 return period_tick * AIROHA_PWM_PERIOD_TICK_NS;
147}
148
149static 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
161static 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
196static 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
268static 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
281static 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
319static 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
357static 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
421static 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
449static 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
490static 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
505static 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
546static 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
571static const struct pwm_ops airoha_pwm_ops = {
572 .apply = airoha_pwm_apply,
573 .get_state = airoha_pwm_get_state,
574};
575
576static 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
601static const struct of_device_id airoha_pwm_of_match[] = {
602 { .compatible = "airoha,en7581-pwm" },
603 { /* sentinel */ }
604};
605MODULE_DEVICE_TABLE(of, airoha_pwm_of_match);
606
607static 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};
615module_platform_driver(airoha_pwm_driver);
616
617MODULE_AUTHOR("Lorenzo Bianconi <lorenzo@kernel.org>");
618MODULE_AUTHOR("Markus Gothe <markus.gothe@genexis.eu>");
619MODULE_AUTHOR("Benjamin Larsson <benjamin.larsson@genexis.eu>");
620MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
621MODULE_DESCRIPTION("Airoha EN7581 PWM driver");
622MODULE_LICENSE("GPL");
623

source code of linux/drivers/pwm/pwm-airoha.c