drivers/hwmon/aspeed-g6-pwm-tach.c

Source file repositories/reference/linux-study-clean/drivers/hwmon/aspeed-g6-pwm-tach.c

File Facts

System
Linux kernel
Corpus path
drivers/hwmon/aspeed-g6-pwm-tach.c
Extension
.c
Size
17226 bytes
Lines
544
Domain
Driver Families
Bucket
drivers/hwmon
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 aspeed_pwm_tach_data {
	struct device *dev;
	void __iomem *base;
	struct clk *clk;
	struct reset_control *reset;
	unsigned long clk_rate;
	bool tach_present[TACH_ASPEED_NR_TACHS];
	u32 tach_divisor;
};

static inline struct aspeed_pwm_tach_data *
aspeed_pwm_chip_to_data(struct pwm_chip *chip)
{
	return pwmchip_get_drvdata(chip);
}

static int aspeed_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
				struct pwm_state *state)
{
	struct aspeed_pwm_tach_data *priv = aspeed_pwm_chip_to_data(chip);
	u32 hwpwm = pwm->hwpwm;
	bool polarity, pin_en, clk_en;
	u32 duty_pt, val;
	u64 div_h, div_l, duty_cycle_period, dividend;

	val = readl(priv->base + PWM_ASPEED_CTRL(hwpwm));
	polarity = FIELD_GET(PWM_ASPEED_CTRL_INVERSE, val);
	pin_en = FIELD_GET(PWM_ASPEED_CTRL_PIN_ENABLE, val);
	clk_en = FIELD_GET(PWM_ASPEED_CTRL_CLK_ENABLE, val);
	div_h = FIELD_GET(PWM_ASPEED_CTRL_CLK_DIV_H, val);
	div_l = FIELD_GET(PWM_ASPEED_CTRL_CLK_DIV_L, val);
	val = readl(priv->base + PWM_ASPEED_DUTY_CYCLE(hwpwm));
	duty_pt = FIELD_GET(PWM_ASPEED_DUTY_CYCLE_FALLING_POINT, val);
	duty_cycle_period = FIELD_GET(PWM_ASPEED_DUTY_CYCLE_PERIOD, val);
	/*
	 * This multiplication doesn't overflow, the upper bound is
	 * 1000000000 * 256 * 256 << 15 = 0x1dcd650000000000
	 */
	dividend = (u64)NSEC_PER_SEC * (div_l + 1) * (duty_cycle_period + 1)
		       << div_h;
	state->period = DIV_ROUND_UP_ULL(dividend, priv->clk_rate);

	if (clk_en && duty_pt) {
		dividend = (u64)NSEC_PER_SEC * (div_l + 1) * duty_pt
				 << div_h;
		state->duty_cycle = DIV_ROUND_UP_ULL(dividend, priv->clk_rate);
	} else {
		state->duty_cycle = clk_en ? state->period : 0;
	}
	state->polarity = polarity ? PWM_POLARITY_INVERSED : PWM_POLARITY_NORMAL;
	state->enabled = pin_en;
	return 0;
}

static int aspeed_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
			    const struct pwm_state *state)
{
	struct aspeed_pwm_tach_data *priv = aspeed_pwm_chip_to_data(chip);
	u32 hwpwm = pwm->hwpwm, duty_pt, val;
	u64 div_h, div_l, divisor, expect_period;
	bool clk_en;

	expect_period = div64_u64(ULLONG_MAX, (u64)priv->clk_rate);
	expect_period = min(expect_period, state->period);
	dev_dbg(pwmchip_parent(chip), "expect period: %lldns, duty_cycle: %lldns",
		expect_period, state->duty_cycle);
	/*
	 * Pick the smallest value for div_h so that div_l can be the biggest
	 * which results in a finer resolution near the target period value.
	 */
	divisor = (u64)NSEC_PER_SEC * (PWM_ASPEED_FIXED_PERIOD + 1) *
		  (FIELD_MAX(PWM_ASPEED_CTRL_CLK_DIV_L) + 1);
	div_h = order_base_2(DIV64_U64_ROUND_UP(priv->clk_rate * expect_period, divisor));
	if (div_h > 0xf)
		div_h = 0xf;

	divisor = ((u64)NSEC_PER_SEC * (PWM_ASPEED_FIXED_PERIOD + 1)) << div_h;
	div_l = div64_u64(priv->clk_rate * expect_period, divisor);

	if (div_l == 0)
		return -ERANGE;

	div_l -= 1;

	if (div_l > 255)
		div_l = 255;

	dev_dbg(pwmchip_parent(chip), "clk source: %ld div_h %lld, div_l : %lld\n",
		priv->clk_rate, div_h, div_l);
	/* duty_pt = duty_cycle * (PERIOD + 1) / period */

Annotation

Implementation Notes