Skip to content

Commit 0191c80

Browse files
Marek VasutUwe Kleine-König
authored andcommitted
pwm: argon-fan-hat: Add Argon40 Fan HAT support
Add trivial PWM driver for Argon40 Fan HAT, which is a RaspberryPi blower fan hat which can be controlled over I2C. Model this device as a PWM, so the pwm-fan can be attached to it and handle thermal zones and RPM management in a generic manner. Signed-off-by: Marek Vasut <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Uwe Kleine-König <[email protected]>
1 parent f6bd99a commit 0191c80

File tree

3 files changed

+119
-0
lines changed

3 files changed

+119
-0
lines changed

drivers/pwm/Kconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ config PWM_APPLE
6666
To compile this driver as a module, choose M here: the module
6767
will be called pwm-apple.
6868

69+
config PWM_ARGON_FAN_HAT
70+
tristate "Argon40 Fan HAT support"
71+
depends on I2C && OF
72+
help
73+
Generic PWM framework driver for Argon40 Fan HAT.
74+
75+
To compile this driver as a module, choose M here: the module
76+
will be called pwm-argon-fan-hat.
77+
6978
config PWM_ATMEL
7079
tristate "Atmel PWM support"
7180
depends on ARCH_AT91 || COMPILE_TEST

drivers/pwm/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ obj-$(CONFIG_PWM) += core.o
33
obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o
44
obj-$(CONFIG_PWM_ADP5585) += pwm-adp5585.o
55
obj-$(CONFIG_PWM_APPLE) += pwm-apple.o
6+
obj-$(CONFIG_PWM_ARGON_FAN_HAT) += pwm-argon-fan-hat.o
67
obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o
78
obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o
89
obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o

drivers/pwm/pwm-argon-fan-hat.c

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
/*
3+
* Copyright (C) 2025 Marek Vasut
4+
*
5+
* Limitations:
6+
* - no support for offset/polarity
7+
* - fixed 30 kHz period
8+
*
9+
* Argon Fan HAT https://argon40.com/products/argon-fan-hat
10+
*/
11+
12+
#include <linux/err.h>
13+
#include <linux/i2c.h>
14+
#include <linux/module.h>
15+
#include <linux/pwm.h>
16+
17+
#define ARGON40_FAN_HAT_PERIOD_NS 33333 /* ~30 kHz */
18+
19+
#define ARGON40_FAN_HAT_REG_DUTY_CYCLE 0x80
20+
21+
static int argon_fan_hat_round_waveform_tohw(struct pwm_chip *chip,
22+
struct pwm_device *pwm,
23+
const struct pwm_waveform *wf,
24+
void *_wfhw)
25+
{
26+
u8 *wfhw = _wfhw;
27+
28+
if (wf->duty_length_ns > ARGON40_FAN_HAT_PERIOD_NS)
29+
*wfhw = 100;
30+
else
31+
*wfhw = mul_u64_u64_div_u64(wf->duty_length_ns, 100, ARGON40_FAN_HAT_PERIOD_NS);
32+
33+
return 0;
34+
}
35+
36+
static int argon_fan_hat_round_waveform_fromhw(struct pwm_chip *chip,
37+
struct pwm_device *pwm,
38+
const void *_wfhw,
39+
struct pwm_waveform *wf)
40+
{
41+
const u8 *wfhw = _wfhw;
42+
43+
wf->period_length_ns = ARGON40_FAN_HAT_PERIOD_NS;
44+
wf->duty_length_ns = DIV64_U64_ROUND_UP(wf->period_length_ns * *wfhw, 100);
45+
wf->duty_offset_ns = 0;
46+
47+
return 0;
48+
}
49+
50+
static int argon_fan_hat_write_waveform(struct pwm_chip *chip,
51+
struct pwm_device *pwm,
52+
const void *_wfhw)
53+
{
54+
struct i2c_client *i2c = pwmchip_get_drvdata(chip);
55+
const u8 *wfhw = _wfhw;
56+
57+
return i2c_smbus_write_byte_data(i2c, ARGON40_FAN_HAT_REG_DUTY_CYCLE, *wfhw);
58+
}
59+
60+
static const struct pwm_ops argon_fan_hat_pwm_ops = {
61+
.sizeof_wfhw = sizeof(u8),
62+
.round_waveform_fromhw = argon_fan_hat_round_waveform_fromhw,
63+
.round_waveform_tohw = argon_fan_hat_round_waveform_tohw,
64+
.write_waveform = argon_fan_hat_write_waveform,
65+
/*
66+
* The controller does not provide any way to read info back,
67+
* reading from the controller stops the fan, therefore there
68+
* is no .read_waveform here.
69+
*/
70+
};
71+
72+
static int argon_fan_hat_i2c_probe(struct i2c_client *i2c)
73+
{
74+
struct pwm_chip *chip = devm_pwmchip_alloc(&i2c->dev, 1, 0);
75+
int ret;
76+
77+
if (IS_ERR(chip))
78+
return PTR_ERR(chip);
79+
80+
chip->ops = &argon_fan_hat_pwm_ops;
81+
pwmchip_set_drvdata(chip, i2c);
82+
83+
ret = devm_pwmchip_add(&i2c->dev, chip);
84+
if (ret)
85+
return dev_err_probe(&i2c->dev, ret, "Could not add PWM chip\n");
86+
87+
return 0;
88+
}
89+
90+
static const struct of_device_id argon_fan_hat_dt_ids[] = {
91+
{ .compatible = "argon40,fan-hat" },
92+
{ },
93+
};
94+
MODULE_DEVICE_TABLE(of, argon_fan_hat_dt_ids);
95+
96+
static struct i2c_driver argon_fan_hat_driver = {
97+
.driver = {
98+
.name = "argon-fan-hat",
99+
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
100+
.of_match_table = argon_fan_hat_dt_ids,
101+
},
102+
.probe = argon_fan_hat_i2c_probe,
103+
};
104+
105+
module_i2c_driver(argon_fan_hat_driver);
106+
107+
MODULE_AUTHOR("Marek Vasut <[email protected]>");
108+
MODULE_DESCRIPTION("Argon40 Fan HAT");
109+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)