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.
- 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.
- Allocates kernel memory; connect allocation flags and lifetime to context constraints.
- Defines or uses C structs; map object ownership, embedded links, reference counts, and lock ownership.
Dependency Surface
linux/bitfield.hlinux/clk.hlinux/delay.hlinux/errno.hlinux/hwmon.hlinux/io.hlinux/kernel.hlinux/math64.hlinux/module.hlinux/of_device.hlinux/of_platform.hlinux/platform_device.hlinux/pwm.hlinux/reset.hlinux/sysfs.h
Detected Declarations
struct aspeed_pwm_tach_datafunction aspeed_pwm_chip_to_datafunction aspeed_pwm_get_statefunction aspeed_pwm_applyfunction aspeed_tach_ch_enablefunction aspeed_tach_val_to_rpmfunction aspeed_get_fan_tach_ch_rpmfunction aspeed_tach_hwmon_readfunction aspeed_tach_hwmon_writefunction aspeed_tach_dev_is_visiblefunction aspeed_present_fan_tachfunction aspeed_create_fan_monitorfunction aspeed_pwm_tach_reset_assertfunction aspeed_pwm_tach_probefunction for_each_child_of_node_scoped
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
- Immediate include surface: `linux/bitfield.h`, `linux/clk.h`, `linux/delay.h`, `linux/errno.h`, `linux/hwmon.h`, `linux/io.h`, `linux/kernel.h`, `linux/math64.h`.
- Detected declarations: `struct aspeed_pwm_tach_data`, `function aspeed_pwm_chip_to_data`, `function aspeed_pwm_get_state`, `function aspeed_pwm_apply`, `function aspeed_tach_ch_enable`, `function aspeed_tach_val_to_rpm`, `function aspeed_get_fan_tach_ch_rpm`, `function aspeed_tach_hwmon_read`, `function aspeed_tach_hwmon_write`, `function aspeed_tach_dev_is_visible`.
- Atlas domain: Driver Families / drivers/hwmon.
- Implementation status: source implementation candidate.
Implementation Notes
- This generated page is the file-by-file coverage layer; curated subsystem chapters should link here when they synthesize a multi-file control flow.
- Core OS pages should be promoted from atlas-only to deep-reviewed when they explain data structures, invariants, locking, lifecycle, and C implementation snippets.
- Driver-family pages are intentionally pattern-oriented unless they are part of the selected PCIe/NVMe representative device path.