drivers/pwm/pwm-gpio.c

Source file repositories/reference/linux-study-clean/drivers/pwm/pwm-gpio.c

File Facts

System
Linux kernel
Corpus path
drivers/pwm/pwm-gpio.c
Extension
.c
Size
5727 bytes
Lines
241
Domain
Driver Families
Bucket
drivers/pwm
Inferred role
Driver Families: implementation source
Status
source implementation candidate

Why This File Exists

Repeatable hardware-adapter layer. Deep compatibility for every driver is out of scope; this atlas records patterns, probe lifecycles, bus glue, IRQ/DMA usage, and links back to core abstractions.

Dependency Surface

Detected Declarations

Annotated Snippet

struct pwm_gpio {
	struct hrtimer gpio_timer;
	struct gpio_desc *gpio;
	struct pwm_state state;
	struct pwm_state next_state;

	/* Protect internal state between pwm_ops and hrtimer */
	spinlock_t lock;

	bool changing;
	bool running;
	bool level;
};

static void pwm_gpio_round(struct pwm_state *dest, const struct pwm_state *src)
{
	u64 dividend;
	u32 remainder;

	*dest = *src;

	/* Round down to hrtimer resolution */
	dividend = dest->period;
	remainder = do_div(dividend, hrtimer_resolution);
	dest->period -= remainder;

	dividend = dest->duty_cycle;
	remainder = do_div(dividend, hrtimer_resolution);
	dest->duty_cycle -= remainder;
}

static u64 pwm_gpio_toggle(struct pwm_gpio *gpwm, bool level)
{
	const struct pwm_state *state = &gpwm->state;
	bool invert = state->polarity == PWM_POLARITY_INVERSED;

	gpwm->level = level;
	gpiod_set_value(gpwm->gpio, gpwm->level ^ invert);

	if (!state->duty_cycle || state->duty_cycle == state->period) {
		gpwm->running = false;
		return 0;
	}

	gpwm->running = true;
	return level ? state->duty_cycle : state->period - state->duty_cycle;
}

static enum hrtimer_restart pwm_gpio_timer(struct hrtimer *gpio_timer)
{
	struct pwm_gpio *gpwm = container_of(gpio_timer, struct pwm_gpio,
					     gpio_timer);
	u64 next_toggle;
	bool new_level;

	guard(spinlock_irqsave)(&gpwm->lock);

	/* Apply new state at end of current period */
	if (!gpwm->level && gpwm->changing) {
		gpwm->changing = false;
		gpwm->state = gpwm->next_state;
		new_level = !!gpwm->state.duty_cycle;
	} else {
		new_level = !gpwm->level;
	}

	next_toggle = pwm_gpio_toggle(gpwm, new_level);
	if (next_toggle)
		hrtimer_forward(gpio_timer, hrtimer_get_expires(gpio_timer),
				ns_to_ktime(next_toggle));

	return next_toggle ? HRTIMER_RESTART : HRTIMER_NORESTART;
}

static int pwm_gpio_apply(struct pwm_chip *chip, struct pwm_device *pwm,
			  const struct pwm_state *state)
{
	struct pwm_gpio *gpwm = pwmchip_get_drvdata(chip);
	bool invert = state->polarity == PWM_POLARITY_INVERSED;

	if (state->duty_cycle && state->duty_cycle < hrtimer_resolution)
		return -EINVAL;

	if (state->duty_cycle != state->period &&
	    (state->period - state->duty_cycle < hrtimer_resolution))
		return -EINVAL;

	if (!state->enabled) {
		hrtimer_cancel(&gpwm->gpio_timer);
	} else if (!gpwm->running) {

Annotation

Implementation Notes