drivers/pwm/pwm-ipq.c

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

File Facts

System
Linux kernel
Corpus path
drivers/pwm/pwm-ipq.c
Extension
.c
Size
7576 bytes
Lines
265
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 ipq_pwm_chip {
	void __iomem *mem;
	unsigned long clk_rate;
};

static struct ipq_pwm_chip *ipq_pwm_from_chip(struct pwm_chip *chip)
{
	return pwmchip_get_drvdata(chip);
}

static unsigned int ipq_pwm_reg_read(struct pwm_device *pwm, unsigned int reg)
{
	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
	unsigned int off = 8 * pwm->hwpwm + reg;

	return readl(ipq_chip->mem + off);
}

static void ipq_pwm_reg_write(struct pwm_device *pwm, unsigned int reg,
			      unsigned int val)
{
	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
	unsigned int off = 8 * pwm->hwpwm + reg;

	writel(val, ipq_chip->mem + off);
}

static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
			 const struct pwm_state *state)
{
	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
	unsigned int pre_div, pwm_div;
	u64 period_ns, duty_ns;
	unsigned long val = 0;
	unsigned long hi_dur;

	if (!state->enabled) {
		/* clear IPQ_PWM_REG1_ENABLE */
		ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, IPQ_PWM_REG1_UPDATE);
		return 0;
	}

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

	/*
	 * Check the upper and lower bounds for the period as per
	 * hardware limits
	 */
	if (state->period < IPQ_PWM_MIN_PERIOD_NS)
		return -ERANGE;
	period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
	duty_ns = min(state->duty_cycle, period_ns);

	/*
	 * Pick the maximal value for PWM_DIV that still allows a
	 * 100% relative duty cycle. This allows a fine grained
	 * selection of duty cycles.
	 */
	pwm_div = IPQ_PWM_MAX_DIV - 1;

	/*
	 * although mul_u64_u64_div_u64 returns a u64, in practice it
	 * won't overflow due to above constraints. Take the max period
	 * of 10^9 (NSEC_PER_SEC) and the pwm_div + 1 (IPQ_PWM_MAX_DIV)
	 *  10^9 * 10^8
	 * ------------- => which fits well into a 32-bit unsigned int.
	 * 10^9 * 65,535
	 */
	pre_div = mul_u64_u64_div_u64(period_ns, ipq_chip->clk_rate,
				      (u64)NSEC_PER_SEC * (pwm_div + 1));

	if (!pre_div)
		return -ERANGE;

	pre_div -= 1;

	if (pre_div > IPQ_PWM_MAX_DIV)
		pre_div = IPQ_PWM_MAX_DIV;

	/* pwm duty = HI_DUR * (PRE_DIV + 1) / clk_rate */
	hi_dur = mul_u64_u64_div_u64(duty_ns, ipq_chip->clk_rate,
				     (u64)NSEC_PER_SEC * (pre_div + 1));

	val = FIELD_PREP(IPQ_PWM_REG0_HI_DURATION, hi_dur) |
		FIELD_PREP(IPQ_PWM_REG0_PWM_DIV, pwm_div);
	ipq_pwm_reg_write(pwm, IPQ_PWM_REG0, val);

	val = FIELD_PREP(IPQ_PWM_REG1_PRE_DIV, pre_div);
	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);

Annotation

Implementation Notes