drivers/pwm/pwm-img.c

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

File Facts

System
Linux kernel
Corpus path
drivers/pwm/pwm-img.c
Extension
.c
Size
10723 bytes
Lines
424
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 img_pwm_soc_data {
	u32 max_timebase;
};

struct img_pwm_chip {
	struct clk	*pwm_clk;
	struct clk	*sys_clk;
	void __iomem	*base;
	struct regmap	*periph_regs;
	int		max_period_ns;
	int		min_period_ns;
	const struct img_pwm_soc_data   *data;
	u32		suspend_ctrl_cfg;
	u32		suspend_ch_cfg[IMG_PWM_NPWM];
};

static inline struct img_pwm_chip *to_img_pwm_chip(struct pwm_chip *chip)
{
	return pwmchip_get_drvdata(chip);
}

static inline void img_pwm_writel(struct img_pwm_chip *imgchip,
				  u32 reg, u32 val)
{
	writel(val, imgchip->base + reg);
}

static inline u32 img_pwm_readl(struct img_pwm_chip *imgchip, u32 reg)
{
	return readl(imgchip->base + reg);
}

static int img_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
			  int duty_ns, int period_ns)
{
	u32 val, div, duty, timebase;
	unsigned long mul, output_clk_hz, input_clk_hz;
	struct img_pwm_chip *imgchip = to_img_pwm_chip(chip);
	unsigned int max_timebase = imgchip->data->max_timebase;
	int ret;

	if (period_ns < imgchip->min_period_ns ||
	    period_ns > imgchip->max_period_ns) {
		dev_err(pwmchip_parent(chip), "configured period not in range\n");
		return -ERANGE;
	}

	input_clk_hz = clk_get_rate(imgchip->pwm_clk);
	output_clk_hz = DIV_ROUND_UP(NSEC_PER_SEC, period_ns);

	mul = DIV_ROUND_UP(input_clk_hz, output_clk_hz);
	if (mul <= max_timebase) {
		div = PWM_CTRL_CFG_NO_SUB_DIV;
		timebase = DIV_ROUND_UP(mul, 1);
	} else if (mul <= max_timebase * 8) {
		div = PWM_CTRL_CFG_SUB_DIV0;
		timebase = DIV_ROUND_UP(mul, 8);
	} else if (mul <= max_timebase * 64) {
		div = PWM_CTRL_CFG_SUB_DIV1;
		timebase = DIV_ROUND_UP(mul, 64);
	} else if (mul <= max_timebase * 512) {
		div = PWM_CTRL_CFG_SUB_DIV0_DIV1;
		timebase = DIV_ROUND_UP(mul, 512);
	} else {
		dev_err(pwmchip_parent(chip),
			"failed to configure timebase steps/divider value\n");
		return -EINVAL;
	}

	duty = DIV_ROUND_UP(timebase * duty_ns, period_ns);

	ret = pm_runtime_resume_and_get(pwmchip_parent(chip));
	if (ret < 0)
		return ret;

	val = img_pwm_readl(imgchip, PWM_CTRL_CFG);
	val &= ~(PWM_CTRL_CFG_DIV_MASK << PWM_CTRL_CFG_DIV_SHIFT(pwm->hwpwm));
	val |= (div & PWM_CTRL_CFG_DIV_MASK) <<
		PWM_CTRL_CFG_DIV_SHIFT(pwm->hwpwm);
	img_pwm_writel(imgchip, PWM_CTRL_CFG, val);

	val = (duty << PWM_CH_CFG_DUTY_SHIFT) |
	      (timebase << PWM_CH_CFG_TMBASE_SHIFT);
	img_pwm_writel(imgchip, PWM_CH_CFG(pwm->hwpwm), val);

	pm_runtime_put_autosuspend(pwmchip_parent(chip));

	return 0;
}

Annotation

Implementation Notes