Hello,
On Wed, May 29, 2024 at 12:10:31PM +0200, Farouk Bouabid wrote:
Mule is a device that can output a PWM signal based on I2C commands.
Add pwm driver for Mule PWM-over-I2C controller.
Signed-off-by: Farouk Bouabid <farouk.bouabid@xxxxxxxxx>
---
drivers/pwm/Kconfig | 10 +++++
drivers/pwm/Makefile | 1 +
drivers/pwm/pwm-mule.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 126 insertions(+)
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 4b956d661755..eb8cfa113ec7 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -425,6 +425,16 @@ config PWM_MICROCHIP_CORE
To compile this driver as a module, choose M here: the module
will be called pwm-microchip-core.
+config PWM_MULE
+ tristate "Mule PWM-over-I2C support"
+ depends on I2C && OF
It would be easy to drop the hard dependency on OF. Please do that.
diff --git a/drivers/pwm/pwm-mule.c b/drivers/pwm/pwm-mule.c
new file mode 100644
index 000000000000..e8593a48b16e
--- /dev/null
+++ b/drivers/pwm/pwm-mule.c
@@ -0,0 +1,115 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Mule PWM-over-I2C controller driver
+ *
+ * Copyright (C) 2024 Theobroma Systems Design und Consulting GmbH
Is there a publicly available datasheet? I guess not. (I ask because
adding a link there to such a document would be nice.)
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+
+struct mule_pwm {
+ struct mutex lock;
+ struct regmap *regmap;
+};
+
+static const struct regmap_config pwm_mule_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+
+#define MULE_PWM_DCY_REG 0x0
+#define MULE_PWM_FREQ_L_REG 0x1 /* LSB register */
+#define MULE_PWM_FREQ_H_REG 0x2 /* MSB register */
+
+#define NANOSECONDS_TO_HZ(x) (1000000000UL/(x))
Don't introduce such a macro if you only use it once. Having the
division in the function results in code that is easier to read (IMHO).
+static int pwm_mule_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct mule_pwm *priv = pwmchip_get_drvdata(chip);
+ u8 duty_cycle;
+ u64 freq;
+ int ret;
+
+ freq = NANOSECONDS_TO_HZ(state->period);
+
+ if (freq > U16_MAX) /* Frequency is 16-bit wide */ {
+ dev_err(chip->dev,
+ "Failed to set frequency: %llu Hz: out of 16-bit range\n", freq);
+ return -EINVAL;
+ }
You're supposed to configure the biggest possible period not bigger than
the requested period. So this should be:
/*
* The period is configured using a 16 bit wide register holding
* the frequency in Hz.
*/
unsigned int period = min_t(u64, state->period, NSEC_PER_SEC);
unsigned int freq = DIV_ROUND_UP(NSEC_PER_SEC, period);
if (freq > U16_MAX)
return -EINVAL;
+ if (state->enabled)
+ duty_cycle = pwm_get_relative_duty_cycle(state, 100);
This is wrong for two reasons:
- It uses rounding to the nearest duty_cycle, however you're supposed
to round down.
- It uses the requested period instead of the real one.
I wonder why the hardware doesn't use the whole 8 bits here.
+ else
+ duty_cycle = 0;
+
+ mutex_lock(&priv->lock);
If you use the guard helper, you don't need to resort to goto for error
handling.
+ ret = regmap_bulk_write(priv->regmap, MULE_PWM_FREQ_L_REG, &freq, 2);
+ if (ret) {
+ dev_err(chip->dev,
+ "Failed to set frequency: %llu Hz: %d\n", freq, ret);
+ goto out;
+ }
+
+ ret = regmap_write(priv->regmap, MULE_PWM_DCY_REG, duty_cycle);
+ if (ret)
+ dev_err(chip->dev,
+ "Failed to set duty cycle: %u: %d\n", duty_cycle, ret);
Please document how the hardware behaves here in a "Limitations" section
as several other drivers do. Questions to answer include: Does it
complete a period when the parameters are updated? Can it happen that a
glitch is emitted while MULE_PWM_FREQ_[LH]_REG is updated but
MULE_PWM_DCY_REG isn't yet? Maybe updating MULE_PWM_FREQ_[LH]_REG isn't
even atomic? "Doesn't support disabling, configures duty_cycle=0 when
disabled is requested."
Maybe write all three registers in a bulk write, then you might even be
able to drop the lock.
Also please fail silently.