xref: /linux/drivers/pwm/pwm-argon-fan-hat.c (revision 22c55fb9eb92395d999b8404d73e58540d11bdd8)
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