File Index
Files and directories
Sign in
FILE

pwm-argon-fan-hat.c

File preview with syntax highlighting.

linux-6.19.14/drivers/pwm/pwm-argon-fan-hat.c 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
 21static 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
 36static 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
 50static 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
 60static 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
 72static 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
 90static const struct of_device_id argon_fan_hat_dt_ids[] = {
 91	{ .compatible = "argon40,fan-hat" },
 92	{ },
 93};
 94MODULE_DEVICE_TABLE(of, argon_fan_hat_dt_ids);
 95
 96static 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
105module_i2c_driver(argon_fan_hat_driver);
106
107MODULE_AUTHOR("Marek Vasut <marek.vasut+renesas@mailbox.org>");
108MODULE_DESCRIPTION("Argon40 Fan HAT");
109MODULE_LICENSE("GPL");
~/ Read-only file preview.