drivers/pwm/pwm-microchip-core.c

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

File Facts

System
Linux kernel
Corpus path
drivers/pwm/pwm-microchip-core.c
Extension
.c
Size
16646 bytes
Lines
491
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 mchp_core_pwm_chip {
	struct clk *clk;
	void __iomem *base;
	ktime_t update_timestamp;
	u32 sync_update_mask;
	u16 channel_enabled;
};

static inline struct mchp_core_pwm_chip *to_mchp_core_pwm(struct pwm_chip *chip)
{
	return pwmchip_get_drvdata(chip);
}

static void mchp_core_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm,
				 bool enable, u64 period)
{
	struct mchp_core_pwm_chip *mchp_core_pwm = to_mchp_core_pwm(chip);
	u8 channel_enable, reg_offset, shift;

	/*
	 * There are two adjacent 8 bit control regs, the lower reg controls
	 * 0-7 and the upper reg 8-15. Check if the pwm is in the upper reg
	 * and if so, offset by the bus width.
	 */
	reg_offset = MCHPCOREPWM_EN(pwm->hwpwm >> 3);
	shift = pwm->hwpwm & 7;

	channel_enable = readb_relaxed(mchp_core_pwm->base + reg_offset);
	channel_enable &= ~(1 << shift);
	channel_enable |= (enable << shift);

	writel_relaxed(channel_enable, mchp_core_pwm->base + reg_offset);
	mchp_core_pwm->channel_enabled &= ~BIT(pwm->hwpwm);
	mchp_core_pwm->channel_enabled |= enable << pwm->hwpwm;

	/*
	 * The updated values will not appear on the bus until they have been
	 * applied to the waveform at the beginning of the next period.
	 * This is a NO-OP if the channel does not have shadow registers.
	 */
	if (mchp_core_pwm->sync_update_mask & (1 << pwm->hwpwm))
		mchp_core_pwm->update_timestamp = ktime_add_ns(ktime_get(), period);
}

static void mchp_core_pwm_wait_for_sync_update(struct mchp_core_pwm_chip *mchp_core_pwm,
					       unsigned int channel)
{
	/*
	 * If a shadow register is used for this PWM channel, and iff there is
	 * a pending update to the waveform, we must wait for it to be applied
	 * before attempting to read its state. Reading the registers yields
	 * the currently implemented settings & the new ones are only readable
	 * once the current period has ended.
	 */

	if (mchp_core_pwm->sync_update_mask & (1 << channel)) {
		ktime_t current_time = ktime_get();
		s64 remaining_ns;
		u32 delay_us;

		remaining_ns = ktime_to_ns(ktime_sub(mchp_core_pwm->update_timestamp,
						     current_time));

		/*
		 * If the update has gone through, don't bother waiting for
		 * obvious reasons. Otherwise wait around for an appropriate
		 * amount of time for the update to go through.
		 */
		if (remaining_ns <= 0)
			return;

		delay_us = DIV_ROUND_UP_ULL(remaining_ns, NSEC_PER_USEC);
		fsleep(delay_us);
	}
}

static u64 mchp_core_pwm_calc_duty(const struct pwm_state *state, u64 clk_rate,
				   u8 prescale, u8 period_steps)
{
	u64 duty_steps, tmp;

	/*
	 * Calculate the duty cycle in multiples of the prescaled period:
	 * duty_steps = duty_in_ns / step_in_ns
	 * step_in_ns = (prescale * NSEC_PER_SEC) / clk_rate
	 * The code below is rearranged slightly to only divide once.
	 */
	tmp = (((u64)prescale) + 1) * NSEC_PER_SEC;
	duty_steps = mul_u64_u64_div_u64(state->duty_cycle, clk_rate, tmp);

Annotation

Implementation Notes