drivers/pwm/pwm-sifive.c

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

File Facts

System
Linux kernel
Corpus path
drivers/pwm/pwm-sifive.c
Extension
.c
Size
10678 bytes
Lines
375
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_sifive_ddata {
	struct device *parent;
	struct mutex lock; /* lock to protect user_count and approx_period */
	struct notifier_block notifier;
	struct clk *clk;
	void __iomem *regs;
	unsigned int real_period;
	unsigned int approx_period;
	int user_count;
};

static inline
struct pwm_sifive_ddata *pwm_sifive_chip_to_ddata(struct pwm_chip *chip)
{
	return pwmchip_get_drvdata(chip);
}

static int pwm_sifive_request(struct pwm_chip *chip, struct pwm_device *pwm)
{
	struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip);

	mutex_lock(&ddata->lock);
	ddata->user_count++;
	mutex_unlock(&ddata->lock);

	return 0;
}

static void pwm_sifive_free(struct pwm_chip *chip, struct pwm_device *pwm)
{
	struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip);

	mutex_lock(&ddata->lock);
	ddata->user_count--;
	mutex_unlock(&ddata->lock);
}

/* Called holding ddata->lock */
static void pwm_sifive_update_clock(struct pwm_sifive_ddata *ddata,
				    unsigned long rate)
{
	unsigned long long num;
	unsigned long scale_pow;
	int scale;
	u32 val;
	/*
	 * The PWM unit is used with pwmzerocmp=0, so the only way to modify the
	 * period length is using pwmscale which provides the number of bits the
	 * counter is shifted before being feed to the comparators. A period
	 * lasts (1 << (PWM_SIFIVE_CMPWIDTH + pwmscale)) clock ticks.
	 * (1 << (PWM_SIFIVE_CMPWIDTH + scale)) * 10^9/rate = period
	 */
	scale_pow = div64_ul(ddata->approx_period * (u64)rate, NSEC_PER_SEC);
	scale = clamp(ilog2(scale_pow) - PWM_SIFIVE_CMPWIDTH, 0, 0xf);

	val = PWM_SIFIVE_PWMCFG_EN_ALWAYS |
	      FIELD_PREP(PWM_SIFIVE_PWMCFG_SCALE, scale);
	writel(val, ddata->regs + PWM_SIFIVE_PWMCFG);

	/* As scale <= 15 the shift operation cannot overflow. */
	num = (unsigned long long)NSEC_PER_SEC << (PWM_SIFIVE_CMPWIDTH + scale);
	ddata->real_period = DIV_ROUND_UP_ULL(num, rate);
	dev_dbg(ddata->parent,
		"New real_period = %u ns\n", ddata->real_period);
}

static int pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
				struct pwm_state *state)
{
	struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip);
	u32 duty, val, inactive;

	inactive = readl(ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));
	/*
	 * PWM hardware uses 'inactive' counts in pwmcmp, so invert to get actual duty.
	 * Here, 'inactive' is the low time and we compute duty as max_count - inactive.
	 */
	duty = (1U << PWM_SIFIVE_CMPWIDTH) - 1 - inactive;

	state->enabled = duty > 0;

	val = readl(ddata->regs + PWM_SIFIVE_PWMCFG);
	if (!(val & PWM_SIFIVE_PWMCFG_EN_ALWAYS))
		state->enabled = false;

	state->period = ddata->real_period;
	state->duty_cycle = DIV_ROUND_UP_ULL((u64)duty * ddata->real_period,
					     (1U << PWM_SIFIVE_CMPWIDTH));
	state->polarity = PWM_POLARITY_NORMAL;

Annotation

Implementation Notes