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"); 110