drivers/pwm/pwm-sunplus.c

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

File Facts

System
Linux kernel
Corpus path
drivers/pwm/pwm-sunplus.c
Extension
.c
Size
6677 bytes
Lines
234
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 sunplus_pwm {
	void __iomem *base;
	struct clk *clk;
};

static inline struct sunplus_pwm *to_sunplus_pwm(struct pwm_chip *chip)
{
	return pwmchip_get_drvdata(chip);
}

static int sunplus_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
			     const struct pwm_state *state)
{
	struct sunplus_pwm *priv = to_sunplus_pwm(chip);
	u32 dd_freq, duty, mode0, mode1;
	u64 clk_rate;

	if (state->polarity != pwm->state.polarity)
		return -EINVAL;

	if (!state->enabled) {
		/* disable pwm channel output */
		mode0 = readl(priv->base + SP7021_PWM_MODE0);
		mode0 &= ~SP7021_PWM_MODE0_PWMEN(pwm->hwpwm);
		writel(mode0, priv->base + SP7021_PWM_MODE0);
		/* disable pwm channel clk source */
		mode1 = readl(priv->base + SP7021_PWM_MODE1);
		mode1 &= ~SP7021_PWM_MODE1_CNT_EN(pwm->hwpwm);
		writel(mode1, priv->base + SP7021_PWM_MODE1);
		return 0;
	}

	clk_rate = clk_get_rate(priv->clk);

	/*
	 * The following calculations might overflow if clk is bigger
	 * than 256 GHz. In practise it's 202.5MHz, so this limitation
	 * is only theoretic.
	 */
	if (clk_rate > (u64)SP7021_PWM_FREQ_SCALER * NSEC_PER_SEC)
		return -EINVAL;

	/*
	 * With clk_rate limited above we have dd_freq <= state->period,
	 * so this cannot overflow.
	 */
	dd_freq = mul_u64_u64_div_u64(clk_rate, state->period, (u64)SP7021_PWM_FREQ_SCALER
				* NSEC_PER_SEC);

	if (dd_freq == 0)
		return -EINVAL;

	if (dd_freq > SP7021_PWM_FREQ_MAX)
		dd_freq = SP7021_PWM_FREQ_MAX;

	writel(dd_freq, priv->base + SP7021_PWM_FREQ(pwm->hwpwm));

	/* cal and set pwm duty */
	mode0 = readl(priv->base + SP7021_PWM_MODE0);
	mode0 |= SP7021_PWM_MODE0_PWMEN(pwm->hwpwm);
	mode1 = readl(priv->base + SP7021_PWM_MODE1);
	mode1 |= SP7021_PWM_MODE1_CNT_EN(pwm->hwpwm);
	if (state->duty_cycle == state->period) {
		/* PWM channel output = high */
		mode0 |= SP7021_PWM_MODE0_BYPASS(pwm->hwpwm);
		duty = SP7021_PWM_DUTY_DD_SEL(pwm->hwpwm) | SP7021_PWM_DUTY_MAX;
	} else {
		mode0 &= ~SP7021_PWM_MODE0_BYPASS(pwm->hwpwm);
		/*
		 * duty_ns <= period_ns 27 bits, clk_rate 28 bits, won't overflow.
		 */
		duty = mul_u64_u64_div_u64(state->duty_cycle, clk_rate,
					   (u64)dd_freq * NSEC_PER_SEC);
		duty = SP7021_PWM_DUTY_DD_SEL(pwm->hwpwm) | duty;
	}
	writel(duty, priv->base + SP7021_PWM_DUTY(pwm->hwpwm));
	writel(mode1, priv->base + SP7021_PWM_MODE1);
	writel(mode0, priv->base + SP7021_PWM_MODE0);

	return 0;
}

static int sunplus_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
				 struct pwm_state *state)
{
	struct sunplus_pwm *priv = to_sunplus_pwm(chip);
	u32 mode0, dd_freq, duty;
	u64 clk_rate;

	mode0 = readl(priv->base + SP7021_PWM_MODE0);

Annotation

Implementation Notes