Thread (6 messages) 6 messages, 3 authors, 2020-06-29

Re: [PATCH v3 2/2] Add PWM fan controller driver for LGM SoC

From: Uwe Kleine-König <hidden>
Date: 2020-06-29 19:43:26
Also in: linux-pwm, lkml

Hello Rahul,

On Mon, Jun 29, 2020 at 05:03:47PM +0800, Rahul Tanwar wrote:
quoted hunk ↗ jump to hunk
diff --git a/drivers/pwm/pwm-intel-lgm.c b/drivers/pwm/pwm-intel-lgm.c
new file mode 100644
index 000000000000..661fa7d9145d
--- /dev/null
+++ b/drivers/pwm/pwm-intel-lgm.c
@@ -0,0 +1,265 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020 Intel Corporation.
+ *
+ * Notes & Limitations:
+ * - The hardware supports fixed period which is dependent on 2/3 or 4
+ *   wire fan mode.
+ * - Supports normal polarity. Does not support changing polarity.
+ * - When PWM is disabled, output of PWM will become 0(inactive). It doesn't
+ *   keep track of running period.
+ * - When duty cycle is changed, PWM output may be a mix of previous setting
+ *   and new setting for the first period. From second period, the output is
+ *   based on new setting.
+ * - Supports 100% duty cycle.
This would be worth mentioning if it didn't support that. IMHO you can
drop this one.
+ * - It is a dedicated PWM fan controller. There are no other consumers for
+ *   this PWM controller.
+ */
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+
+#define PWM_FAN_CON0		0x0
+#define PWM_FAN_EN_EN		BIT(0)
+#define PWM_FAN_EN_DIS		0x0
+#define PWM_FAN_EN_MSK		BIT(0)
+#define PWM_FAN_MODE_2WIRE	0x0
+#define PWM_FAN_MODE_4WIRE	0x1
+#define PWM_FAN_MODE_MSK	BIT(1)
+#define PWM_FAN_DC_MSK		GENMASK(23, 16)
+
+#define PWM_FAN_CON1		0x4
+#define PWM_FAN_MAX_RPM_MSK	GENMASK(15, 0)
+
+#define MAX_RPM			(BIT(16) - 1)
+#define DFAULT_RPM		4000
DEFAULT_RPM?
+#define MAX_DUTY_CYCLE		(BIT(8) - 1)
+
+#define DC_BITS			8
+
+#define PERIOD_2WIRE_NSECS	40000000
+#define PERIOD_4WIRE_NSECS	40000
Please add a common prefix to these definitions.
+#define LGM_PWM_DIV_ROUND_DOWN(n, d) (((n) + ((d) / 2) - 1) / (d))
+
+struct lgm_pwm_chip {
+	struct pwm_chip chip;
+	struct regmap *regmap;
+	struct clk *clk;
+	struct reset_control *rst;
+	u32 period;
+};
+
+static inline struct lgm_pwm_chip *to_lgm_pwm_chip(struct pwm_chip *chip)
+{
+	return container_of(chip, struct lgm_pwm_chip, chip);
+}
+
+static int lgm_pwm_enable(struct pwm_chip *chip, bool enable)
+{
+	struct lgm_pwm_chip *pc = to_lgm_pwm_chip(chip);
+	struct regmap *regmap = pc->regmap;
+
+	if (enable)
+		regmap_update_bits(regmap, PWM_FAN_CON0,
+				   PWM_FAN_EN_MSK, PWM_FAN_EN_EN);
+	else
+		regmap_update_bits(regmap, PWM_FAN_CON0,
+			   	   PWM_FAN_EN_MSK, PWM_FAN_EN_DIS);
Is it easier to understand what happens here if you write this as:

+	regmap_update_bits(regmap, PWM_FAN_CON0, PWM_FAN_EN_MSK,
+			   enable ? PWM_FAN_EN_EN : PWM_FAN_EN_DIS);

? (This is what I'd prefer, but maybe this is subjective?)
+
+	return 0;
+}
+
+static int lgm_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+			 const struct pwm_state *state)
+{
+	struct lgm_pwm_chip *pc = to_lgm_pwm_chip(chip);
+	u32 duty_cycle, val;
+	unsigned int period;
+
+	period = min_t(unsigned int, state->period, pc->period);
Given that state->period will most probably change type (from unsigned
int to u64) soon, it would be nice to use this already here.
+	if (state->polarity != PWM_POLARITY_NORMAL ||
+	    period < pc->period)
+		return -EINVAL;
+
+	duty_cycle = min_t(u32, state->duty_cycle, period);
+
+	/* reg_value = duty_ns * MAX_REG_VAL(0xff) / period_ns */
s/MAX_REG_VAL/MAX_DUTY_CYCLE/
+	val = LGM_PWM_DIV_ROUND_DOWN(duty_cycle << DC_BITS, period);
The rounding is wrong here, you have to round down. I think you need to
do:

	val = duty_cycle * MAX_DUTY_CYCLE / period;
+	val = min_t(u32, val, MAX_DUTY_CYCLE);
Then as duty_cycle <= period this is a noop and can be dropped.
+	regmap_update_bits(pc->regmap, PWM_FAN_CON0, PWM_FAN_DC_MSK,
+			   FIELD_PREP(PWM_FAN_DC_MSK, val));
+
+	if (state->enabled != regmap_test_bits(pc->regmap, PWM_FAN_CON0,
+					       PWM_FAN_EN_EN))
+		lgm_pwm_enable(chip, state->enabled);
Here a spike can happen that you can prevent:

	pwm_apply_state(pwm, { .enabled = 1, .duty_cycle = 0ms, .period = 40ms })
	pwm_apply_state(pwm, { .enabled = 0, .duty_cycle = 40ms, .period = 40ms })

As apply first configures the duty_cycle, the output goes high before
disabling. So better do something like:

	if (!state->enabled) {
		lgm_pwm_enable(chip, 0);
		return;
	}

	configure_duty_cycle();

	if ( state->enabled)
		lgm_pwm_enable(chip, 1);
+	return 0;
+}
+
+static void lgm_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+			      struct pwm_state *state)
+{
+	struct lgm_pwm_chip *pc = to_lgm_pwm_chip(chip);
+	u32 duty, val;
+
+	state->enabled = regmap_test_bits(pc->regmap, PWM_FAN_CON0,
+					  PWM_FAN_EN_EN);
+	state->polarity = PWM_POLARITY_NORMAL;
+	state->period = pc->period; /* fixed period */
+
+	regmap_read(pc->regmap, PWM_FAN_CON0, &val);
+	duty = FIELD_GET(PWM_FAN_DC_MSK, val);
+	state->duty_cycle = duty * pc->period >> DC_BITS;
If PWM_FAN_DC = 255 means 100% the calculation is wrong. You said in the
v2 thread: 0 = disabled (0%) and 255 = 100%, so we need here:

	state->duty_cycle = DIV_ROUND_UP(duty * pc->period, 255);

.
+	state->duty_cycle = roundup_pow_of_two(state->duty_cycle);
+}
+[..]
+static int lgm_pwm_probe(struct platform_device *pdev)
+{
+	struct lgm_pwm_chip *pc;
+	struct device *dev = &pdev->dev;
+	void __iomem *io_base;
+	int ret;
+
+	pc = devm_kzalloc(dev, sizeof(*pc), GFP_KERNEL);
+	if (!pc)
+		return -ENOMEM;
+
+	io_base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(io_base))
+		return PTR_ERR(io_base);
+
+	pc->regmap = devm_regmap_init_mmio(dev, io_base, &lgm_pwm_regmap_config);
+	if (IS_ERR(pc->regmap)) {
+		ret = PTR_ERR(pc->regmap);
+		dev_err(dev, "failed to init register map: %pe\n", pc->regmap);
+		return ret;
+	}
+
+	pc->clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(pc->clk)) {
+		ret = PTR_ERR(pc->clk);
+		dev_err(dev, "failed to get clock: %pe\n", pc->clk);
+		return ret;
+	}
+
+	pc->rst = devm_reset_control_get_exclusive(dev, NULL);
+	if (IS_ERR(pc->rst)) {
+		ret = PTR_ERR(pc->rst);
+		dev_err(dev, "failed to get reset control: %pe\n", pc->rst);
Please skip the error messages if ret = -EPROBE_DEFER.
+		return ret;
+	}
+
+	ret = reset_control_deassert(pc->rst);
+	if (ret) {
+		dev_err(dev, "cannot deassert reset control: %pe\n",
+			ERR_PTR(ret));
+		return ret;
+	}
+
+	ret = clk_prepare_enable(pc->clk);
+	if (ret) {
+		dev_err(dev, "failed to enable clock\n");
		reset_control_assert(pc->rst);
+		return ret;
+	}
Best regards
Uwe

-- 
Pengutronix e.K.                           | Uwe Kleine-König            |
Industrial Linux Solutions                 | https://www.pengutronix.de/ |

Attachments

Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help