drivers/pwm/pwm-spear.c

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

File Facts

System
Linux kernel
Corpus path
drivers/pwm/pwm-spear.c
Extension
.c
Size
6592 bytes
Lines
263
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 spear_pwm_chip {
	void __iomem *mmio_base;
	struct clk *clk;
};

static inline struct spear_pwm_chip *to_spear_pwm_chip(struct pwm_chip *chip)
{
	return pwmchip_get_drvdata(chip);
}

static inline u32 spear_pwm_readl(struct spear_pwm_chip *chip, unsigned int num,
				  unsigned long offset)
{
	return readl_relaxed(chip->mmio_base + (num << 4) + offset);
}

static inline void spear_pwm_writel(struct spear_pwm_chip *chip,
				    unsigned int num, unsigned long offset,
				    unsigned long val)
{
	writel_relaxed(val, chip->mmio_base + (num << 4) + offset);
}

static int spear_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
			    u64 duty_ns, u64 period_ns)
{
	struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
	u64 val, div, clk_rate;
	unsigned long prescale = PWMCR_MIN_PRESCALE, pv, dc;
	int ret;

	/*
	 * Find pv, dc and prescale to suit duty_ns and period_ns. This is done
	 * according to formulas described below:
	 *
	 * period_ns = 10^9 * (PRESCALE + 1) * PV / PWM_CLK_RATE
	 * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
	 *
	 * PV = (PWM_CLK_RATE * period_ns) / (10^9 * (PRESCALE + 1))
	 * DC = (PWM_CLK_RATE * duty_ns) / (10^9 * (PRESCALE + 1))
	 */
	clk_rate = clk_get_rate(pc->clk);
	while (1) {
		div = 1000000000;
		div *= 1 + prescale;
		val = clk_rate * period_ns;
		pv = div64_u64(val, div);
		val = clk_rate * duty_ns;
		dc = div64_u64(val, div);

		/* if duty_ns and period_ns are not achievable then return */
		if (pv < PWMPCR_MIN_PERIOD || dc < PWMDCR_MIN_DUTY)
			return -EINVAL;

		/*
		 * if pv and dc have crossed their upper limit, then increase
		 * prescale and recalculate pv and dc.
		 */
		if (pv > PWMPCR_MAX_PERIOD || dc > PWMDCR_MAX_DUTY) {
			if (++prescale > PWMCR_MAX_PRESCALE)
				return -EINVAL;
			continue;
		}
		break;
	}

	/*
	 * NOTE: the clock to PWM has to be enabled first before writing to the
	 * registers.
	 */
	ret = clk_enable(pc->clk);
	if (ret)
		return ret;

	spear_pwm_writel(pc, pwm->hwpwm, PWMCR,
			prescale << PWMCR_PRESCALE_SHIFT);
	spear_pwm_writel(pc, pwm->hwpwm, PWMDCR, dc);
	spear_pwm_writel(pc, pwm->hwpwm, PWMPCR, pv);
	clk_disable(pc->clk);

	return 0;
}

static int spear_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
{
	struct spear_pwm_chip *pc = to_spear_pwm_chip(chip);
	int rc = 0;
	u32 val;

	rc = clk_enable(pc->clk);

Annotation

Implementation Notes