Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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 <marek.vasut+renesas@mailbox.org>
Link: https://lore.kernel.org/r/20250629220757.936212-3-marek.vasut+renesas@mailbox.org
Signed-off-by: Uwe Kleine-König <ukleinek@kernel.org>

authored by

Marek Vasut and committed by
Uwe Kleine-König
0191c80e f6bd99a2

+119
+9
drivers/pwm/Kconfig
··· 66 66 To compile this driver as a module, choose M here: the module 67 67 will be called pwm-apple. 68 68 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 + 69 78 config PWM_ATMEL 70 79 tristate "Atmel PWM support" 71 80 depends on ARCH_AT91 || COMPILE_TEST
+1
drivers/pwm/Makefile
··· 3 3 obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o 4 4 obj-$(CONFIG_PWM_ADP5585) += pwm-adp5585.o 5 5 obj-$(CONFIG_PWM_APPLE) += pwm-apple.o 6 + obj-$(CONFIG_PWM_ARGON_FAN_HAT) += pwm-argon-fan-hat.o 6 7 obj-$(CONFIG_PWM_ATMEL) += pwm-atmel.o 7 8 obj-$(CONFIG_PWM_ATMEL_HLCDC_PWM) += pwm-atmel-hlcdc.o 8 9 obj-$(CONFIG_PWM_ATMEL_TCB) += pwm-atmel-tcb.o
+109
drivers/pwm/pwm-argon-fan-hat.c
··· 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 <marek.vasut+renesas@mailbox.org>"); 108 + MODULE_DESCRIPTION("Argon40 Fan HAT"); 109 + MODULE_LICENSE("GPL");