All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
@ 2021-05-19  7:48 ` Baruch Siach
  0 siblings, 0 replies; 34+ messages in thread
From: Baruch Siach @ 2021-05-19  7:48 UTC (permalink / raw)
  To: Thierry Reding, Uwe Kleine-König, Lee Jones
  Cc: Baruch Siach, Andy Gross, Bjorn Andersson, Balaji Prakash J,
	Rob Herring, Robert Marko, devicetree, linux-arm-msm,
	linux-arm-kernel

Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
driver from downstream Codeaurora kernel tree. Removed support for older
(V1) variants because I have no access to that hardware.

Tested on IPQ6010 based hardware.

Signed-off-by: Baruch Siach <baruch@tkos.co.il>
---
 drivers/pwm/Kconfig   |  12 ++
 drivers/pwm/Makefile  |   1 +
 drivers/pwm/pwm-ipq.c | 263 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 276 insertions(+)
 create mode 100644 drivers/pwm/pwm-ipq.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 9a4f66ae8070..54ef62a27bdc 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -260,6 +260,18 @@ config PWM_INTEL_LGM
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-intel-lgm.
 
+config PWM_IPQ
+	tristate "IPQ PWM support"
+	depends on ARCH_QCOM || COMPILE_TEST
+	depends on HAVE_CLK && HAS_IOMEM
+	help
+	  Generic PWM framework driver for IPQ PWM block which supports
+	  4 pwm channels. Each of the these channels can be configured
+	  independent of each other.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called pwm-ipq.
+
 config PWM_IQS620A
 	tristate "Azoteq IQS620A PWM support"
 	depends on MFD_IQS62X || COMPILE_TEST
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 6374d3b1d6f3..73eb955dea1d 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
 obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
 obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
 obj-$(CONFIG_PWM_INTEL_LGM)	+= pwm-intel-lgm.o
+obj-$(CONFIG_PWM_IPQ)		+= pwm-ipq.o
 obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
 obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
 obj-$(CONFIG_PWM_KEEMBAY)	+= pwm-keembay.o
diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
new file mode 100644
index 000000000000..885cf885fcf6
--- /dev/null
+++ b/drivers/pwm/pwm-ipq.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
+/*
+ * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <asm/div64.h>
+#include <linux/of_device.h>
+
+#define CLK_SRC_FREQ		(100*1000*1000)
+#define MAX_PWM_DEVICES		4
+
+/* The default period and duty cycle values to be configured. */
+#define DEFAULT_PERIOD_NS	10
+#define DEFAULT_DUTY_CYCLE_NS	5
+
+/*
+ * Enable bit is set to enable output toggling in pwm device.
+ * Update bit is set to reflect the changed divider and high duration
+ * values in register.
+ */
+#define PWM_ENABLE		0x80000000
+#define PWM_UPDATE		0x40000000
+
+/* The frequency range supported is 1Hz to 100MHz */
+#define MIN_PERIOD_NS	10
+#define MAX_PERIOD_NS	1000000000
+
+/*
+ * The max value specified for each field is based on the number of bits
+ * in the pwm control register for that field
+ */
+#define MAX_PWM_CFG		0xFFFF
+
+#define PWM_CTRL_HI_SHIFT	16
+
+#define PWM_CFG_REG0 0 /*PWM_DIV PWM_HI*/
+#define PWM_CFG_REG1 1 /*ENABLE UPDATE PWM_PRE_DIV*/
+
+struct ipq_pwm_chip {
+	struct pwm_chip chip;
+	struct clk *clk;
+	void __iomem *mem;
+};
+
+static struct ipq_pwm_chip *to_ipq_pwm_chip(struct pwm_chip *chip)
+{
+	return container_of(chip, struct ipq_pwm_chip, chip);
+}
+
+static unsigned ipq_pwm_reg_offset(struct pwm_device *pwm, unsigned reg)
+{
+	return ((pwm->hwpwm * 2) + reg) * 4;
+}
+
+static void config_div_and_duty(struct pwm_device *pwm, int pre_div,
+			unsigned long long pwm_div, unsigned long period_ns,
+			unsigned long long duty_ns)
+{
+	unsigned long hi_dur;
+	unsigned long long quotient;
+	unsigned long val = 0;
+	struct ipq_pwm_chip *ipq_chip = to_ipq_pwm_chip(pwm->chip);
+
+	/*
+	 * high duration = pwm duty * ( pwm div + 1)
+	 * pwm duty = duty_ns / period_ns
+	 */
+	quotient = (pwm_div + 1) * duty_ns;
+	do_div(quotient, period_ns);
+	hi_dur = quotient;
+
+	val |= ((hi_dur & MAX_PWM_CFG) << PWM_CTRL_HI_SHIFT);
+	val |= (pwm_div & MAX_PWM_CFG);
+	writel(val, ipq_chip->mem + ipq_pwm_reg_offset(pwm, PWM_CFG_REG0));
+	val = pre_div & MAX_PWM_CFG;
+	writel(val, ipq_chip->mem + ipq_pwm_reg_offset(pwm, PWM_CFG_REG1));
+}
+
+static int ipq_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct ipq_pwm_chip *ipq_chip = to_ipq_pwm_chip(pwm->chip);
+	unsigned offset = ipq_pwm_reg_offset(pwm, PWM_CFG_REG1);
+	unsigned long val;
+
+	val = readl(ipq_chip->mem + offset);
+	val |= (PWM_ENABLE | PWM_UPDATE);
+	writel(val, ipq_chip->mem + offset);
+
+	return 0;
+}
+
+static void ipq_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct ipq_pwm_chip *ipq_chip = to_ipq_pwm_chip(pwm->chip);
+	unsigned offset = ipq_pwm_reg_offset(pwm, PWM_CFG_REG1);
+	unsigned long val;
+
+	val = readl(ipq_chip->mem + offset);
+	val |= PWM_UPDATE;
+	val &= ~PWM_ENABLE;
+	writel(val, ipq_chip->mem + offset);
+}
+
+static int ipq_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+						int duty_ns, int period_ns)
+{
+	struct ipq_pwm_chip *ipq_chip = to_ipq_pwm_chip(chip);
+	unsigned long freq;
+	int pre_div, close_pre_div, close_pwm_div;
+	int pwm_div;
+	long long diff;
+	unsigned long rate = clk_get_rate(ipq_chip->clk);
+	unsigned long min_diff = rate;
+	uint64_t fin_ps;
+
+	if (period_ns > MAX_PERIOD_NS || period_ns < MIN_PERIOD_NS) {
+		pr_err("PWM Frequency range supported is %dHz to %dHz\n"
+			"Switching to default configuration values\n",
+		       (int)NSEC_PER_SEC / MAX_PERIOD_NS,
+		       (int)NSEC_PER_SEC / MIN_PERIOD_NS);
+		period_ns = DEFAULT_PERIOD_NS;
+		duty_ns = DEFAULT_DUTY_CYCLE_NS;
+		pwm->state.period = period_ns;
+		pwm->state.duty_cycle = duty_ns;
+	}
+
+	/* freq in Hz for period in nano second*/
+	freq = NSEC_PER_SEC / period_ns;
+	fin_ps = (uint64_t)NSEC_PER_SEC * 1000;
+	do_div(fin_ps, rate);
+	close_pre_div = MAX_PWM_CFG;
+	close_pwm_div = MAX_PWM_CFG;
+
+	ipq_pwm_disable(chip, pwm);
+
+	for (pre_div = 0; pre_div <= MAX_PWM_CFG; pre_div++) {
+		pwm_div = DIV_ROUND_CLOSEST_ULL((uint64_t)period_ns * 1000,
+							fin_ps * (pre_div + 1));
+		pwm_div--;
+		if (pwm_div <= MAX_PWM_CFG) {
+			diff = (uint64_t)rate - ((uint64_t)freq * (pre_div + 1) * (pwm_div + 1));
+
+			if (diff < 0)
+				diff = -diff;
+			if (!diff) {
+				close_pre_div = pre_div;
+				close_pwm_div = pwm_div;
+				break;
+			}
+			if (diff < min_diff) {
+				min_diff = diff;
+				close_pre_div = pre_div;
+				close_pwm_div = pwm_div;
+			}
+		}
+	}
+
+	/* config divider values for the closest possible frequency */
+	config_div_and_duty(pwm, close_pre_div, close_pwm_div,
+					period_ns, duty_ns);
+	if (pwm->state.enabled)
+		ipq_pwm_enable(chip, pwm);
+
+	return 0;
+}
+
+static int ipq_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	pwm->state.period = DEFAULT_PERIOD_NS;
+	pwm->state.duty_cycle = DEFAULT_DUTY_CYCLE_NS;
+
+	ipq_pwm_config(chip, pwm, pwm->state.duty_cycle, pwm->state.period);
+
+	return 0;
+}
+
+static void ipq_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	ipq_pwm_disable(chip, pwm);
+}
+
+static struct pwm_ops ipq_pwm_ops = {
+	.request = ipq_pwm_request,
+	.free = ipq_pwm_free,
+	.config = ipq_pwm_config,
+	.enable = ipq_pwm_enable,
+	.disable = ipq_pwm_disable,
+	.owner = THIS_MODULE,
+};
+
+static int ipq_pwm_probe(struct platform_device *pdev)
+{
+	struct ipq_pwm_chip *pwm;
+	struct device *dev;
+	int ret;
+
+	dev = &pdev->dev;
+	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
+	if (!pwm) {
+		dev_err(dev, "failed to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(pdev, pwm);
+
+	pwm->mem = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(pwm->mem))
+		return PTR_ERR(pwm->mem);
+
+	pwm->clk = devm_clk_get(dev, "core");
+	if (!IS_ERR(pwm->clk)) {
+		ret = clk_set_rate(pwm->clk, CLK_SRC_FREQ);
+		if (ret)
+			return ret;
+		ret = clk_prepare_enable(pwm->clk);
+		if (ret)
+			return ret;
+	}
+
+	pwm->chip.dev = dev;
+	pwm->chip.ops = &ipq_pwm_ops;
+	pwm->chip.npwm = MAX_PWM_DEVICES;
+
+	ret = pwmchip_add(&pwm->chip);
+	if (ret < 0) {
+		dev_err(dev, "pwmchip_add() failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ipq_pwm_remove(struct platform_device *pdev)
+{
+	struct ipq_pwm_chip *pwm = platform_get_drvdata(pdev);
+
+	return pwmchip_remove(&pwm->chip);
+}
+
+static const struct of_device_id pwm_ipq_dt_match[] = {
+	{ .compatible = "qcom,pwm-ipq6018", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match);
+
+static struct platform_driver ipq_pwm_driver = {
+	.driver = {
+		.name = "ipq-pwm",
+		.owner = THIS_MODULE,
+		.of_match_table = pwm_ipq_dt_match,
+	},
+	.probe = ipq_pwm_probe,
+	.remove = ipq_pwm_remove,
+};
+
+module_platform_driver(ipq_pwm_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
@ 2021-05-19  7:48 ` Baruch Siach
  0 siblings, 0 replies; 34+ messages in thread
From: Baruch Siach @ 2021-05-19  7:48 UTC (permalink / raw)
  To: Thierry Reding, Uwe Kleine-König, Lee Jones
  Cc: Baruch Siach, Andy Gross, Bjorn Andersson, Balaji Prakash J,
	Rob Herring, Robert Marko, devicetree, linux-arm-msm,
	linux-arm-kernel

Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
driver from downstream Codeaurora kernel tree. Removed support for older
(V1) variants because I have no access to that hardware.

Tested on IPQ6010 based hardware.

Signed-off-by: Baruch Siach <baruch@tkos.co.il>
---
 drivers/pwm/Kconfig   |  12 ++
 drivers/pwm/Makefile  |   1 +
 drivers/pwm/pwm-ipq.c | 263 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 276 insertions(+)
 create mode 100644 drivers/pwm/pwm-ipq.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 9a4f66ae8070..54ef62a27bdc 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -260,6 +260,18 @@ config PWM_INTEL_LGM
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-intel-lgm.
 
+config PWM_IPQ
+	tristate "IPQ PWM support"
+	depends on ARCH_QCOM || COMPILE_TEST
+	depends on HAVE_CLK && HAS_IOMEM
+	help
+	  Generic PWM framework driver for IPQ PWM block which supports
+	  4 pwm channels. Each of the these channels can be configured
+	  independent of each other.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called pwm-ipq.
+
 config PWM_IQS620A
 	tristate "Azoteq IQS620A PWM support"
 	depends on MFD_IQS62X || COMPILE_TEST
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 6374d3b1d6f3..73eb955dea1d 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
 obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
 obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
 obj-$(CONFIG_PWM_INTEL_LGM)	+= pwm-intel-lgm.o
+obj-$(CONFIG_PWM_IPQ)		+= pwm-ipq.o
 obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
 obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
 obj-$(CONFIG_PWM_KEEMBAY)	+= pwm-keembay.o
diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
new file mode 100644
index 000000000000..885cf885fcf6
--- /dev/null
+++ b/drivers/pwm/pwm-ipq.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
+/*
+ * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <asm/div64.h>
+#include <linux/of_device.h>
+
+#define CLK_SRC_FREQ		(100*1000*1000)
+#define MAX_PWM_DEVICES		4
+
+/* The default period and duty cycle values to be configured. */
+#define DEFAULT_PERIOD_NS	10
+#define DEFAULT_DUTY_CYCLE_NS	5
+
+/*
+ * Enable bit is set to enable output toggling in pwm device.
+ * Update bit is set to reflect the changed divider and high duration
+ * values in register.
+ */
+#define PWM_ENABLE		0x80000000
+#define PWM_UPDATE		0x40000000
+
+/* The frequency range supported is 1Hz to 100MHz */
+#define MIN_PERIOD_NS	10
+#define MAX_PERIOD_NS	1000000000
+
+/*
+ * The max value specified for each field is based on the number of bits
+ * in the pwm control register for that field
+ */
+#define MAX_PWM_CFG		0xFFFF
+
+#define PWM_CTRL_HI_SHIFT	16
+
+#define PWM_CFG_REG0 0 /*PWM_DIV PWM_HI*/
+#define PWM_CFG_REG1 1 /*ENABLE UPDATE PWM_PRE_DIV*/
+
+struct ipq_pwm_chip {
+	struct pwm_chip chip;
+	struct clk *clk;
+	void __iomem *mem;
+};
+
+static struct ipq_pwm_chip *to_ipq_pwm_chip(struct pwm_chip *chip)
+{
+	return container_of(chip, struct ipq_pwm_chip, chip);
+}
+
+static unsigned ipq_pwm_reg_offset(struct pwm_device *pwm, unsigned reg)
+{
+	return ((pwm->hwpwm * 2) + reg) * 4;
+}
+
+static void config_div_and_duty(struct pwm_device *pwm, int pre_div,
+			unsigned long long pwm_div, unsigned long period_ns,
+			unsigned long long duty_ns)
+{
+	unsigned long hi_dur;
+	unsigned long long quotient;
+	unsigned long val = 0;
+	struct ipq_pwm_chip *ipq_chip = to_ipq_pwm_chip(pwm->chip);
+
+	/*
+	 * high duration = pwm duty * ( pwm div + 1)
+	 * pwm duty = duty_ns / period_ns
+	 */
+	quotient = (pwm_div + 1) * duty_ns;
+	do_div(quotient, period_ns);
+	hi_dur = quotient;
+
+	val |= ((hi_dur & MAX_PWM_CFG) << PWM_CTRL_HI_SHIFT);
+	val |= (pwm_div & MAX_PWM_CFG);
+	writel(val, ipq_chip->mem + ipq_pwm_reg_offset(pwm, PWM_CFG_REG0));
+	val = pre_div & MAX_PWM_CFG;
+	writel(val, ipq_chip->mem + ipq_pwm_reg_offset(pwm, PWM_CFG_REG1));
+}
+
+static int ipq_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct ipq_pwm_chip *ipq_chip = to_ipq_pwm_chip(pwm->chip);
+	unsigned offset = ipq_pwm_reg_offset(pwm, PWM_CFG_REG1);
+	unsigned long val;
+
+	val = readl(ipq_chip->mem + offset);
+	val |= (PWM_ENABLE | PWM_UPDATE);
+	writel(val, ipq_chip->mem + offset);
+
+	return 0;
+}
+
+static void ipq_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct ipq_pwm_chip *ipq_chip = to_ipq_pwm_chip(pwm->chip);
+	unsigned offset = ipq_pwm_reg_offset(pwm, PWM_CFG_REG1);
+	unsigned long val;
+
+	val = readl(ipq_chip->mem + offset);
+	val |= PWM_UPDATE;
+	val &= ~PWM_ENABLE;
+	writel(val, ipq_chip->mem + offset);
+}
+
+static int ipq_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+						int duty_ns, int period_ns)
+{
+	struct ipq_pwm_chip *ipq_chip = to_ipq_pwm_chip(chip);
+	unsigned long freq;
+	int pre_div, close_pre_div, close_pwm_div;
+	int pwm_div;
+	long long diff;
+	unsigned long rate = clk_get_rate(ipq_chip->clk);
+	unsigned long min_diff = rate;
+	uint64_t fin_ps;
+
+	if (period_ns > MAX_PERIOD_NS || period_ns < MIN_PERIOD_NS) {
+		pr_err("PWM Frequency range supported is %dHz to %dHz\n"
+			"Switching to default configuration values\n",
+		       (int)NSEC_PER_SEC / MAX_PERIOD_NS,
+		       (int)NSEC_PER_SEC / MIN_PERIOD_NS);
+		period_ns = DEFAULT_PERIOD_NS;
+		duty_ns = DEFAULT_DUTY_CYCLE_NS;
+		pwm->state.period = period_ns;
+		pwm->state.duty_cycle = duty_ns;
+	}
+
+	/* freq in Hz for period in nano second*/
+	freq = NSEC_PER_SEC / period_ns;
+	fin_ps = (uint64_t)NSEC_PER_SEC * 1000;
+	do_div(fin_ps, rate);
+	close_pre_div = MAX_PWM_CFG;
+	close_pwm_div = MAX_PWM_CFG;
+
+	ipq_pwm_disable(chip, pwm);
+
+	for (pre_div = 0; pre_div <= MAX_PWM_CFG; pre_div++) {
+		pwm_div = DIV_ROUND_CLOSEST_ULL((uint64_t)period_ns * 1000,
+							fin_ps * (pre_div + 1));
+		pwm_div--;
+		if (pwm_div <= MAX_PWM_CFG) {
+			diff = (uint64_t)rate - ((uint64_t)freq * (pre_div + 1) * (pwm_div + 1));
+
+			if (diff < 0)
+				diff = -diff;
+			if (!diff) {
+				close_pre_div = pre_div;
+				close_pwm_div = pwm_div;
+				break;
+			}
+			if (diff < min_diff) {
+				min_diff = diff;
+				close_pre_div = pre_div;
+				close_pwm_div = pwm_div;
+			}
+		}
+	}
+
+	/* config divider values for the closest possible frequency */
+	config_div_and_duty(pwm, close_pre_div, close_pwm_div,
+					period_ns, duty_ns);
+	if (pwm->state.enabled)
+		ipq_pwm_enable(chip, pwm);
+
+	return 0;
+}
+
+static int ipq_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	pwm->state.period = DEFAULT_PERIOD_NS;
+	pwm->state.duty_cycle = DEFAULT_DUTY_CYCLE_NS;
+
+	ipq_pwm_config(chip, pwm, pwm->state.duty_cycle, pwm->state.period);
+
+	return 0;
+}
+
+static void ipq_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	ipq_pwm_disable(chip, pwm);
+}
+
+static struct pwm_ops ipq_pwm_ops = {
+	.request = ipq_pwm_request,
+	.free = ipq_pwm_free,
+	.config = ipq_pwm_config,
+	.enable = ipq_pwm_enable,
+	.disable = ipq_pwm_disable,
+	.owner = THIS_MODULE,
+};
+
+static int ipq_pwm_probe(struct platform_device *pdev)
+{
+	struct ipq_pwm_chip *pwm;
+	struct device *dev;
+	int ret;
+
+	dev = &pdev->dev;
+	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
+	if (!pwm) {
+		dev_err(dev, "failed to allocate memory\n");
+		return -ENOMEM;
+	}
+
+	platform_set_drvdata(pdev, pwm);
+
+	pwm->mem = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(pwm->mem))
+		return PTR_ERR(pwm->mem);
+
+	pwm->clk = devm_clk_get(dev, "core");
+	if (!IS_ERR(pwm->clk)) {
+		ret = clk_set_rate(pwm->clk, CLK_SRC_FREQ);
+		if (ret)
+			return ret;
+		ret = clk_prepare_enable(pwm->clk);
+		if (ret)
+			return ret;
+	}
+
+	pwm->chip.dev = dev;
+	pwm->chip.ops = &ipq_pwm_ops;
+	pwm->chip.npwm = MAX_PWM_DEVICES;
+
+	ret = pwmchip_add(&pwm->chip);
+	if (ret < 0) {
+		dev_err(dev, "pwmchip_add() failed: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ipq_pwm_remove(struct platform_device *pdev)
+{
+	struct ipq_pwm_chip *pwm = platform_get_drvdata(pdev);
+
+	return pwmchip_remove(&pwm->chip);
+}
+
+static const struct of_device_id pwm_ipq_dt_match[] = {
+	{ .compatible = "qcom,pwm-ipq6018", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match);
+
+static struct platform_driver ipq_pwm_driver = {
+	.driver = {
+		.name = "ipq-pwm",
+		.owner = THIS_MODULE,
+		.of_match_table = pwm_ipq_dt_match,
+	},
+	.probe = ipq_pwm_probe,
+	.remove = ipq_pwm_remove,
+};
+
+module_platform_driver(ipq_pwm_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
-- 
2.30.2


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 2/3] dt-bindings: pwm: add IPQ6018 binding
  2021-05-19  7:48 ` Baruch Siach
@ 2021-05-19  7:48   ` Baruch Siach
  -1 siblings, 0 replies; 34+ messages in thread
From: Baruch Siach @ 2021-05-19  7:48 UTC (permalink / raw)
  To: Thierry Reding, Uwe Kleine-König, Lee Jones
  Cc: Baruch Siach, Andy Gross, Bjorn Andersson, Balaji Prakash J,
	Rob Herring, Robert Marko, devicetree, linux-arm-msm,
	linux-arm-kernel

DT binding for the PWM block in Qualcomm IPQ6018 SoC.

Signed-off-by: Baruch Siach <baruch@tkos.co.il>
---
 .../devicetree/bindings/pwm/ipq-pwm.yaml      | 53 +++++++++++++++++++
 1 file changed, 53 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pwm/ipq-pwm.yaml

diff --git a/Documentation/devicetree/bindings/pwm/ipq-pwm.yaml b/Documentation/devicetree/bindings/pwm/ipq-pwm.yaml
new file mode 100644
index 000000000000..a98f20664702
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/ipq-pwm.yaml
@@ -0,0 +1,53 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pwm/ipq-pwm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm IPQ6018 PWM controller
+
+maintainers:
+  - Baruch Siach <baruch@tkos.co.il>
+
+properties:
+  "#pwm-cells":
+    description: |
+      Should be set to 2.
+
+  compatible:
+    const: qcom,pwm-ipq6018
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  clock-names:
+    const: core
+
+required:
+  - "#pwm-cells"
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/qcom,gcc-ipq6018.h>
+
+    soc {
+        #address-cells = <2>;
+        #size-cells = <2>;
+
+        pwm@1941010 {
+            #pwm-cells = <2>;
+            compatible = "qcom,pwm-ipq6018";
+            reg = <0x0 0x1941010 0x0 0x20>;
+            clocks = <&gcc GCC_ADSS_PWM_CLK>;
+            clock-names = "core";
+        };
+    };
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 2/3] dt-bindings: pwm: add IPQ6018 binding
@ 2021-05-19  7:48   ` Baruch Siach
  0 siblings, 0 replies; 34+ messages in thread
From: Baruch Siach @ 2021-05-19  7:48 UTC (permalink / raw)
  To: Thierry Reding, Uwe Kleine-König, Lee Jones
  Cc: Baruch Siach, Andy Gross, Bjorn Andersson, Balaji Prakash J,
	Rob Herring, Robert Marko, devicetree, linux-arm-msm,
	linux-arm-kernel

DT binding for the PWM block in Qualcomm IPQ6018 SoC.

Signed-off-by: Baruch Siach <baruch@tkos.co.il>
---
 .../devicetree/bindings/pwm/ipq-pwm.yaml      | 53 +++++++++++++++++++
 1 file changed, 53 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pwm/ipq-pwm.yaml

diff --git a/Documentation/devicetree/bindings/pwm/ipq-pwm.yaml b/Documentation/devicetree/bindings/pwm/ipq-pwm.yaml
new file mode 100644
index 000000000000..a98f20664702
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/ipq-pwm.yaml
@@ -0,0 +1,53 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/pwm/ipq-pwm.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm IPQ6018 PWM controller
+
+maintainers:
+  - Baruch Siach <baruch@tkos.co.il>
+
+properties:
+  "#pwm-cells":
+    description: |
+      Should be set to 2.
+
+  compatible:
+    const: qcom,pwm-ipq6018
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  clock-names:
+    const: core
+
+required:
+  - "#pwm-cells"
+  - compatible
+  - reg
+  - clocks
+  - clock-names
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/clock/qcom,gcc-ipq6018.h>
+
+    soc {
+        #address-cells = <2>;
+        #size-cells = <2>;
+
+        pwm@1941010 {
+            #pwm-cells = <2>;
+            compatible = "qcom,pwm-ipq6018";
+            reg = <0x0 0x1941010 0x0 0x20>;
+            clocks = <&gcc GCC_ADSS_PWM_CLK>;
+            clock-names = "core";
+        };
+    };
-- 
2.30.2


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 3/3] arm64: dts: ipq6018: add pwm node
  2021-05-19  7:48 ` Baruch Siach
@ 2021-05-19  7:48   ` Baruch Siach
  -1 siblings, 0 replies; 34+ messages in thread
From: Baruch Siach @ 2021-05-19  7:48 UTC (permalink / raw)
  To: Thierry Reding, Uwe Kleine-König, Lee Jones
  Cc: Baruch Siach, Andy Gross, Bjorn Andersson, Balaji Prakash J,
	Rob Herring, Robert Marko, devicetree, linux-arm-msm,
	linux-arm-kernel

Describe the PWM block on IPQ6018.

Signed-off-by: Baruch Siach <baruch@tkos.co.il>
---
 arch/arm64/boot/dts/qcom/ipq6018.dtsi | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/arch/arm64/boot/dts/qcom/ipq6018.dtsi b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
index 6ee7b99c21ec..2da75bb558ff 100644
--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
@@ -355,6 +355,15 @@ i2c_1: i2c@78b7000 { /* BLSP1 QUP2 */
 			status = "disabled";
 		};
 
+		pwm: pwm@1941010 {
+			#pwm-cells = <2>;
+			compatible = "qcom,pwm-ipq6018";
+			reg = <0x0 0x1941010 0x0 0x20>;
+			clocks = <&gcc GCC_ADSS_PWM_CLK>;
+			clock-names = "core";
+			status = "disabled";
+		};
+
 		qpic_bam: dma-controller@7984000 {
 			compatible = "qcom,bam-v1.7.0";
 			reg = <0x0 0x07984000 0x0 0x1a000>;
-- 
2.30.2


^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 3/3] arm64: dts: ipq6018: add pwm node
@ 2021-05-19  7:48   ` Baruch Siach
  0 siblings, 0 replies; 34+ messages in thread
From: Baruch Siach @ 2021-05-19  7:48 UTC (permalink / raw)
  To: Thierry Reding, Uwe Kleine-König, Lee Jones
  Cc: Baruch Siach, Andy Gross, Bjorn Andersson, Balaji Prakash J,
	Rob Herring, Robert Marko, devicetree, linux-arm-msm,
	linux-arm-kernel

Describe the PWM block on IPQ6018.

Signed-off-by: Baruch Siach <baruch@tkos.co.il>
---
 arch/arm64/boot/dts/qcom/ipq6018.dtsi | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/arch/arm64/boot/dts/qcom/ipq6018.dtsi b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
index 6ee7b99c21ec..2da75bb558ff 100644
--- a/arch/arm64/boot/dts/qcom/ipq6018.dtsi
+++ b/arch/arm64/boot/dts/qcom/ipq6018.dtsi
@@ -355,6 +355,15 @@ i2c_1: i2c@78b7000 { /* BLSP1 QUP2 */
 			status = "disabled";
 		};
 
+		pwm: pwm@1941010 {
+			#pwm-cells = <2>;
+			compatible = "qcom,pwm-ipq6018";
+			reg = <0x0 0x1941010 0x0 0x20>;
+			clocks = <&gcc GCC_ADSS_PWM_CLK>;
+			clock-names = "core";
+			status = "disabled";
+		};
+
 		qpic_bam: dma-controller@7984000 {
 			compatible = "qcom,bam-v1.7.0";
 			reg = <0x0 0x07984000 0x0 0x1a000>;
-- 
2.30.2


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* Re: [PATCH 2/3] dt-bindings: pwm: add IPQ6018 binding
  2021-05-19  7:48   ` Baruch Siach
@ 2021-05-21  1:34     ` Rob Herring
  -1 siblings, 0 replies; 34+ messages in thread
From: Rob Herring @ 2021-05-21  1:34 UTC (permalink / raw)
  To: Baruch Siach
  Cc: Thierry Reding, Uwe Kleine-König, Lee Jones, Andy Gross,
	Bjorn Andersson, Balaji Prakash J, Robert Marko, devicetree,
	linux-arm-msm, linux-arm-kernel

On Wed, May 19, 2021 at 10:48:45AM +0300, Baruch Siach wrote:
> DT binding for the PWM block in Qualcomm IPQ6018 SoC.
> 
> Signed-off-by: Baruch Siach <baruch@tkos.co.il>
> ---
>  .../devicetree/bindings/pwm/ipq-pwm.yaml      | 53 +++++++++++++++++++
>  1 file changed, 53 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/pwm/ipq-pwm.yaml
> 
> diff --git a/Documentation/devicetree/bindings/pwm/ipq-pwm.yaml b/Documentation/devicetree/bindings/pwm/ipq-pwm.yaml
> new file mode 100644
> index 000000000000..a98f20664702
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/pwm/ipq-pwm.yaml
> @@ -0,0 +1,53 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/pwm/ipq-pwm.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Qualcomm IPQ6018 PWM controller
> +
> +maintainers:
> +  - Baruch Siach <baruch@tkos.co.il>
> +
> +properties:
> +  "#pwm-cells":
> +    description: |
> +      Should be set to 2.

constraints, not free form text:

const: 2

> +
> +  compatible:
> +    const: qcom,pwm-ipq6018
> +
> +  reg:
> +    maxItems: 1
> +
> +  clocks:
> +    maxItems: 1
> +
> +  clock-names:
> +    const: core
> +
> +required:
> +  - "#pwm-cells"
> +  - compatible
> +  - reg
> +  - clocks
> +  - clock-names
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/clock/qcom,gcc-ipq6018.h>
> +
> +    soc {
> +        #address-cells = <2>;
> +        #size-cells = <2>;
> +
> +        pwm@1941010 {
> +            #pwm-cells = <2>;
> +            compatible = "qcom,pwm-ipq6018";
> +            reg = <0x0 0x1941010 0x0 0x20>;
> +            clocks = <&gcc GCC_ADSS_PWM_CLK>;
> +            clock-names = "core";
> +        };
> +    };
> -- 
> 2.30.2
> 

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 2/3] dt-bindings: pwm: add IPQ6018 binding
@ 2021-05-21  1:34     ` Rob Herring
  0 siblings, 0 replies; 34+ messages in thread
From: Rob Herring @ 2021-05-21  1:34 UTC (permalink / raw)
  To: Baruch Siach
  Cc: Thierry Reding, Uwe Kleine-König, Lee Jones, Andy Gross,
	Bjorn Andersson, Balaji Prakash J, Robert Marko, devicetree,
	linux-arm-msm, linux-arm-kernel

On Wed, May 19, 2021 at 10:48:45AM +0300, Baruch Siach wrote:
> DT binding for the PWM block in Qualcomm IPQ6018 SoC.
> 
> Signed-off-by: Baruch Siach <baruch@tkos.co.il>
> ---
>  .../devicetree/bindings/pwm/ipq-pwm.yaml      | 53 +++++++++++++++++++
>  1 file changed, 53 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/pwm/ipq-pwm.yaml
> 
> diff --git a/Documentation/devicetree/bindings/pwm/ipq-pwm.yaml b/Documentation/devicetree/bindings/pwm/ipq-pwm.yaml
> new file mode 100644
> index 000000000000..a98f20664702
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/pwm/ipq-pwm.yaml
> @@ -0,0 +1,53 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/pwm/ipq-pwm.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Qualcomm IPQ6018 PWM controller
> +
> +maintainers:
> +  - Baruch Siach <baruch@tkos.co.il>
> +
> +properties:
> +  "#pwm-cells":
> +    description: |
> +      Should be set to 2.

constraints, not free form text:

const: 2

> +
> +  compatible:
> +    const: qcom,pwm-ipq6018
> +
> +  reg:
> +    maxItems: 1
> +
> +  clocks:
> +    maxItems: 1
> +
> +  clock-names:
> +    const: core
> +
> +required:
> +  - "#pwm-cells"
> +  - compatible
> +  - reg
> +  - clocks
> +  - clock-names
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/clock/qcom,gcc-ipq6018.h>
> +
> +    soc {
> +        #address-cells = <2>;
> +        #size-cells = <2>;
> +
> +        pwm@1941010 {
> +            #pwm-cells = <2>;
> +            compatible = "qcom,pwm-ipq6018";
> +            reg = <0x0 0x1941010 0x0 0x20>;
> +            clocks = <&gcc GCC_ADSS_PWM_CLK>;
> +            clock-names = "core";
> +        };
> +    };
> -- 
> 2.30.2
> 

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
  2021-05-19  7:48 ` Baruch Siach
@ 2021-05-22 21:35   ` Uwe Kleine-König
  -1 siblings, 0 replies; 34+ messages in thread
From: Uwe Kleine-König @ 2021-05-22 21:35 UTC (permalink / raw)
  To: Baruch Siach
  Cc: Thierry Reding, Lee Jones, Andy Gross, Bjorn Andersson,
	Balaji Prakash J, Rob Herring, Robert Marko, devicetree,
	linux-arm-msm, linux-arm-kernel

[-- Attachment #1: Type: text/plain, Size: 11457 bytes --]

Hello Baruch,

On Wed, May 19, 2021 at 10:48:44AM +0300, Baruch Siach wrote:
> Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
> driver from downstream Codeaurora kernel tree. Removed support for older
> (V1) variants because I have no access to that hardware.
> 
> Tested on IPQ6010 based hardware.
> 
> Signed-off-by: Baruch Siach <baruch@tkos.co.il>
> ---
>  drivers/pwm/Kconfig   |  12 ++
>  drivers/pwm/Makefile  |   1 +
>  drivers/pwm/pwm-ipq.c | 263 ++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 276 insertions(+)
>  create mode 100644 drivers/pwm/pwm-ipq.c
> 
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 9a4f66ae8070..54ef62a27bdc 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -260,6 +260,18 @@ config PWM_INTEL_LGM
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called pwm-intel-lgm.
>  
> +config PWM_IPQ
> +	tristate "IPQ PWM support"
> +	depends on ARCH_QCOM || COMPILE_TEST
> +	depends on HAVE_CLK && HAS_IOMEM
> +	help
> +	  Generic PWM framework driver for IPQ PWM block which supports
> +	  4 pwm channels. Each of the these channels can be configured
> +	  independent of each other.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called pwm-ipq.
> +
>  config PWM_IQS620A
>  	tristate "Azoteq IQS620A PWM support"
>  	depends on MFD_IQS62X || COMPILE_TEST
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 6374d3b1d6f3..73eb955dea1d 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -22,6 +22,7 @@ obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
>  obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
>  obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
>  obj-$(CONFIG_PWM_INTEL_LGM)	+= pwm-intel-lgm.o
> +obj-$(CONFIG_PWM_IPQ)		+= pwm-ipq.o
>  obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
>  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>  obj-$(CONFIG_PWM_KEEMBAY)	+= pwm-keembay.o
> diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
> new file mode 100644
> index 000000000000..885cf885fcf6
> --- /dev/null
> +++ b/drivers/pwm/pwm-ipq.c
> @@ -0,0 +1,263 @@
> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
> +/*
> + * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +#include <asm/div64.h>
> +#include <linux/of_device.h>
> +
> +#define CLK_SRC_FREQ		(100*1000*1000)
> +#define MAX_PWM_DEVICES		4
> +
> +/* The default period and duty cycle values to be configured. */
> +#define DEFAULT_PERIOD_NS	10
> +#define DEFAULT_DUTY_CYCLE_NS	5

These define's names are too generic. Please prefix them with your
driver's name.

> +/*
> + * Enable bit is set to enable output toggling in pwm device.
> + * Update bit is set to reflect the changed divider and high duration
> + * values in register.
> + */
> +#define PWM_ENABLE		0x80000000
> +#define PWM_UPDATE		0x40000000
> +
> +/* The frequency range supported is 1Hz to 100MHz */
> +#define MIN_PERIOD_NS	10
> +#define MAX_PERIOD_NS	1000000000
> +
> +/*
> + * The max value specified for each field is based on the number of bits
> + * in the pwm control register for that field
> + */
> +#define MAX_PWM_CFG		0xFFFF
> +
> +#define PWM_CTRL_HI_SHIFT	16
> +
> +#define PWM_CFG_REG0 0 /*PWM_DIV PWM_HI*/
> +#define PWM_CFG_REG1 1 /*ENABLE UPDATE PWM_PRE_DIV*/
> +
> +struct ipq_pwm_chip {
> +	struct pwm_chip chip;
> +	struct clk *clk;
> +	void __iomem *mem;
> +};
> +
> +static struct ipq_pwm_chip *to_ipq_pwm_chip(struct pwm_chip *chip)
> +{
> +	return container_of(chip, struct ipq_pwm_chip, chip);
> +}
> +
> +static unsigned ipq_pwm_reg_offset(struct pwm_device *pwm, unsigned reg)
> +{
> +	return ((pwm->hwpwm * 2) + reg) * 4;
> +}
> +
> +static void config_div_and_duty(struct pwm_device *pwm, int pre_div,
> +			unsigned long long pwm_div, unsigned long period_ns,
> +			unsigned long long duty_ns)
> +{
> +	unsigned long hi_dur;
> +	unsigned long long quotient;
> +	unsigned long val = 0;
> +	struct ipq_pwm_chip *ipq_chip = to_ipq_pwm_chip(pwm->chip);
> +
> +	/*
> +	 * high duration = pwm duty * ( pwm div + 1)

s/( /(/

> +	 * pwm duty = duty_ns / period_ns
> +	 */
> +	quotient = (pwm_div + 1) * duty_ns;
> +	do_div(quotient, period_ns);
> +	hi_dur = quotient;
> +
> +	val |= ((hi_dur & MAX_PWM_CFG) << PWM_CTRL_HI_SHIFT);
> +	val |= (pwm_div & MAX_PWM_CFG);
> +	writel(val, ipq_chip->mem + ipq_pwm_reg_offset(pwm, PWM_CFG_REG0));
> +	val = pre_div & MAX_PWM_CFG;
> +	writel(val, ipq_chip->mem + ipq_pwm_reg_offset(pwm, PWM_CFG_REG1));
> +}
> +
> +static int ipq_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	struct ipq_pwm_chip *ipq_chip = to_ipq_pwm_chip(pwm->chip);
> +	unsigned offset = ipq_pwm_reg_offset(pwm, PWM_CFG_REG1);
> +	unsigned long val;
> +
> +	val = readl(ipq_chip->mem + offset);
> +	val |= (PWM_ENABLE | PWM_UPDATE);

The parenthesis are not needed here.

> +	writel(val, ipq_chip->mem + offset);
> +
> +	return 0;
> +}
> +
> +static void ipq_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	struct ipq_pwm_chip *ipq_chip = to_ipq_pwm_chip(pwm->chip);
> +	unsigned offset = ipq_pwm_reg_offset(pwm, PWM_CFG_REG1);
> +	unsigned long val;
> +
> +	val = readl(ipq_chip->mem + offset);
> +	val |= PWM_UPDATE;

What is the effect of this register bit?

Does the output become inactive or does it freeze at state that happens
to be emitted when the ENABLE bit is removed?

> +	val &= ~PWM_ENABLE;
> +	writel(val, ipq_chip->mem + offset);
> +}
> +
> +static int ipq_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
> +						int duty_ns, int period_ns)
> +{
> +	struct ipq_pwm_chip *ipq_chip = to_ipq_pwm_chip(chip);
> +	unsigned long freq;
> +	int pre_div, close_pre_div, close_pwm_div;
> +	int pwm_div;
> +	long long diff;
> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
> +	unsigned long min_diff = rate;
> +	uint64_t fin_ps;
> +
> +	if (period_ns > MAX_PERIOD_NS || period_ns < MIN_PERIOD_NS) {
> +		pr_err("PWM Frequency range supported is %dHz to %dHz\n"
> +			"Switching to default configuration values\n",
> +		       (int)NSEC_PER_SEC / MAX_PERIOD_NS,
> +		       (int)NSEC_PER_SEC / MIN_PERIOD_NS);
> +		period_ns = DEFAULT_PERIOD_NS;
> +		duty_ns = DEFAULT_DUTY_CYCLE_NS;
> +		pwm->state.period = period_ns;
> +		pwm->state.duty_cycle = duty_ns;
> +	}

Please implement the biggest period not bigger than the requested
period. That is do something like:

	if (period_ns < MIN_PERIOD_NS)
		return -ERANGE;

	if (period_ns > MAX_PERIOD_NS)
		period_ns = MAX_PERIOD_NS;

> +
> +	/* freq in Hz for period in nano second*/
> +	freq = NSEC_PER_SEC / period_ns;
> +	fin_ps = (uint64_t)NSEC_PER_SEC * 1000;
> +	do_div(fin_ps, rate);
> +	close_pre_div = MAX_PWM_CFG;
> +	close_pwm_div = MAX_PWM_CFG;
> +
> +	ipq_pwm_disable(chip, pwm);

This is necessary?

> +	for (pre_div = 0; pre_div <= MAX_PWM_CFG; pre_div++) {
> +		pwm_div = DIV_ROUND_CLOSEST_ULL((uint64_t)period_ns * 1000,
> +							fin_ps * (pre_div + 1));
> +		pwm_div--;

What if pwm_div was 0 before? (Didn't check if this can happen, maybe it
doesn't with period_ns >= MIN_PERIOD_NS?)

> +		if (pwm_div <= MAX_PWM_CFG) {
> +			diff = (uint64_t)rate - ((uint64_t)freq * (pre_div + 1) * (pwm_div + 1));
> +
> +			if (diff < 0)
> +				diff = -diff;
> +			if (!diff) {
> +				close_pre_div = pre_div;
> +				close_pwm_div = pwm_div;
> +				break;
> +			}
> +			if (diff < min_diff) {
> +				min_diff = diff;
> +				close_pre_div = pre_div;
> +				close_pwm_div = pwm_div;
> +			}

As written above: Never pick a period bigger than the requested period.

> +		}
> +	}
> +
> +	/* config divider values for the closest possible frequency */
> +	config_div_and_duty(pwm, close_pre_div, close_pwm_div,
> +					period_ns, duty_ns);

Please align follow up lines to the opening (.

> +	if (pwm->state.enabled)
> +		ipq_pwm_enable(chip, pwm);
> +
> +	return 0;
> +}
> +
> +static int ipq_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	pwm->state.period = DEFAULT_PERIOD_NS;
> +	pwm->state.duty_cycle = DEFAULT_DUTY_CYCLE_NS;
> +
> +	ipq_pwm_config(chip, pwm, pwm->state.duty_cycle, pwm->state.period);

.request is not supposed to modify the emitted wave form.

> +
> +	return 0;
> +}
> +
> +static void ipq_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	ipq_pwm_disable(chip, pwm);

neither is .free.

> +}
> +
> +static struct pwm_ops ipq_pwm_ops = {
> +	.request = ipq_pwm_request,
> +	.free = ipq_pwm_free,
> +	.config = ipq_pwm_config,
> +	.enable = ipq_pwm_enable,
> +	.disable = ipq_pwm_disable,

Please implement .apply instead of .config/.enable/.disable.

If you had enabled PWM_DEBUG during your tests you would have noticed
yourself ...

> +	.owner = THIS_MODULE,
> +};
> +
> +static int ipq_pwm_probe(struct platform_device *pdev)
> +{
> +	struct ipq_pwm_chip *pwm;
> +	struct device *dev;
> +	int ret;
> +
> +	dev = &pdev->dev;
> +	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
> +	if (!pwm) {
> +		dev_err(dev, "failed to allocate memory\n");

You're not supposed to emit a message for -ENOMEM type errors. This is
already loud enough.

> +		return -ENOMEM;
> +	}
> +
> +	platform_set_drvdata(pdev, pwm);
> +
> +	pwm->mem = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(pwm->mem))
> +		return PTR_ERR(pwm->mem);
> +
> +	pwm->clk = devm_clk_get(dev, "core");
> +	if (!IS_ERR(pwm->clk)) {

If devm_clk_get() returns an error you should return an error, too.
You might want to use devm_clk_get_optional().

> +		ret = clk_set_rate(pwm->clk, CLK_SRC_FREQ);
> +		if (ret)
> +			return ret;
> +		ret = clk_prepare_enable(pwm->clk);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	pwm->chip.dev = dev;
> +	pwm->chip.ops = &ipq_pwm_ops;
> +	pwm->chip.npwm = MAX_PWM_DEVICES;
> +
> +	ret = pwmchip_add(&pwm->chip);
> +	if (ret < 0) {
> +		dev_err(dev, "pwmchip_add() failed: %d\n", ret);
> +		return ret;

	return dev_err_probe(dev, ret, "...")

please. And you have to disable the clock again.

> +	}
> +
> +	return 0;
> +}
> +
> +static int ipq_pwm_remove(struct platform_device *pdev)
> +{
> +	struct ipq_pwm_chip *pwm = platform_get_drvdata(pdev);
> +
> +	return pwmchip_remove(&pwm->chip);

Please ignore the return value of pwmchip_remove(). This is eventually
changed to return void.

> +}
> +
> +static const struct of_device_id pwm_ipq_dt_match[] = {
> +	{ .compatible = "qcom,pwm-ipq6018", },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match);
> +
> +static struct platform_driver ipq_pwm_driver = {
> +	.driver = {
> +		.name = "ipq-pwm",
> +		.owner = THIS_MODULE,
> +		.of_match_table = pwm_ipq_dt_match,
> +	},
> +	.probe = ipq_pwm_probe,
> +	.remove = ipq_pwm_remove,
> +};
> +
> +module_platform_driver(ipq_pwm_driver);
> +
> +MODULE_LICENSE("Dual BSD/GPL");

Best regards
Uwe

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

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
@ 2021-05-22 21:35   ` Uwe Kleine-König
  0 siblings, 0 replies; 34+ messages in thread
From: Uwe Kleine-König @ 2021-05-22 21:35 UTC (permalink / raw)
  To: Baruch Siach
  Cc: Thierry Reding, Lee Jones, Andy Gross, Bjorn Andersson,
	Balaji Prakash J, Rob Herring, Robert Marko, devicetree,
	linux-arm-msm, linux-arm-kernel


[-- Attachment #1.1: Type: text/plain, Size: 11457 bytes --]

Hello Baruch,

On Wed, May 19, 2021 at 10:48:44AM +0300, Baruch Siach wrote:
> Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
> driver from downstream Codeaurora kernel tree. Removed support for older
> (V1) variants because I have no access to that hardware.
> 
> Tested on IPQ6010 based hardware.
> 
> Signed-off-by: Baruch Siach <baruch@tkos.co.il>
> ---
>  drivers/pwm/Kconfig   |  12 ++
>  drivers/pwm/Makefile  |   1 +
>  drivers/pwm/pwm-ipq.c | 263 ++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 276 insertions(+)
>  create mode 100644 drivers/pwm/pwm-ipq.c
> 
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 9a4f66ae8070..54ef62a27bdc 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -260,6 +260,18 @@ config PWM_INTEL_LGM
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called pwm-intel-lgm.
>  
> +config PWM_IPQ
> +	tristate "IPQ PWM support"
> +	depends on ARCH_QCOM || COMPILE_TEST
> +	depends on HAVE_CLK && HAS_IOMEM
> +	help
> +	  Generic PWM framework driver for IPQ PWM block which supports
> +	  4 pwm channels. Each of the these channels can be configured
> +	  independent of each other.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called pwm-ipq.
> +
>  config PWM_IQS620A
>  	tristate "Azoteq IQS620A PWM support"
>  	depends on MFD_IQS62X || COMPILE_TEST
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 6374d3b1d6f3..73eb955dea1d 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -22,6 +22,7 @@ obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
>  obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
>  obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
>  obj-$(CONFIG_PWM_INTEL_LGM)	+= pwm-intel-lgm.o
> +obj-$(CONFIG_PWM_IPQ)		+= pwm-ipq.o
>  obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
>  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>  obj-$(CONFIG_PWM_KEEMBAY)	+= pwm-keembay.o
> diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
> new file mode 100644
> index 000000000000..885cf885fcf6
> --- /dev/null
> +++ b/drivers/pwm/pwm-ipq.c
> @@ -0,0 +1,263 @@
> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
> +/*
> + * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +#include <asm/div64.h>
> +#include <linux/of_device.h>
> +
> +#define CLK_SRC_FREQ		(100*1000*1000)
> +#define MAX_PWM_DEVICES		4
> +
> +/* The default period and duty cycle values to be configured. */
> +#define DEFAULT_PERIOD_NS	10
> +#define DEFAULT_DUTY_CYCLE_NS	5

These define's names are too generic. Please prefix them with your
driver's name.

> +/*
> + * Enable bit is set to enable output toggling in pwm device.
> + * Update bit is set to reflect the changed divider and high duration
> + * values in register.
> + */
> +#define PWM_ENABLE		0x80000000
> +#define PWM_UPDATE		0x40000000
> +
> +/* The frequency range supported is 1Hz to 100MHz */
> +#define MIN_PERIOD_NS	10
> +#define MAX_PERIOD_NS	1000000000
> +
> +/*
> + * The max value specified for each field is based on the number of bits
> + * in the pwm control register for that field
> + */
> +#define MAX_PWM_CFG		0xFFFF
> +
> +#define PWM_CTRL_HI_SHIFT	16
> +
> +#define PWM_CFG_REG0 0 /*PWM_DIV PWM_HI*/
> +#define PWM_CFG_REG1 1 /*ENABLE UPDATE PWM_PRE_DIV*/
> +
> +struct ipq_pwm_chip {
> +	struct pwm_chip chip;
> +	struct clk *clk;
> +	void __iomem *mem;
> +};
> +
> +static struct ipq_pwm_chip *to_ipq_pwm_chip(struct pwm_chip *chip)
> +{
> +	return container_of(chip, struct ipq_pwm_chip, chip);
> +}
> +
> +static unsigned ipq_pwm_reg_offset(struct pwm_device *pwm, unsigned reg)
> +{
> +	return ((pwm->hwpwm * 2) + reg) * 4;
> +}
> +
> +static void config_div_and_duty(struct pwm_device *pwm, int pre_div,
> +			unsigned long long pwm_div, unsigned long period_ns,
> +			unsigned long long duty_ns)
> +{
> +	unsigned long hi_dur;
> +	unsigned long long quotient;
> +	unsigned long val = 0;
> +	struct ipq_pwm_chip *ipq_chip = to_ipq_pwm_chip(pwm->chip);
> +
> +	/*
> +	 * high duration = pwm duty * ( pwm div + 1)

s/( /(/

> +	 * pwm duty = duty_ns / period_ns
> +	 */
> +	quotient = (pwm_div + 1) * duty_ns;
> +	do_div(quotient, period_ns);
> +	hi_dur = quotient;
> +
> +	val |= ((hi_dur & MAX_PWM_CFG) << PWM_CTRL_HI_SHIFT);
> +	val |= (pwm_div & MAX_PWM_CFG);
> +	writel(val, ipq_chip->mem + ipq_pwm_reg_offset(pwm, PWM_CFG_REG0));
> +	val = pre_div & MAX_PWM_CFG;
> +	writel(val, ipq_chip->mem + ipq_pwm_reg_offset(pwm, PWM_CFG_REG1));
> +}
> +
> +static int ipq_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	struct ipq_pwm_chip *ipq_chip = to_ipq_pwm_chip(pwm->chip);
> +	unsigned offset = ipq_pwm_reg_offset(pwm, PWM_CFG_REG1);
> +	unsigned long val;
> +
> +	val = readl(ipq_chip->mem + offset);
> +	val |= (PWM_ENABLE | PWM_UPDATE);

The parenthesis are not needed here.

> +	writel(val, ipq_chip->mem + offset);
> +
> +	return 0;
> +}
> +
> +static void ipq_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	struct ipq_pwm_chip *ipq_chip = to_ipq_pwm_chip(pwm->chip);
> +	unsigned offset = ipq_pwm_reg_offset(pwm, PWM_CFG_REG1);
> +	unsigned long val;
> +
> +	val = readl(ipq_chip->mem + offset);
> +	val |= PWM_UPDATE;

What is the effect of this register bit?

Does the output become inactive or does it freeze at state that happens
to be emitted when the ENABLE bit is removed?

> +	val &= ~PWM_ENABLE;
> +	writel(val, ipq_chip->mem + offset);
> +}
> +
> +static int ipq_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
> +						int duty_ns, int period_ns)
> +{
> +	struct ipq_pwm_chip *ipq_chip = to_ipq_pwm_chip(chip);
> +	unsigned long freq;
> +	int pre_div, close_pre_div, close_pwm_div;
> +	int pwm_div;
> +	long long diff;
> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
> +	unsigned long min_diff = rate;
> +	uint64_t fin_ps;
> +
> +	if (period_ns > MAX_PERIOD_NS || period_ns < MIN_PERIOD_NS) {
> +		pr_err("PWM Frequency range supported is %dHz to %dHz\n"
> +			"Switching to default configuration values\n",
> +		       (int)NSEC_PER_SEC / MAX_PERIOD_NS,
> +		       (int)NSEC_PER_SEC / MIN_PERIOD_NS);
> +		period_ns = DEFAULT_PERIOD_NS;
> +		duty_ns = DEFAULT_DUTY_CYCLE_NS;
> +		pwm->state.period = period_ns;
> +		pwm->state.duty_cycle = duty_ns;
> +	}

Please implement the biggest period not bigger than the requested
period. That is do something like:

	if (period_ns < MIN_PERIOD_NS)
		return -ERANGE;

	if (period_ns > MAX_PERIOD_NS)
		period_ns = MAX_PERIOD_NS;

> +
> +	/* freq in Hz for period in nano second*/
> +	freq = NSEC_PER_SEC / period_ns;
> +	fin_ps = (uint64_t)NSEC_PER_SEC * 1000;
> +	do_div(fin_ps, rate);
> +	close_pre_div = MAX_PWM_CFG;
> +	close_pwm_div = MAX_PWM_CFG;
> +
> +	ipq_pwm_disable(chip, pwm);

This is necessary?

> +	for (pre_div = 0; pre_div <= MAX_PWM_CFG; pre_div++) {
> +		pwm_div = DIV_ROUND_CLOSEST_ULL((uint64_t)period_ns * 1000,
> +							fin_ps * (pre_div + 1));
> +		pwm_div--;

What if pwm_div was 0 before? (Didn't check if this can happen, maybe it
doesn't with period_ns >= MIN_PERIOD_NS?)

> +		if (pwm_div <= MAX_PWM_CFG) {
> +			diff = (uint64_t)rate - ((uint64_t)freq * (pre_div + 1) * (pwm_div + 1));
> +
> +			if (diff < 0)
> +				diff = -diff;
> +			if (!diff) {
> +				close_pre_div = pre_div;
> +				close_pwm_div = pwm_div;
> +				break;
> +			}
> +			if (diff < min_diff) {
> +				min_diff = diff;
> +				close_pre_div = pre_div;
> +				close_pwm_div = pwm_div;
> +			}

As written above: Never pick a period bigger than the requested period.

> +		}
> +	}
> +
> +	/* config divider values for the closest possible frequency */
> +	config_div_and_duty(pwm, close_pre_div, close_pwm_div,
> +					period_ns, duty_ns);

Please align follow up lines to the opening (.

> +	if (pwm->state.enabled)
> +		ipq_pwm_enable(chip, pwm);
> +
> +	return 0;
> +}
> +
> +static int ipq_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	pwm->state.period = DEFAULT_PERIOD_NS;
> +	pwm->state.duty_cycle = DEFAULT_DUTY_CYCLE_NS;
> +
> +	ipq_pwm_config(chip, pwm, pwm->state.duty_cycle, pwm->state.period);

.request is not supposed to modify the emitted wave form.

> +
> +	return 0;
> +}
> +
> +static void ipq_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
> +{
> +	ipq_pwm_disable(chip, pwm);

neither is .free.

> +}
> +
> +static struct pwm_ops ipq_pwm_ops = {
> +	.request = ipq_pwm_request,
> +	.free = ipq_pwm_free,
> +	.config = ipq_pwm_config,
> +	.enable = ipq_pwm_enable,
> +	.disable = ipq_pwm_disable,

Please implement .apply instead of .config/.enable/.disable.

If you had enabled PWM_DEBUG during your tests you would have noticed
yourself ...

> +	.owner = THIS_MODULE,
> +};
> +
> +static int ipq_pwm_probe(struct platform_device *pdev)
> +{
> +	struct ipq_pwm_chip *pwm;
> +	struct device *dev;
> +	int ret;
> +
> +	dev = &pdev->dev;
> +	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
> +	if (!pwm) {
> +		dev_err(dev, "failed to allocate memory\n");

You're not supposed to emit a message for -ENOMEM type errors. This is
already loud enough.

> +		return -ENOMEM;
> +	}
> +
> +	platform_set_drvdata(pdev, pwm);
> +
> +	pwm->mem = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(pwm->mem))
> +		return PTR_ERR(pwm->mem);
> +
> +	pwm->clk = devm_clk_get(dev, "core");
> +	if (!IS_ERR(pwm->clk)) {

If devm_clk_get() returns an error you should return an error, too.
You might want to use devm_clk_get_optional().

> +		ret = clk_set_rate(pwm->clk, CLK_SRC_FREQ);
> +		if (ret)
> +			return ret;
> +		ret = clk_prepare_enable(pwm->clk);
> +		if (ret)
> +			return ret;
> +	}
> +
> +	pwm->chip.dev = dev;
> +	pwm->chip.ops = &ipq_pwm_ops;
> +	pwm->chip.npwm = MAX_PWM_DEVICES;
> +
> +	ret = pwmchip_add(&pwm->chip);
> +	if (ret < 0) {
> +		dev_err(dev, "pwmchip_add() failed: %d\n", ret);
> +		return ret;

	return dev_err_probe(dev, ret, "...")

please. And you have to disable the clock again.

> +	}
> +
> +	return 0;
> +}
> +
> +static int ipq_pwm_remove(struct platform_device *pdev)
> +{
> +	struct ipq_pwm_chip *pwm = platform_get_drvdata(pdev);
> +
> +	return pwmchip_remove(&pwm->chip);

Please ignore the return value of pwmchip_remove(). This is eventually
changed to return void.

> +}
> +
> +static const struct of_device_id pwm_ipq_dt_match[] = {
> +	{ .compatible = "qcom,pwm-ipq6018", },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match);
> +
> +static struct platform_driver ipq_pwm_driver = {
> +	.driver = {
> +		.name = "ipq-pwm",
> +		.owner = THIS_MODULE,
> +		.of_match_table = pwm_ipq_dt_match,
> +	},
> +	.probe = ipq_pwm_probe,
> +	.remove = ipq_pwm_remove,
> +};
> +
> +module_platform_driver(ipq_pwm_driver);
> +
> +MODULE_LICENSE("Dual BSD/GPL");

Best regards
Uwe

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

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
  2021-05-19  7:48 ` Baruch Siach
@ 2021-05-22 21:37   ` Uwe Kleine-König
  -1 siblings, 0 replies; 34+ messages in thread
From: Uwe Kleine-König @ 2021-05-22 21:37 UTC (permalink / raw)
  To: Baruch Siach
  Cc: Thierry Reding, Lee Jones, Andy Gross, Bjorn Andersson,
	Balaji Prakash J, Rob Herring, Robert Marko, devicetree,
	linux-arm-msm, linux-arm-kernel

[-- Attachment #1: Type: text/plain, Size: 261 bytes --]

Hello again,

for v2 please make sure to add linux-pwm@vger.kernel.org to Cc.

Best regards
Uwe

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

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
@ 2021-05-22 21:37   ` Uwe Kleine-König
  0 siblings, 0 replies; 34+ messages in thread
From: Uwe Kleine-König @ 2021-05-22 21:37 UTC (permalink / raw)
  To: Baruch Siach
  Cc: Thierry Reding, Lee Jones, Andy Gross, Bjorn Andersson,
	Balaji Prakash J, Rob Herring, Robert Marko, devicetree,
	linux-arm-msm, linux-arm-kernel


[-- Attachment #1.1: Type: text/plain, Size: 261 bytes --]

Hello again,

for v2 please make sure to add linux-pwm@vger.kernel.org to Cc.

Best regards
Uwe

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

[-- Attachment #1.2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]

[-- Attachment #2: Type: text/plain, Size: 176 bytes --]

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
  2021-05-22 21:35   ` Uwe Kleine-König
@ 2021-05-23 15:54     ` Baruch Siach
  -1 siblings, 0 replies; 34+ messages in thread
From: Baruch Siach @ 2021-05-23 15:54 UTC (permalink / raw)
  To: Uwe Kleine-König
  Cc: Thierry Reding, Lee Jones, Andy Gross, Bjorn Andersson,
	Balaji Prakash J, Rob Herring, Robert Marko, devicetree,
	linux-arm-msm, linux-arm-kernel, linux-pwm

Hi Uwe,

Thanks for your review comments.

On Sun, May 23 2021, Uwe Kleine-König wrote:
> On Wed, May 19, 2021 at 10:48:44AM +0300, Baruch Siach wrote:
>> Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
>> driver from downstream Codeaurora kernel tree. Removed support for older
>> (V1) variants because I have no access to that hardware.
>> 
>> Tested on IPQ6010 based hardware.
>> 
>> Signed-off-by: Baruch Siach <baruch@tkos.co.il>

[...]

>> +static void ipq_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
>> +{
>> +	struct ipq_pwm_chip *ipq_chip = to_ipq_pwm_chip(pwm->chip);
>> +	unsigned offset = ipq_pwm_reg_offset(pwm, PWM_CFG_REG1);
>> +	unsigned long val;
>> +
>> +	val = readl(ipq_chip->mem + offset);
>> +	val |= PWM_UPDATE;
>
> What is the effect of this register bit?
>
> Does the output become inactive or does it freeze at state that happens
> to be emitted when the ENABLE bit is removed?

I don't know. PWM does not work when this bit is not set here. The
original downstream driver[1] does not set this bit on disable. But it
also enables PWM unconditionally on .config. I added the 'enabled' check
in .config, and then PWM stopped working even when enabled later. It was
only by accident (excess copy/paste) that I found this workaround.

A comment on the original code says that PWM_UPDATE is "auto cleared".
This is evidently not true on my hardware (IPQ6010). This might be true
for older variants of this PWM block. Unfortunately, I have no access to
hardware documentation.

[1] https://source.codeaurora.org/quic/qsdk/oss/kernel/linux-ipq-5.4/tree/drivers/pwm/pwm-ipq.c?h=NHSS.QSDK.11.4.1.r1&id=9e4627b7088b0c06ddd910c8770274d26613de9e

baruch

-- 
                                                     ~. .~   Tk Open Systems
=}------------------------------------------------ooO--U--Ooo------------{=
   - baruch@tkos.co.il - tel: +972.52.368.4656, http://www.tkos.co.il -

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
@ 2021-05-23 15:54     ` Baruch Siach
  0 siblings, 0 replies; 34+ messages in thread
From: Baruch Siach @ 2021-05-23 15:54 UTC (permalink / raw)
  To: Uwe Kleine-König
  Cc: Thierry Reding, Lee Jones, Andy Gross, Bjorn Andersson,
	Balaji Prakash J, Rob Herring, Robert Marko, devicetree,
	linux-arm-msm, linux-arm-kernel, linux-pwm

Hi Uwe,

Thanks for your review comments.

On Sun, May 23 2021, Uwe Kleine-König wrote:
> On Wed, May 19, 2021 at 10:48:44AM +0300, Baruch Siach wrote:
>> Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
>> driver from downstream Codeaurora kernel tree. Removed support for older
>> (V1) variants because I have no access to that hardware.
>> 
>> Tested on IPQ6010 based hardware.
>> 
>> Signed-off-by: Baruch Siach <baruch@tkos.co.il>

[...]

>> +static void ipq_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
>> +{
>> +	struct ipq_pwm_chip *ipq_chip = to_ipq_pwm_chip(pwm->chip);
>> +	unsigned offset = ipq_pwm_reg_offset(pwm, PWM_CFG_REG1);
>> +	unsigned long val;
>> +
>> +	val = readl(ipq_chip->mem + offset);
>> +	val |= PWM_UPDATE;
>
> What is the effect of this register bit?
>
> Does the output become inactive or does it freeze at state that happens
> to be emitted when the ENABLE bit is removed?

I don't know. PWM does not work when this bit is not set here. The
original downstream driver[1] does not set this bit on disable. But it
also enables PWM unconditionally on .config. I added the 'enabled' check
in .config, and then PWM stopped working even when enabled later. It was
only by accident (excess copy/paste) that I found this workaround.

A comment on the original code says that PWM_UPDATE is "auto cleared".
This is evidently not true on my hardware (IPQ6010). This might be true
for older variants of this PWM block. Unfortunately, I have no access to
hardware documentation.

[1] https://source.codeaurora.org/quic/qsdk/oss/kernel/linux-ipq-5.4/tree/drivers/pwm/pwm-ipq.c?h=NHSS.QSDK.11.4.1.r1&id=9e4627b7088b0c06ddd910c8770274d26613de9e

baruch

-- 
                                                     ~. .~   Tk Open Systems
=}------------------------------------------------ooO--U--Ooo------------{=
   - baruch@tkos.co.il - tel: +972.52.368.4656, http://www.tkos.co.il -

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
  2023-09-22  8:35         ` Baruch Siach
@ 2023-09-22 10:56           ` Devi Priya
  -1 siblings, 0 replies; 34+ messages in thread
From: Devi Priya @ 2023-09-22 10:56 UTC (permalink / raw)
  To: Baruch Siach
  Cc: Thierry Reding, Uwe Kleine-König, Andy Gross,
	Bjorn Andersson, Rob Herring, Robert Marko, Kathiravan T,
	linux-pwm, devicetree, linux-arm-msm, linux-arm-kernel



On 9/22/2023 2:05 PM, Baruch Siach wrote:
> Hi Devi,
> 
> On Fri, Sep 22 2023, Devi Priya wrote:
>> On 9/15/2023 12:06 PM, Baruch Siach wrote:
>>> Hi Devi,
>>> [ Dropped/updated codeaurora.org addresses ]
>>> On Fri, Sep 15 2023, Devi Priya wrote:
>>>> On 2/7/2022 3:00 PM, Baruch Siach wrote:
>>>>> From: Baruch Siach <baruch.siach@siklu.com>
>>>>> Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
>>>>> driver from downstream Codeaurora kernel tree. Removed support for older
>>>>> (V1) variants because I have no access to that hardware.
>>>>> Tested on IPQ6010 based hardware.
>>>>> Signed-off-by: Baruch Siach <baruch.siach@siklu.com>
>>>>> ---
>>>>> v11:
>>>>
>>>> Just curious to know if you have plans to post the next revision!
>>> I have been waiting for comments from pwm maintainers before sending the
>>> next revision.
>>> Unfortunately since then I lost access to the hardware, so I can't test
>>> suggested implementation changes.  The only pending issue in v11 is the
>>> trivial change that Nathan Chancellor suggested, which should be safe.
>>> If you like to take it from here you are welcome.
>>
>> Could you pls point me to the pending comment that has to be addressed.
>> Was not able to find any open comments in the latest series.
>> https://lore.kernel.org/linux-arm-msm/8a331c88-c7d4-3a14-0ec3-fd616ea24a99@quicinc.com/
> 
> See here:
> 
>    https://lore.kernel.org/all/YgK63cI177ZeF5v1@dev-arch.archlinux-ax161/

Great, thanks!
Devi Priya
> 
> baruch
> 
>>>>> Address comment from Uwe Kleine-König:
>>>>>      Drop redundant registers field comments
>>>>>      Fix period limit check in .apply
>>>>>      Clarify the comment explaining skip of pre_div > pwm_div values
>>>>>      Add explicit check for clock rate within limit
>>>>>      Add comment explaining the selection of initial pre_div
>>>>>      Use pwm_div division with remainder instead of separate diff calculation
>>>>>      Round up duty_cycle calculation in .get_state
>>>>> v10:
>>>>>      Restore round up in pwm_div calculation; otherwise diff is always <=
>>>>>      0, so only bingo match works
>>>>>      Don't overwrite min_diff on every loop iteration
>>>>> v9:
>>>>> Address comment from Uwe Kleine-König:
>>>>>      Use period_ns*rate in dividers calculation for better accuracy
>>>>>      Round down pre_div and pwm_div
>>>>>      Add a comment explaining why pwm_div can't underflow
>>>>>      Add a comment explaining why pre_div > pwm_div end the search loop
>>>>>      Drop 'CFG_' from register macros
>>>>>      Rename to_ipq_pwm_chip() to ipq_pwm_from_chip()
>>>>>      Change bare 'unsigned' to 'unsigned int'
>>>>>      Clarify the comment on separate REG1 write for enable/disable
>>>>>      Round up the period value in .get_state
>>>>>      Use direct readl/writel so no need to check for regmap errors
>>>>> v7:
>>>>>      Change 'offset' to 'reg' for the tcsr offset (Rob)
>>>>>      Drop clock name; there is only one clock (Bjorn)
>>>>>      Simplify probe failure code path (Bjorn)
>>>>> v6:
>>>>> Address Uwe Kleine-König review comments:
>>>>>      Drop IPQ_PWM_MAX_DEVICES
>>>>>      Rely on assigned-clock-rates; drop IPQ_PWM_CLK_SRC_FREQ
>>>>>      Simplify register offset calculation
>>>>>      Calculate duty cycle more precisely
>>>>>      Refuse to set inverted polarity
>>>>>      Drop redundant IPQ_PWM_REG1_ENABLE bit clear
>>>>>      Remove x1000 factor in pwm_div calculation, use rate directly, and round
>>>>> up
>>>>>      Choose initial pre_div such that pwm_div < IPQ_PWM_MAX_DIV
>>>>>      Ensure pre_div <= pwm_div
>>>>>      Rename close_ to best_
>>>>>      Explain in comment why effective_div doesn't overflow
>>>>>      Limit pwm_div to IPQ_PWM_MAX_DIV - 1 to allow 100% duty cycle
>>>>>      Disable clock only after pwmchip_remove()
>>>>>      const pwm_ops
>>>>> Other changes:
>>>>>      Add missing linux/bitfield.h header include (kernel test robot)
>>>>>      Adjust code for PWM device node under TCSR (Rob Herring)
>>>>> v5:
>>>>> Use &tcsr_q6 syscon to access registers (Bjorn Andersson)
>>>>> Address Uwe Kleine-König review comments:
>>>>>      Implement .get_state()
>>>>>      Add IPQ_PWM_ prefix to local macros
>>>>>      Use GENMASK/BIT/FIELD_PREP for register fields access
>>>>>      Make type of config_div_and_duty() parameters consistent
>>>>>      Derive IPQ_PWM_MIN_PERIOD_NS from IPQ_PWM_CLK_SRC_FREQ
>>>>>      Integrate enable/disable into config_div_and_duty() to save register
>>>>> read,
>>>>>      and reduce frequency glitch on update
>>>>>      Use min() instead of min_t()
>>>>>      Fix comment format
>>>>>      Use dev_err_probe() to indicate probe step failure
>>>>>      Add missing clk_disable_unprepare() in .remove
>>>>>      Don't set .owner
>>>>> v4:
>>>>>      Use div64_u64() to fix link for 32-bit targets ((kernel test robot
>>>>>      <lkp@intel.com>, Uwe Kleine-König)
>>>>> v3:
>>>>>      s/qcom,pwm-ipq6018/qcom,ipq6018-pwm/ (Rob Herring)
>>>>>      Fix integer overflow on 32-bit targets (kernel test robot
>>>>> <lkp@intel.com>)
>>>>> v2:
>>>>> Address Uwe Kleine-König review comments:
>>>>>      Fix period calculation when out of range
>>>>>      Don't set period larger than requested
>>>>>      Remove PWM disable on configuration change
>>>>>      Implement .apply instead of non-atomic .config/.enable/.disable
>>>>>      Don't modify PWM on .request/.free
>>>>>      Check pwm_div underflow
>>>>>      Fix various code and comment formatting issues
>>>>> Other changes:
>>>>>      Use u64 divisor safe division
>>>>>      Remove now empty .request/.free
>>>>> ---
>>>>>     drivers/pwm/Kconfig   |  12 ++
>>>>>     drivers/pwm/Makefile  |   1 +
>>>>>     drivers/pwm/pwm-ipq.c | 281 ++++++++++++++++++++++++++++++++++++++++++
>>>>>     3 files changed, 294 insertions(+)
>>>>>     create mode 100644 drivers/pwm/pwm-ipq.c
>>>>> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
>>>>> index 21e3b05a5153..e39718137ecd 100644
>>>>> --- a/drivers/pwm/Kconfig
>>>>> +++ b/drivers/pwm/Kconfig
>>>>> @@ -260,6 +260,18 @@ config PWM_INTEL_LGM
>>>>>     	  To compile this driver as a module, choose M here: the module
>>>>>     	  will be called pwm-intel-lgm.
>>>>>     +config PWM_IPQ
>>>>> +	tristate "IPQ PWM support"
>>>>> +	depends on ARCH_QCOM || COMPILE_TEST
>>>>> +	depends on HAVE_CLK && HAS_IOMEM
>>>>> +	help
>>>>> +	  Generic PWM framework driver for IPQ PWM block which supports
>>>>> +	  4 pwm channels. Each of the these channels can be configured
>>>>> +	  independent of each other.
>>>>> +
>>>>> +	  To compile this driver as a module, choose M here: the module
>>>>> +	  will be called pwm-ipq.
>>>>> +
>>>>>     config PWM_IQS620A
>>>>>     	tristate "Azoteq IQS620A PWM support"
>>>>>     	depends on MFD_IQS62X || COMPILE_TEST
>>>>> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
>>>>> index 708840b7fba8..7402feae4b36 100644
>>>>> --- a/drivers/pwm/Makefile
>>>>> +++ b/drivers/pwm/Makefile
>>>>> @@ -22,6 +22,7 @@ obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
>>>>>     obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
>>>>>     obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
>>>>>     obj-$(CONFIG_PWM_INTEL_LGM)	+= pwm-intel-lgm.o
>>>>> +obj-$(CONFIG_PWM_IPQ)		+= pwm-ipq.o
>>>>>     obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
>>>>>     obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>>>>>     obj-$(CONFIG_PWM_KEEMBAY)	+= pwm-keembay.o
>>>>> diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
>>>>> new file mode 100644
>>>>> index 000000000000..994027290bcb
>>>>> --- /dev/null
>>>>> +++ b/drivers/pwm/pwm-ipq.c
>>>>> @@ -0,0 +1,281 @@
>>>>> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
>>>>> +/*
>>>>> + * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
>>>>> + */
>>>>> +
>>>>> +#include <linux/module.h>
>>>>> +#include <linux/platform_device.h>
>>>>> +#include <linux/pwm.h>
>>>>> +#include <linux/clk.h>
>>>>> +#include <linux/io.h>
>>>>> +#include <linux/of.h>
>>>>> +#include <linux/math64.h>
>>>>> +#include <linux/of_device.h>
>>>>> +#include <linux/bitfield.h>
>>>>> +#include <linux/units.h>
>>>>> +
>>>>> +/* The frequency range supported is 1 Hz to clock rate */
>>>>> +#define IPQ_PWM_MAX_PERIOD_NS	((u64)NSEC_PER_SEC)
>>>>> +
>>>>> +/*
>>>>> + * The max value specified for each field is based on the number of bits
>>>>> + * in the pwm control register for that field
>>>>> + */
>>>>> +#define IPQ_PWM_MAX_DIV		0xFFFF
>>>>> +
>>>>> +/*
>>>>> + * Two 32-bit registers for each PWM: REG0, and REG1.
>>>>> + * Base offset for PWM #i is at 8 * #i.
>>>>> + */
>>>>> +#define IPQ_PWM_REG0			0
>>>>> +#define IPQ_PWM_REG0_PWM_DIV		GENMASK(15, 0)
>>>>> +#define IPQ_PWM_REG0_HI_DURATION	GENMASK(31, 16)
>>>>> +
>>>>> +#define IPQ_PWM_REG1			4
>>>>> +#define IPQ_PWM_REG1_PRE_DIV		GENMASK(15, 0)
>>>>> +/*
>>>>> + * Enable bit is set to enable output toggling in pwm device.
>>>>> + * Update bit is set to reflect the changed divider and high duration
>>>>> + * values in register.
>>>>> + */
>>>>> +#define IPQ_PWM_REG1_UPDATE		BIT(30)
>>>>> +#define IPQ_PWM_REG1_ENABLE		BIT(31)
>>>>> +
>>>>> +
>>>>> +struct ipq_pwm_chip {
>>>>> +	struct pwm_chip chip;
>>>>> +	struct clk *clk;
>>>>> +	void __iomem *mem;
>>>>> +};
>>>>> +
>>>>> +static struct ipq_pwm_chip *ipq_pwm_from_chip(struct pwm_chip *chip)
>>>>> +{
>>>>> +	return container_of(chip, struct ipq_pwm_chip, chip);
>>>>> +}
>>>>> +
>>>>> +static unsigned int ipq_pwm_reg_read(struct pwm_device *pwm, unsigned int reg)
>>>>> +{
>>>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
>>>>> +	unsigned int off = 8 * pwm->hwpwm + reg;
>>>>> +
>>>>> +	return readl(ipq_chip->mem + off);
>>>>> +}
>>>>> +
>>>>> +static void ipq_pwm_reg_write(struct pwm_device *pwm, unsigned int reg,
>>>>> +			      unsigned int val)
>>>>> +{
>>>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
>>>>> +	unsigned int off = 8 * pwm->hwpwm + reg;
>>>>> +
>>>>> +	writel(val, ipq_chip->mem + off);
>>>>> +}
>>>>> +
>>>>> +static void config_div_and_duty(struct pwm_device *pwm, unsigned int pre_div,
>>>>> +			unsigned int pwm_div, unsigned long rate, u64 duty_ns,
>>>>> +			bool enable)
>>>>> +{
>>>>> +	unsigned long hi_dur;
>>>>> +	unsigned long val = 0;
>>>>> +
>>>>> +	/*
>>>>> +	 * high duration = pwm duty * (pwm div + 1)
>>>>> +	 * pwm duty = duty_ns / period_ns
>>>>> +	 */
>>>>> +	hi_dur = div64_u64(duty_ns * rate, (pre_div + 1) * NSEC_PER_SEC);
>>>>> +
>>>>> +	val = FIELD_PREP(IPQ_PWM_REG0_HI_DURATION, hi_dur) |
>>>>> +		FIELD_PREP(IPQ_PWM_REG0_PWM_DIV, pwm_div);
>>>>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG0, val);
>>>>> +
>>>>> +	val = FIELD_PREP(IPQ_PWM_REG1_PRE_DIV, pre_div);
>>>>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
>>>>> +
>>>>> +	/* PWM enable toggle needs a separate write to REG1 */
>>>>> +	val |= IPQ_PWM_REG1_UPDATE;
>>>>> +	if (enable)
>>>>> +		val |= IPQ_PWM_REG1_ENABLE;
>>>>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
>>>>> +}
>>>>> +
>>>>> +static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
>>>>> +			 const struct pwm_state *state)
>>>>> +{
>>>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
>>>>> +	unsigned int pre_div, pwm_div, best_pre_div, best_pwm_div;
>>>>> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
>>>>> +	u64 period_ns, duty_ns, period_rate;
>>>>> +	u64 min_diff;
>>>>> +
>>>>> +	if (state->polarity != PWM_POLARITY_NORMAL)
>>>>> +		return -EINVAL;
>>>>> +
>>>>> +	if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC, rate))
>>>>> +		return -ERANGE;
>>>>> +
>>>>> +	period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
>>>>> +	duty_ns = min(state->duty_cycle, period_ns);
>>>>> +
>>>>> +	/*
>>>>> +	 * period_ns is 1G or less. As long as rate is less than 16 GHz,
>>>>> +	 * period_rate does not overflow. Make that explicit.
>>>>> +	 */
>>>>> +	if (rate > 16ULL * GIGA)
>>>>> +		return -EINVAL;
>>>>> +	period_rate = period_ns * rate;
>>>>> +	best_pre_div = IPQ_PWM_MAX_DIV;
>>>>> +	best_pwm_div = IPQ_PWM_MAX_DIV;
>>>>> +	/*
>>>>> +	 * We don't need to consider pre_div values smaller than
>>>>> +	 *
>>>>> +	 *                              period_rate
>>>>> +	 *  pre_div_min := ------------------------------------
>>>>> +	 *                 NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1)
>>>>> +	 *
>>>>> +	 * because pre_div = pre_div_min results in a better
>>>>> +	 * approximation.
>>>>> +	 */
>>>>> +	pre_div = div64_u64(period_rate,
>>>>> +			(u64)NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1));
>>>>> +	min_diff = period_rate;
>>>>> +
>>>>> +	for (; pre_div <= IPQ_PWM_MAX_DIV; pre_div++) {
>>>>> +		u64 remainder;
>>>>> +
>>>>> +		pwm_div = div64_u64_rem(period_rate,
>>>>> +				(u64)NSEC_PER_SEC * (pre_div + 1), &remainder);
>>>>> +		/* pwm_div is unsigned; the check below catches underflow */
>>>>> +		pwm_div--;
>>>>> +
>>>>> +		/*
>>>>> +		 * Swapping values for pre_div and pwm_div produces the same
>>>>> +		 * period length. So we can skip all settings with pre_div >
>>>>> +		 * pwm_div which results in bigger constraints for selecting
>>>>> +		 * the duty_cycle than with the two values swapped.
>>>>> +		 */
>>>>> +		if (pre_div > pwm_div)
>>>>> +			break;
>>>>> +
>>>>> +		/*
>>>>> +		 * Make sure we can do 100% duty cycle where
>>>>> +		 * hi_dur == pwm_div + 1
>>>>> +		 */
>>>>> +		if (pwm_div > IPQ_PWM_MAX_DIV - 1)
>>>>> +			continue;
>>>>> +
>>>>> +		if (remainder < min_diff) {
>>>>> +			best_pre_div = pre_div;
>>>>> +			best_pwm_div = pwm_div;
>>>>> +			min_diff = remainder;
>>>>> +
>>>>> +			if (min_diff == 0) /* bingo */
>>>>> +				break;
>>>>> +		}
>>>>> +	}
>>>>> +
>>>>> +	/* config divider values for the closest possible frequency */
>>>>> +	config_div_and_duty(pwm, best_pre_div, best_pwm_div,
>>>>> +			    rate, duty_ns, state->enabled);
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static void ipq_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
>>>>> +			      struct pwm_state *state)
>>>>> +{
>>>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
>>>>> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
>>>>> +	unsigned int pre_div, pwm_div, hi_dur;
>>>>> +	u64 effective_div, hi_div;
>>>>> +	u32 reg0, reg1;
>>>>> +
>>>>> +	reg0 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG0);
>>>>> +	reg1 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG1);
>>>>> +
>>>>> +	state->polarity = PWM_POLARITY_NORMAL;
>>>>> +	state->enabled = reg1 & IPQ_PWM_REG1_ENABLE;
>>>>> +
>>>>> +	pwm_div = FIELD_GET(IPQ_PWM_REG0_PWM_DIV, reg0);
>>>>> +	hi_dur = FIELD_GET(IPQ_PWM_REG0_HI_DURATION, reg0);
>>>>> +	pre_div = FIELD_GET(IPQ_PWM_REG1_PRE_DIV, reg1);
>>>>> +
>>>>> +	/* No overflow here, both pre_div and pwm_div <= 0xffff */
>>>>> +	effective_div = (u64)(pre_div + 1) * (pwm_div + 1);
>>>>> +	state->period = DIV64_U64_ROUND_UP(effective_div * NSEC_PER_SEC, rate);
>>>>> +
>>>>> +	hi_div = hi_dur * (pre_div + 1);
>>>>> +	state->duty_cycle = DIV64_U64_ROUND_UP(hi_div * NSEC_PER_SEC, rate);
>>>>> +}
>>>>> +
>>>>> +static const struct pwm_ops ipq_pwm_ops = {
>>>>> +	.apply = ipq_pwm_apply,
>>>>> +	.get_state = ipq_pwm_get_state,
>>>>> +	.owner = THIS_MODULE,
>>>>> +};
>>>>> +
>>>>> +static int ipq_pwm_probe(struct platform_device *pdev)
>>>>> +{
>>>>> +	struct ipq_pwm_chip *pwm;
>>>>> +	struct device *dev = &pdev->dev;
>>>>> +	int ret;
>>>>> +
>>>>> +	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
>>>>> +	if (!pwm)
>>>>> +		return -ENOMEM;
>>>>> +
>>>>> +	platform_set_drvdata(pdev, pwm);
>>>>> +
>>>>> +	pwm->mem = devm_platform_ioremap_resource(pdev, 0);
>>>>> +	if (IS_ERR(pwm->mem))
>>>>> +		return dev_err_probe(dev, PTR_ERR(pwm->mem),
>>>>> +				"regs map failed");
>>>>> +
>>>>> +	pwm->clk = devm_clk_get(dev, NULL);
>>>>> +	if (IS_ERR(pwm->clk))
>>>>> +		return dev_err_probe(dev, PTR_ERR(pwm->clk),
>>>>> +				"failed to get clock");
>>>>> +
>>>>> +	ret = clk_prepare_enable(pwm->clk);
>>>>> +	if (ret)
>>>>> +		return dev_err_probe(dev, ret, "clock enable failed");
>>>>> +
>>>>> +	pwm->chip.dev = dev;
>>>>> +	pwm->chip.ops = &ipq_pwm_ops;
>>>>> +	pwm->chip.npwm = 4;
>>>>> +
>>>>> +	ret = pwmchip_add(&pwm->chip);
>>>>> +	if (ret < 0) {
>>>>> +		dev_err_probe(dev, ret, "pwmchip_add() failed\n");
>>>>> +		clk_disable_unprepare(pwm->clk);
>>>>> +	}
>>>>> +
>>>>> +	return ret;
>>>>> +}
>>>>> +
>>>>> +static int ipq_pwm_remove(struct platform_device *pdev)
>>>>> +{
>>>>> +	struct ipq_pwm_chip *pwm = platform_get_drvdata(pdev);
>>>>> +
>>>>> +	pwmchip_remove(&pwm->chip);
>>>>> +	clk_disable_unprepare(pwm->clk);
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static const struct of_device_id pwm_ipq_dt_match[] = {
>>>>> +	{ .compatible = "qcom,ipq6018-pwm", },
>>>>> +	{}
>>>>> +};
>>>>> +MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match);
>>>>> +
>>>>> +static struct platform_driver ipq_pwm_driver = {
>>>>> +	.driver = {
>>>>> +		.name = "ipq-pwm",
>>>>> +		.of_match_table = pwm_ipq_dt_match,
>>>>> +	},
>>>>> +	.probe = ipq_pwm_probe,
>>>>> +	.remove = ipq_pwm_remove,
>>>>> +};
>>>>> +
>>>>> +module_platform_driver(ipq_pwm_driver);
>>>>> +
>>>>> +MODULE_LICENSE("Dual BSD/GPL");
>>>
> 
> 

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
@ 2023-09-22 10:56           ` Devi Priya
  0 siblings, 0 replies; 34+ messages in thread
From: Devi Priya @ 2023-09-22 10:56 UTC (permalink / raw)
  To: Baruch Siach
  Cc: Thierry Reding, Uwe Kleine-König, Andy Gross,
	Bjorn Andersson, Rob Herring, Robert Marko, Kathiravan T,
	linux-pwm, devicetree, linux-arm-msm, linux-arm-kernel



On 9/22/2023 2:05 PM, Baruch Siach wrote:
> Hi Devi,
> 
> On Fri, Sep 22 2023, Devi Priya wrote:
>> On 9/15/2023 12:06 PM, Baruch Siach wrote:
>>> Hi Devi,
>>> [ Dropped/updated codeaurora.org addresses ]
>>> On Fri, Sep 15 2023, Devi Priya wrote:
>>>> On 2/7/2022 3:00 PM, Baruch Siach wrote:
>>>>> From: Baruch Siach <baruch.siach@siklu.com>
>>>>> Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
>>>>> driver from downstream Codeaurora kernel tree. Removed support for older
>>>>> (V1) variants because I have no access to that hardware.
>>>>> Tested on IPQ6010 based hardware.
>>>>> Signed-off-by: Baruch Siach <baruch.siach@siklu.com>
>>>>> ---
>>>>> v11:
>>>>
>>>> Just curious to know if you have plans to post the next revision!
>>> I have been waiting for comments from pwm maintainers before sending the
>>> next revision.
>>> Unfortunately since then I lost access to the hardware, so I can't test
>>> suggested implementation changes.  The only pending issue in v11 is the
>>> trivial change that Nathan Chancellor suggested, which should be safe.
>>> If you like to take it from here you are welcome.
>>
>> Could you pls point me to the pending comment that has to be addressed.
>> Was not able to find any open comments in the latest series.
>> https://lore.kernel.org/linux-arm-msm/8a331c88-c7d4-3a14-0ec3-fd616ea24a99@quicinc.com/
> 
> See here:
> 
>    https://lore.kernel.org/all/YgK63cI177ZeF5v1@dev-arch.archlinux-ax161/

Great, thanks!
Devi Priya
> 
> baruch
> 
>>>>> Address comment from Uwe Kleine-König:
>>>>>      Drop redundant registers field comments
>>>>>      Fix period limit check in .apply
>>>>>      Clarify the comment explaining skip of pre_div > pwm_div values
>>>>>      Add explicit check for clock rate within limit
>>>>>      Add comment explaining the selection of initial pre_div
>>>>>      Use pwm_div division with remainder instead of separate diff calculation
>>>>>      Round up duty_cycle calculation in .get_state
>>>>> v10:
>>>>>      Restore round up in pwm_div calculation; otherwise diff is always <=
>>>>>      0, so only bingo match works
>>>>>      Don't overwrite min_diff on every loop iteration
>>>>> v9:
>>>>> Address comment from Uwe Kleine-König:
>>>>>      Use period_ns*rate in dividers calculation for better accuracy
>>>>>      Round down pre_div and pwm_div
>>>>>      Add a comment explaining why pwm_div can't underflow
>>>>>      Add a comment explaining why pre_div > pwm_div end the search loop
>>>>>      Drop 'CFG_' from register macros
>>>>>      Rename to_ipq_pwm_chip() to ipq_pwm_from_chip()
>>>>>      Change bare 'unsigned' to 'unsigned int'
>>>>>      Clarify the comment on separate REG1 write for enable/disable
>>>>>      Round up the period value in .get_state
>>>>>      Use direct readl/writel so no need to check for regmap errors
>>>>> v7:
>>>>>      Change 'offset' to 'reg' for the tcsr offset (Rob)
>>>>>      Drop clock name; there is only one clock (Bjorn)
>>>>>      Simplify probe failure code path (Bjorn)
>>>>> v6:
>>>>> Address Uwe Kleine-König review comments:
>>>>>      Drop IPQ_PWM_MAX_DEVICES
>>>>>      Rely on assigned-clock-rates; drop IPQ_PWM_CLK_SRC_FREQ
>>>>>      Simplify register offset calculation
>>>>>      Calculate duty cycle more precisely
>>>>>      Refuse to set inverted polarity
>>>>>      Drop redundant IPQ_PWM_REG1_ENABLE bit clear
>>>>>      Remove x1000 factor in pwm_div calculation, use rate directly, and round
>>>>> up
>>>>>      Choose initial pre_div such that pwm_div < IPQ_PWM_MAX_DIV
>>>>>      Ensure pre_div <= pwm_div
>>>>>      Rename close_ to best_
>>>>>      Explain in comment why effective_div doesn't overflow
>>>>>      Limit pwm_div to IPQ_PWM_MAX_DIV - 1 to allow 100% duty cycle
>>>>>      Disable clock only after pwmchip_remove()
>>>>>      const pwm_ops
>>>>> Other changes:
>>>>>      Add missing linux/bitfield.h header include (kernel test robot)
>>>>>      Adjust code for PWM device node under TCSR (Rob Herring)
>>>>> v5:
>>>>> Use &tcsr_q6 syscon to access registers (Bjorn Andersson)
>>>>> Address Uwe Kleine-König review comments:
>>>>>      Implement .get_state()
>>>>>      Add IPQ_PWM_ prefix to local macros
>>>>>      Use GENMASK/BIT/FIELD_PREP for register fields access
>>>>>      Make type of config_div_and_duty() parameters consistent
>>>>>      Derive IPQ_PWM_MIN_PERIOD_NS from IPQ_PWM_CLK_SRC_FREQ
>>>>>      Integrate enable/disable into config_div_and_duty() to save register
>>>>> read,
>>>>>      and reduce frequency glitch on update
>>>>>      Use min() instead of min_t()
>>>>>      Fix comment format
>>>>>      Use dev_err_probe() to indicate probe step failure
>>>>>      Add missing clk_disable_unprepare() in .remove
>>>>>      Don't set .owner
>>>>> v4:
>>>>>      Use div64_u64() to fix link for 32-bit targets ((kernel test robot
>>>>>      <lkp@intel.com>, Uwe Kleine-König)
>>>>> v3:
>>>>>      s/qcom,pwm-ipq6018/qcom,ipq6018-pwm/ (Rob Herring)
>>>>>      Fix integer overflow on 32-bit targets (kernel test robot
>>>>> <lkp@intel.com>)
>>>>> v2:
>>>>> Address Uwe Kleine-König review comments:
>>>>>      Fix period calculation when out of range
>>>>>      Don't set period larger than requested
>>>>>      Remove PWM disable on configuration change
>>>>>      Implement .apply instead of non-atomic .config/.enable/.disable
>>>>>      Don't modify PWM on .request/.free
>>>>>      Check pwm_div underflow
>>>>>      Fix various code and comment formatting issues
>>>>> Other changes:
>>>>>      Use u64 divisor safe division
>>>>>      Remove now empty .request/.free
>>>>> ---
>>>>>     drivers/pwm/Kconfig   |  12 ++
>>>>>     drivers/pwm/Makefile  |   1 +
>>>>>     drivers/pwm/pwm-ipq.c | 281 ++++++++++++++++++++++++++++++++++++++++++
>>>>>     3 files changed, 294 insertions(+)
>>>>>     create mode 100644 drivers/pwm/pwm-ipq.c
>>>>> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
>>>>> index 21e3b05a5153..e39718137ecd 100644
>>>>> --- a/drivers/pwm/Kconfig
>>>>> +++ b/drivers/pwm/Kconfig
>>>>> @@ -260,6 +260,18 @@ config PWM_INTEL_LGM
>>>>>     	  To compile this driver as a module, choose M here: the module
>>>>>     	  will be called pwm-intel-lgm.
>>>>>     +config PWM_IPQ
>>>>> +	tristate "IPQ PWM support"
>>>>> +	depends on ARCH_QCOM || COMPILE_TEST
>>>>> +	depends on HAVE_CLK && HAS_IOMEM
>>>>> +	help
>>>>> +	  Generic PWM framework driver for IPQ PWM block which supports
>>>>> +	  4 pwm channels. Each of the these channels can be configured
>>>>> +	  independent of each other.
>>>>> +
>>>>> +	  To compile this driver as a module, choose M here: the module
>>>>> +	  will be called pwm-ipq.
>>>>> +
>>>>>     config PWM_IQS620A
>>>>>     	tristate "Azoteq IQS620A PWM support"
>>>>>     	depends on MFD_IQS62X || COMPILE_TEST
>>>>> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
>>>>> index 708840b7fba8..7402feae4b36 100644
>>>>> --- a/drivers/pwm/Makefile
>>>>> +++ b/drivers/pwm/Makefile
>>>>> @@ -22,6 +22,7 @@ obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
>>>>>     obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
>>>>>     obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
>>>>>     obj-$(CONFIG_PWM_INTEL_LGM)	+= pwm-intel-lgm.o
>>>>> +obj-$(CONFIG_PWM_IPQ)		+= pwm-ipq.o
>>>>>     obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
>>>>>     obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>>>>>     obj-$(CONFIG_PWM_KEEMBAY)	+= pwm-keembay.o
>>>>> diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
>>>>> new file mode 100644
>>>>> index 000000000000..994027290bcb
>>>>> --- /dev/null
>>>>> +++ b/drivers/pwm/pwm-ipq.c
>>>>> @@ -0,0 +1,281 @@
>>>>> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
>>>>> +/*
>>>>> + * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
>>>>> + */
>>>>> +
>>>>> +#include <linux/module.h>
>>>>> +#include <linux/platform_device.h>
>>>>> +#include <linux/pwm.h>
>>>>> +#include <linux/clk.h>
>>>>> +#include <linux/io.h>
>>>>> +#include <linux/of.h>
>>>>> +#include <linux/math64.h>
>>>>> +#include <linux/of_device.h>
>>>>> +#include <linux/bitfield.h>
>>>>> +#include <linux/units.h>
>>>>> +
>>>>> +/* The frequency range supported is 1 Hz to clock rate */
>>>>> +#define IPQ_PWM_MAX_PERIOD_NS	((u64)NSEC_PER_SEC)
>>>>> +
>>>>> +/*
>>>>> + * The max value specified for each field is based on the number of bits
>>>>> + * in the pwm control register for that field
>>>>> + */
>>>>> +#define IPQ_PWM_MAX_DIV		0xFFFF
>>>>> +
>>>>> +/*
>>>>> + * Two 32-bit registers for each PWM: REG0, and REG1.
>>>>> + * Base offset for PWM #i is at 8 * #i.
>>>>> + */
>>>>> +#define IPQ_PWM_REG0			0
>>>>> +#define IPQ_PWM_REG0_PWM_DIV		GENMASK(15, 0)
>>>>> +#define IPQ_PWM_REG0_HI_DURATION	GENMASK(31, 16)
>>>>> +
>>>>> +#define IPQ_PWM_REG1			4
>>>>> +#define IPQ_PWM_REG1_PRE_DIV		GENMASK(15, 0)
>>>>> +/*
>>>>> + * Enable bit is set to enable output toggling in pwm device.
>>>>> + * Update bit is set to reflect the changed divider and high duration
>>>>> + * values in register.
>>>>> + */
>>>>> +#define IPQ_PWM_REG1_UPDATE		BIT(30)
>>>>> +#define IPQ_PWM_REG1_ENABLE		BIT(31)
>>>>> +
>>>>> +
>>>>> +struct ipq_pwm_chip {
>>>>> +	struct pwm_chip chip;
>>>>> +	struct clk *clk;
>>>>> +	void __iomem *mem;
>>>>> +};
>>>>> +
>>>>> +static struct ipq_pwm_chip *ipq_pwm_from_chip(struct pwm_chip *chip)
>>>>> +{
>>>>> +	return container_of(chip, struct ipq_pwm_chip, chip);
>>>>> +}
>>>>> +
>>>>> +static unsigned int ipq_pwm_reg_read(struct pwm_device *pwm, unsigned int reg)
>>>>> +{
>>>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
>>>>> +	unsigned int off = 8 * pwm->hwpwm + reg;
>>>>> +
>>>>> +	return readl(ipq_chip->mem + off);
>>>>> +}
>>>>> +
>>>>> +static void ipq_pwm_reg_write(struct pwm_device *pwm, unsigned int reg,
>>>>> +			      unsigned int val)
>>>>> +{
>>>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
>>>>> +	unsigned int off = 8 * pwm->hwpwm + reg;
>>>>> +
>>>>> +	writel(val, ipq_chip->mem + off);
>>>>> +}
>>>>> +
>>>>> +static void config_div_and_duty(struct pwm_device *pwm, unsigned int pre_div,
>>>>> +			unsigned int pwm_div, unsigned long rate, u64 duty_ns,
>>>>> +			bool enable)
>>>>> +{
>>>>> +	unsigned long hi_dur;
>>>>> +	unsigned long val = 0;
>>>>> +
>>>>> +	/*
>>>>> +	 * high duration = pwm duty * (pwm div + 1)
>>>>> +	 * pwm duty = duty_ns / period_ns
>>>>> +	 */
>>>>> +	hi_dur = div64_u64(duty_ns * rate, (pre_div + 1) * NSEC_PER_SEC);
>>>>> +
>>>>> +	val = FIELD_PREP(IPQ_PWM_REG0_HI_DURATION, hi_dur) |
>>>>> +		FIELD_PREP(IPQ_PWM_REG0_PWM_DIV, pwm_div);
>>>>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG0, val);
>>>>> +
>>>>> +	val = FIELD_PREP(IPQ_PWM_REG1_PRE_DIV, pre_div);
>>>>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
>>>>> +
>>>>> +	/* PWM enable toggle needs a separate write to REG1 */
>>>>> +	val |= IPQ_PWM_REG1_UPDATE;
>>>>> +	if (enable)
>>>>> +		val |= IPQ_PWM_REG1_ENABLE;
>>>>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
>>>>> +}
>>>>> +
>>>>> +static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
>>>>> +			 const struct pwm_state *state)
>>>>> +{
>>>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
>>>>> +	unsigned int pre_div, pwm_div, best_pre_div, best_pwm_div;
>>>>> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
>>>>> +	u64 period_ns, duty_ns, period_rate;
>>>>> +	u64 min_diff;
>>>>> +
>>>>> +	if (state->polarity != PWM_POLARITY_NORMAL)
>>>>> +		return -EINVAL;
>>>>> +
>>>>> +	if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC, rate))
>>>>> +		return -ERANGE;
>>>>> +
>>>>> +	period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
>>>>> +	duty_ns = min(state->duty_cycle, period_ns);
>>>>> +
>>>>> +	/*
>>>>> +	 * period_ns is 1G or less. As long as rate is less than 16 GHz,
>>>>> +	 * period_rate does not overflow. Make that explicit.
>>>>> +	 */
>>>>> +	if (rate > 16ULL * GIGA)
>>>>> +		return -EINVAL;
>>>>> +	period_rate = period_ns * rate;
>>>>> +	best_pre_div = IPQ_PWM_MAX_DIV;
>>>>> +	best_pwm_div = IPQ_PWM_MAX_DIV;
>>>>> +	/*
>>>>> +	 * We don't need to consider pre_div values smaller than
>>>>> +	 *
>>>>> +	 *                              period_rate
>>>>> +	 *  pre_div_min := ------------------------------------
>>>>> +	 *                 NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1)
>>>>> +	 *
>>>>> +	 * because pre_div = pre_div_min results in a better
>>>>> +	 * approximation.
>>>>> +	 */
>>>>> +	pre_div = div64_u64(period_rate,
>>>>> +			(u64)NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1));
>>>>> +	min_diff = period_rate;
>>>>> +
>>>>> +	for (; pre_div <= IPQ_PWM_MAX_DIV; pre_div++) {
>>>>> +		u64 remainder;
>>>>> +
>>>>> +		pwm_div = div64_u64_rem(period_rate,
>>>>> +				(u64)NSEC_PER_SEC * (pre_div + 1), &remainder);
>>>>> +		/* pwm_div is unsigned; the check below catches underflow */
>>>>> +		pwm_div--;
>>>>> +
>>>>> +		/*
>>>>> +		 * Swapping values for pre_div and pwm_div produces the same
>>>>> +		 * period length. So we can skip all settings with pre_div >
>>>>> +		 * pwm_div which results in bigger constraints for selecting
>>>>> +		 * the duty_cycle than with the two values swapped.
>>>>> +		 */
>>>>> +		if (pre_div > pwm_div)
>>>>> +			break;
>>>>> +
>>>>> +		/*
>>>>> +		 * Make sure we can do 100% duty cycle where
>>>>> +		 * hi_dur == pwm_div + 1
>>>>> +		 */
>>>>> +		if (pwm_div > IPQ_PWM_MAX_DIV - 1)
>>>>> +			continue;
>>>>> +
>>>>> +		if (remainder < min_diff) {
>>>>> +			best_pre_div = pre_div;
>>>>> +			best_pwm_div = pwm_div;
>>>>> +			min_diff = remainder;
>>>>> +
>>>>> +			if (min_diff == 0) /* bingo */
>>>>> +				break;
>>>>> +		}
>>>>> +	}
>>>>> +
>>>>> +	/* config divider values for the closest possible frequency */
>>>>> +	config_div_and_duty(pwm, best_pre_div, best_pwm_div,
>>>>> +			    rate, duty_ns, state->enabled);
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static void ipq_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
>>>>> +			      struct pwm_state *state)
>>>>> +{
>>>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
>>>>> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
>>>>> +	unsigned int pre_div, pwm_div, hi_dur;
>>>>> +	u64 effective_div, hi_div;
>>>>> +	u32 reg0, reg1;
>>>>> +
>>>>> +	reg0 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG0);
>>>>> +	reg1 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG1);
>>>>> +
>>>>> +	state->polarity = PWM_POLARITY_NORMAL;
>>>>> +	state->enabled = reg1 & IPQ_PWM_REG1_ENABLE;
>>>>> +
>>>>> +	pwm_div = FIELD_GET(IPQ_PWM_REG0_PWM_DIV, reg0);
>>>>> +	hi_dur = FIELD_GET(IPQ_PWM_REG0_HI_DURATION, reg0);
>>>>> +	pre_div = FIELD_GET(IPQ_PWM_REG1_PRE_DIV, reg1);
>>>>> +
>>>>> +	/* No overflow here, both pre_div and pwm_div <= 0xffff */
>>>>> +	effective_div = (u64)(pre_div + 1) * (pwm_div + 1);
>>>>> +	state->period = DIV64_U64_ROUND_UP(effective_div * NSEC_PER_SEC, rate);
>>>>> +
>>>>> +	hi_div = hi_dur * (pre_div + 1);
>>>>> +	state->duty_cycle = DIV64_U64_ROUND_UP(hi_div * NSEC_PER_SEC, rate);
>>>>> +}
>>>>> +
>>>>> +static const struct pwm_ops ipq_pwm_ops = {
>>>>> +	.apply = ipq_pwm_apply,
>>>>> +	.get_state = ipq_pwm_get_state,
>>>>> +	.owner = THIS_MODULE,
>>>>> +};
>>>>> +
>>>>> +static int ipq_pwm_probe(struct platform_device *pdev)
>>>>> +{
>>>>> +	struct ipq_pwm_chip *pwm;
>>>>> +	struct device *dev = &pdev->dev;
>>>>> +	int ret;
>>>>> +
>>>>> +	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
>>>>> +	if (!pwm)
>>>>> +		return -ENOMEM;
>>>>> +
>>>>> +	platform_set_drvdata(pdev, pwm);
>>>>> +
>>>>> +	pwm->mem = devm_platform_ioremap_resource(pdev, 0);
>>>>> +	if (IS_ERR(pwm->mem))
>>>>> +		return dev_err_probe(dev, PTR_ERR(pwm->mem),
>>>>> +				"regs map failed");
>>>>> +
>>>>> +	pwm->clk = devm_clk_get(dev, NULL);
>>>>> +	if (IS_ERR(pwm->clk))
>>>>> +		return dev_err_probe(dev, PTR_ERR(pwm->clk),
>>>>> +				"failed to get clock");
>>>>> +
>>>>> +	ret = clk_prepare_enable(pwm->clk);
>>>>> +	if (ret)
>>>>> +		return dev_err_probe(dev, ret, "clock enable failed");
>>>>> +
>>>>> +	pwm->chip.dev = dev;
>>>>> +	pwm->chip.ops = &ipq_pwm_ops;
>>>>> +	pwm->chip.npwm = 4;
>>>>> +
>>>>> +	ret = pwmchip_add(&pwm->chip);
>>>>> +	if (ret < 0) {
>>>>> +		dev_err_probe(dev, ret, "pwmchip_add() failed\n");
>>>>> +		clk_disable_unprepare(pwm->clk);
>>>>> +	}
>>>>> +
>>>>> +	return ret;
>>>>> +}
>>>>> +
>>>>> +static int ipq_pwm_remove(struct platform_device *pdev)
>>>>> +{
>>>>> +	struct ipq_pwm_chip *pwm = platform_get_drvdata(pdev);
>>>>> +
>>>>> +	pwmchip_remove(&pwm->chip);
>>>>> +	clk_disable_unprepare(pwm->clk);
>>>>> +
>>>>> +	return 0;
>>>>> +}
>>>>> +
>>>>> +static const struct of_device_id pwm_ipq_dt_match[] = {
>>>>> +	{ .compatible = "qcom,ipq6018-pwm", },
>>>>> +	{}
>>>>> +};
>>>>> +MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match);
>>>>> +
>>>>> +static struct platform_driver ipq_pwm_driver = {
>>>>> +	.driver = {
>>>>> +		.name = "ipq-pwm",
>>>>> +		.of_match_table = pwm_ipq_dt_match,
>>>>> +	},
>>>>> +	.probe = ipq_pwm_probe,
>>>>> +	.remove = ipq_pwm_remove,
>>>>> +};
>>>>> +
>>>>> +module_platform_driver(ipq_pwm_driver);
>>>>> +
>>>>> +MODULE_LICENSE("Dual BSD/GPL");
>>>
> 
> 

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
  2023-09-22  6:00       ` Devi Priya
@ 2023-09-22  8:35         ` Baruch Siach
  -1 siblings, 0 replies; 34+ messages in thread
From: Baruch Siach @ 2023-09-22  8:35 UTC (permalink / raw)
  To: Devi Priya
  Cc: Thierry Reding, Uwe Kleine-König, Andy Gross,
	Bjorn Andersson, Rob Herring, Robert Marko, Kathiravan T,
	linux-pwm, devicetree, linux-arm-msm, linux-arm-kernel

Hi Devi,

On Fri, Sep 22 2023, Devi Priya wrote:
> On 9/15/2023 12:06 PM, Baruch Siach wrote:
>> Hi Devi,
>> [ Dropped/updated codeaurora.org addresses ]
>> On Fri, Sep 15 2023, Devi Priya wrote:
>>> On 2/7/2022 3:00 PM, Baruch Siach wrote:
>>>> From: Baruch Siach <baruch.siach@siklu.com>
>>>> Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
>>>> driver from downstream Codeaurora kernel tree. Removed support for older
>>>> (V1) variants because I have no access to that hardware.
>>>> Tested on IPQ6010 based hardware.
>>>> Signed-off-by: Baruch Siach <baruch.siach@siklu.com>
>>>> ---
>>>> v11:
>>>
>>> Just curious to know if you have plans to post the next revision!
>> I have been waiting for comments from pwm maintainers before sending the
>> next revision.
>> Unfortunately since then I lost access to the hardware, so I can't test
>> suggested implementation changes.  The only pending issue in v11 is the
>> trivial change that Nathan Chancellor suggested, which should be safe.
>> If you like to take it from here you are welcome.
>
> Could you pls point me to the pending comment that has to be addressed.
> Was not able to find any open comments in the latest series.
> https://lore.kernel.org/linux-arm-msm/8a331c88-c7d4-3a14-0ec3-fd616ea24a99@quicinc.com/

See here:

  https://lore.kernel.org/all/YgK63cI177ZeF5v1@dev-arch.archlinux-ax161/

baruch

>>>> Address comment from Uwe Kleine-König:
>>>>     Drop redundant registers field comments
>>>>     Fix period limit check in .apply
>>>>     Clarify the comment explaining skip of pre_div > pwm_div values
>>>>     Add explicit check for clock rate within limit
>>>>     Add comment explaining the selection of initial pre_div
>>>>     Use pwm_div division with remainder instead of separate diff calculation
>>>>     Round up duty_cycle calculation in .get_state
>>>> v10:
>>>>     Restore round up in pwm_div calculation; otherwise diff is always <=
>>>>     0, so only bingo match works
>>>>     Don't overwrite min_diff on every loop iteration
>>>> v9:
>>>> Address comment from Uwe Kleine-König:
>>>>     Use period_ns*rate in dividers calculation for better accuracy
>>>>     Round down pre_div and pwm_div
>>>>     Add a comment explaining why pwm_div can't underflow
>>>>     Add a comment explaining why pre_div > pwm_div end the search loop
>>>>     Drop 'CFG_' from register macros
>>>>     Rename to_ipq_pwm_chip() to ipq_pwm_from_chip()
>>>>     Change bare 'unsigned' to 'unsigned int'
>>>>     Clarify the comment on separate REG1 write for enable/disable
>>>>     Round up the period value in .get_state
>>>>     Use direct readl/writel so no need to check for regmap errors
>>>> v7:
>>>>     Change 'offset' to 'reg' for the tcsr offset (Rob)
>>>>     Drop clock name; there is only one clock (Bjorn)
>>>>     Simplify probe failure code path (Bjorn)
>>>> v6:
>>>> Address Uwe Kleine-König review comments:
>>>>     Drop IPQ_PWM_MAX_DEVICES
>>>>     Rely on assigned-clock-rates; drop IPQ_PWM_CLK_SRC_FREQ
>>>>     Simplify register offset calculation
>>>>     Calculate duty cycle more precisely
>>>>     Refuse to set inverted polarity
>>>>     Drop redundant IPQ_PWM_REG1_ENABLE bit clear
>>>>     Remove x1000 factor in pwm_div calculation, use rate directly, and round
>>>> up
>>>>     Choose initial pre_div such that pwm_div < IPQ_PWM_MAX_DIV
>>>>     Ensure pre_div <= pwm_div
>>>>     Rename close_ to best_
>>>>     Explain in comment why effective_div doesn't overflow
>>>>     Limit pwm_div to IPQ_PWM_MAX_DIV - 1 to allow 100% duty cycle
>>>>     Disable clock only after pwmchip_remove()
>>>>     const pwm_ops
>>>> Other changes:
>>>>     Add missing linux/bitfield.h header include (kernel test robot)
>>>>     Adjust code for PWM device node under TCSR (Rob Herring)
>>>> v5:
>>>> Use &tcsr_q6 syscon to access registers (Bjorn Andersson)
>>>> Address Uwe Kleine-König review comments:
>>>>     Implement .get_state()
>>>>     Add IPQ_PWM_ prefix to local macros
>>>>     Use GENMASK/BIT/FIELD_PREP for register fields access
>>>>     Make type of config_div_and_duty() parameters consistent
>>>>     Derive IPQ_PWM_MIN_PERIOD_NS from IPQ_PWM_CLK_SRC_FREQ
>>>>     Integrate enable/disable into config_div_and_duty() to save register
>>>> read,
>>>>     and reduce frequency glitch on update
>>>>     Use min() instead of min_t()
>>>>     Fix comment format
>>>>     Use dev_err_probe() to indicate probe step failure
>>>>     Add missing clk_disable_unprepare() in .remove
>>>>     Don't set .owner
>>>> v4:
>>>>     Use div64_u64() to fix link for 32-bit targets ((kernel test robot
>>>>     <lkp@intel.com>, Uwe Kleine-König)
>>>> v3:
>>>>     s/qcom,pwm-ipq6018/qcom,ipq6018-pwm/ (Rob Herring)
>>>>     Fix integer overflow on 32-bit targets (kernel test robot
>>>> <lkp@intel.com>)
>>>> v2:
>>>> Address Uwe Kleine-König review comments:
>>>>     Fix period calculation when out of range
>>>>     Don't set period larger than requested
>>>>     Remove PWM disable on configuration change
>>>>     Implement .apply instead of non-atomic .config/.enable/.disable
>>>>     Don't modify PWM on .request/.free
>>>>     Check pwm_div underflow
>>>>     Fix various code and comment formatting issues
>>>> Other changes:
>>>>     Use u64 divisor safe division
>>>>     Remove now empty .request/.free
>>>> ---
>>>>    drivers/pwm/Kconfig   |  12 ++
>>>>    drivers/pwm/Makefile  |   1 +
>>>>    drivers/pwm/pwm-ipq.c | 281 ++++++++++++++++++++++++++++++++++++++++++
>>>>    3 files changed, 294 insertions(+)
>>>>    create mode 100644 drivers/pwm/pwm-ipq.c
>>>> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
>>>> index 21e3b05a5153..e39718137ecd 100644
>>>> --- a/drivers/pwm/Kconfig
>>>> +++ b/drivers/pwm/Kconfig
>>>> @@ -260,6 +260,18 @@ config PWM_INTEL_LGM
>>>>    	  To compile this driver as a module, choose M here: the module
>>>>    	  will be called pwm-intel-lgm.
>>>>    +config PWM_IPQ
>>>> +	tristate "IPQ PWM support"
>>>> +	depends on ARCH_QCOM || COMPILE_TEST
>>>> +	depends on HAVE_CLK && HAS_IOMEM
>>>> +	help
>>>> +	  Generic PWM framework driver for IPQ PWM block which supports
>>>> +	  4 pwm channels. Each of the these channels can be configured
>>>> +	  independent of each other.
>>>> +
>>>> +	  To compile this driver as a module, choose M here: the module
>>>> +	  will be called pwm-ipq.
>>>> +
>>>>    config PWM_IQS620A
>>>>    	tristate "Azoteq IQS620A PWM support"
>>>>    	depends on MFD_IQS62X || COMPILE_TEST
>>>> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
>>>> index 708840b7fba8..7402feae4b36 100644
>>>> --- a/drivers/pwm/Makefile
>>>> +++ b/drivers/pwm/Makefile
>>>> @@ -22,6 +22,7 @@ obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
>>>>    obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
>>>>    obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
>>>>    obj-$(CONFIG_PWM_INTEL_LGM)	+= pwm-intel-lgm.o
>>>> +obj-$(CONFIG_PWM_IPQ)		+= pwm-ipq.o
>>>>    obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
>>>>    obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>>>>    obj-$(CONFIG_PWM_KEEMBAY)	+= pwm-keembay.o
>>>> diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
>>>> new file mode 100644
>>>> index 000000000000..994027290bcb
>>>> --- /dev/null
>>>> +++ b/drivers/pwm/pwm-ipq.c
>>>> @@ -0,0 +1,281 @@
>>>> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
>>>> +/*
>>>> + * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
>>>> + */
>>>> +
>>>> +#include <linux/module.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/pwm.h>
>>>> +#include <linux/clk.h>
>>>> +#include <linux/io.h>
>>>> +#include <linux/of.h>
>>>> +#include <linux/math64.h>
>>>> +#include <linux/of_device.h>
>>>> +#include <linux/bitfield.h>
>>>> +#include <linux/units.h>
>>>> +
>>>> +/* The frequency range supported is 1 Hz to clock rate */
>>>> +#define IPQ_PWM_MAX_PERIOD_NS	((u64)NSEC_PER_SEC)
>>>> +
>>>> +/*
>>>> + * The max value specified for each field is based on the number of bits
>>>> + * in the pwm control register for that field
>>>> + */
>>>> +#define IPQ_PWM_MAX_DIV		0xFFFF
>>>> +
>>>> +/*
>>>> + * Two 32-bit registers for each PWM: REG0, and REG1.
>>>> + * Base offset for PWM #i is at 8 * #i.
>>>> + */
>>>> +#define IPQ_PWM_REG0			0
>>>> +#define IPQ_PWM_REG0_PWM_DIV		GENMASK(15, 0)
>>>> +#define IPQ_PWM_REG0_HI_DURATION	GENMASK(31, 16)
>>>> +
>>>> +#define IPQ_PWM_REG1			4
>>>> +#define IPQ_PWM_REG1_PRE_DIV		GENMASK(15, 0)
>>>> +/*
>>>> + * Enable bit is set to enable output toggling in pwm device.
>>>> + * Update bit is set to reflect the changed divider and high duration
>>>> + * values in register.
>>>> + */
>>>> +#define IPQ_PWM_REG1_UPDATE		BIT(30)
>>>> +#define IPQ_PWM_REG1_ENABLE		BIT(31)
>>>> +
>>>> +
>>>> +struct ipq_pwm_chip {
>>>> +	struct pwm_chip chip;
>>>> +	struct clk *clk;
>>>> +	void __iomem *mem;
>>>> +};
>>>> +
>>>> +static struct ipq_pwm_chip *ipq_pwm_from_chip(struct pwm_chip *chip)
>>>> +{
>>>> +	return container_of(chip, struct ipq_pwm_chip, chip);
>>>> +}
>>>> +
>>>> +static unsigned int ipq_pwm_reg_read(struct pwm_device *pwm, unsigned int reg)
>>>> +{
>>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
>>>> +	unsigned int off = 8 * pwm->hwpwm + reg;
>>>> +
>>>> +	return readl(ipq_chip->mem + off);
>>>> +}
>>>> +
>>>> +static void ipq_pwm_reg_write(struct pwm_device *pwm, unsigned int reg,
>>>> +			      unsigned int val)
>>>> +{
>>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
>>>> +	unsigned int off = 8 * pwm->hwpwm + reg;
>>>> +
>>>> +	writel(val, ipq_chip->mem + off);
>>>> +}
>>>> +
>>>> +static void config_div_and_duty(struct pwm_device *pwm, unsigned int pre_div,
>>>> +			unsigned int pwm_div, unsigned long rate, u64 duty_ns,
>>>> +			bool enable)
>>>> +{
>>>> +	unsigned long hi_dur;
>>>> +	unsigned long val = 0;
>>>> +
>>>> +	/*
>>>> +	 * high duration = pwm duty * (pwm div + 1)
>>>> +	 * pwm duty = duty_ns / period_ns
>>>> +	 */
>>>> +	hi_dur = div64_u64(duty_ns * rate, (pre_div + 1) * NSEC_PER_SEC);
>>>> +
>>>> +	val = FIELD_PREP(IPQ_PWM_REG0_HI_DURATION, hi_dur) |
>>>> +		FIELD_PREP(IPQ_PWM_REG0_PWM_DIV, pwm_div);
>>>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG0, val);
>>>> +
>>>> +	val = FIELD_PREP(IPQ_PWM_REG1_PRE_DIV, pre_div);
>>>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
>>>> +
>>>> +	/* PWM enable toggle needs a separate write to REG1 */
>>>> +	val |= IPQ_PWM_REG1_UPDATE;
>>>> +	if (enable)
>>>> +		val |= IPQ_PWM_REG1_ENABLE;
>>>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
>>>> +}
>>>> +
>>>> +static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
>>>> +			 const struct pwm_state *state)
>>>> +{
>>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
>>>> +	unsigned int pre_div, pwm_div, best_pre_div, best_pwm_div;
>>>> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
>>>> +	u64 period_ns, duty_ns, period_rate;
>>>> +	u64 min_diff;
>>>> +
>>>> +	if (state->polarity != PWM_POLARITY_NORMAL)
>>>> +		return -EINVAL;
>>>> +
>>>> +	if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC, rate))
>>>> +		return -ERANGE;
>>>> +
>>>> +	period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
>>>> +	duty_ns = min(state->duty_cycle, period_ns);
>>>> +
>>>> +	/*
>>>> +	 * period_ns is 1G or less. As long as rate is less than 16 GHz,
>>>> +	 * period_rate does not overflow. Make that explicit.
>>>> +	 */
>>>> +	if (rate > 16ULL * GIGA)
>>>> +		return -EINVAL;
>>>> +	period_rate = period_ns * rate;
>>>> +	best_pre_div = IPQ_PWM_MAX_DIV;
>>>> +	best_pwm_div = IPQ_PWM_MAX_DIV;
>>>> +	/*
>>>> +	 * We don't need to consider pre_div values smaller than
>>>> +	 *
>>>> +	 *                              period_rate
>>>> +	 *  pre_div_min := ------------------------------------
>>>> +	 *                 NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1)
>>>> +	 *
>>>> +	 * because pre_div = pre_div_min results in a better
>>>> +	 * approximation.
>>>> +	 */
>>>> +	pre_div = div64_u64(period_rate,
>>>> +			(u64)NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1));
>>>> +	min_diff = period_rate;
>>>> +
>>>> +	for (; pre_div <= IPQ_PWM_MAX_DIV; pre_div++) {
>>>> +		u64 remainder;
>>>> +
>>>> +		pwm_div = div64_u64_rem(period_rate,
>>>> +				(u64)NSEC_PER_SEC * (pre_div + 1), &remainder);
>>>> +		/* pwm_div is unsigned; the check below catches underflow */
>>>> +		pwm_div--;
>>>> +
>>>> +		/*
>>>> +		 * Swapping values for pre_div and pwm_div produces the same
>>>> +		 * period length. So we can skip all settings with pre_div >
>>>> +		 * pwm_div which results in bigger constraints for selecting
>>>> +		 * the duty_cycle than with the two values swapped.
>>>> +		 */
>>>> +		if (pre_div > pwm_div)
>>>> +			break;
>>>> +
>>>> +		/*
>>>> +		 * Make sure we can do 100% duty cycle where
>>>> +		 * hi_dur == pwm_div + 1
>>>> +		 */
>>>> +		if (pwm_div > IPQ_PWM_MAX_DIV - 1)
>>>> +			continue;
>>>> +
>>>> +		if (remainder < min_diff) {
>>>> +			best_pre_div = pre_div;
>>>> +			best_pwm_div = pwm_div;
>>>> +			min_diff = remainder;
>>>> +
>>>> +			if (min_diff == 0) /* bingo */
>>>> +				break;
>>>> +		}
>>>> +	}
>>>> +
>>>> +	/* config divider values for the closest possible frequency */
>>>> +	config_div_and_duty(pwm, best_pre_div, best_pwm_div,
>>>> +			    rate, duty_ns, state->enabled);
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static void ipq_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
>>>> +			      struct pwm_state *state)
>>>> +{
>>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
>>>> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
>>>> +	unsigned int pre_div, pwm_div, hi_dur;
>>>> +	u64 effective_div, hi_div;
>>>> +	u32 reg0, reg1;
>>>> +
>>>> +	reg0 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG0);
>>>> +	reg1 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG1);
>>>> +
>>>> +	state->polarity = PWM_POLARITY_NORMAL;
>>>> +	state->enabled = reg1 & IPQ_PWM_REG1_ENABLE;
>>>> +
>>>> +	pwm_div = FIELD_GET(IPQ_PWM_REG0_PWM_DIV, reg0);
>>>> +	hi_dur = FIELD_GET(IPQ_PWM_REG0_HI_DURATION, reg0);
>>>> +	pre_div = FIELD_GET(IPQ_PWM_REG1_PRE_DIV, reg1);
>>>> +
>>>> +	/* No overflow here, both pre_div and pwm_div <= 0xffff */
>>>> +	effective_div = (u64)(pre_div + 1) * (pwm_div + 1);
>>>> +	state->period = DIV64_U64_ROUND_UP(effective_div * NSEC_PER_SEC, rate);
>>>> +
>>>> +	hi_div = hi_dur * (pre_div + 1);
>>>> +	state->duty_cycle = DIV64_U64_ROUND_UP(hi_div * NSEC_PER_SEC, rate);
>>>> +}
>>>> +
>>>> +static const struct pwm_ops ipq_pwm_ops = {
>>>> +	.apply = ipq_pwm_apply,
>>>> +	.get_state = ipq_pwm_get_state,
>>>> +	.owner = THIS_MODULE,
>>>> +};
>>>> +
>>>> +static int ipq_pwm_probe(struct platform_device *pdev)
>>>> +{
>>>> +	struct ipq_pwm_chip *pwm;
>>>> +	struct device *dev = &pdev->dev;
>>>> +	int ret;
>>>> +
>>>> +	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
>>>> +	if (!pwm)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	platform_set_drvdata(pdev, pwm);
>>>> +
>>>> +	pwm->mem = devm_platform_ioremap_resource(pdev, 0);
>>>> +	if (IS_ERR(pwm->mem))
>>>> +		return dev_err_probe(dev, PTR_ERR(pwm->mem),
>>>> +				"regs map failed");
>>>> +
>>>> +	pwm->clk = devm_clk_get(dev, NULL);
>>>> +	if (IS_ERR(pwm->clk))
>>>> +		return dev_err_probe(dev, PTR_ERR(pwm->clk),
>>>> +				"failed to get clock");
>>>> +
>>>> +	ret = clk_prepare_enable(pwm->clk);
>>>> +	if (ret)
>>>> +		return dev_err_probe(dev, ret, "clock enable failed");
>>>> +
>>>> +	pwm->chip.dev = dev;
>>>> +	pwm->chip.ops = &ipq_pwm_ops;
>>>> +	pwm->chip.npwm = 4;
>>>> +
>>>> +	ret = pwmchip_add(&pwm->chip);
>>>> +	if (ret < 0) {
>>>> +		dev_err_probe(dev, ret, "pwmchip_add() failed\n");
>>>> +		clk_disable_unprepare(pwm->clk);
>>>> +	}
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static int ipq_pwm_remove(struct platform_device *pdev)
>>>> +{
>>>> +	struct ipq_pwm_chip *pwm = platform_get_drvdata(pdev);
>>>> +
>>>> +	pwmchip_remove(&pwm->chip);
>>>> +	clk_disable_unprepare(pwm->clk);
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static const struct of_device_id pwm_ipq_dt_match[] = {
>>>> +	{ .compatible = "qcom,ipq6018-pwm", },
>>>> +	{}
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match);
>>>> +
>>>> +static struct platform_driver ipq_pwm_driver = {
>>>> +	.driver = {
>>>> +		.name = "ipq-pwm",
>>>> +		.of_match_table = pwm_ipq_dt_match,
>>>> +	},
>>>> +	.probe = ipq_pwm_probe,
>>>> +	.remove = ipq_pwm_remove,
>>>> +};
>>>> +
>>>> +module_platform_driver(ipq_pwm_driver);
>>>> +
>>>> +MODULE_LICENSE("Dual BSD/GPL");
>> 


-- 
                                                     ~. .~   Tk Open Systems
=}------------------------------------------------ooO--U--Ooo------------{=
   - baruch@tkos.co.il - tel: +972.52.368.4656, http://www.tkos.co.il -

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
@ 2023-09-22  8:35         ` Baruch Siach
  0 siblings, 0 replies; 34+ messages in thread
From: Baruch Siach @ 2023-09-22  8:35 UTC (permalink / raw)
  To: Devi Priya
  Cc: Thierry Reding, Uwe Kleine-König, Andy Gross,
	Bjorn Andersson, Rob Herring, Robert Marko, Kathiravan T,
	linux-pwm, devicetree, linux-arm-msm, linux-arm-kernel

Hi Devi,

On Fri, Sep 22 2023, Devi Priya wrote:
> On 9/15/2023 12:06 PM, Baruch Siach wrote:
>> Hi Devi,
>> [ Dropped/updated codeaurora.org addresses ]
>> On Fri, Sep 15 2023, Devi Priya wrote:
>>> On 2/7/2022 3:00 PM, Baruch Siach wrote:
>>>> From: Baruch Siach <baruch.siach@siklu.com>
>>>> Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
>>>> driver from downstream Codeaurora kernel tree. Removed support for older
>>>> (V1) variants because I have no access to that hardware.
>>>> Tested on IPQ6010 based hardware.
>>>> Signed-off-by: Baruch Siach <baruch.siach@siklu.com>
>>>> ---
>>>> v11:
>>>
>>> Just curious to know if you have plans to post the next revision!
>> I have been waiting for comments from pwm maintainers before sending the
>> next revision.
>> Unfortunately since then I lost access to the hardware, so I can't test
>> suggested implementation changes.  The only pending issue in v11 is the
>> trivial change that Nathan Chancellor suggested, which should be safe.
>> If you like to take it from here you are welcome.
>
> Could you pls point me to the pending comment that has to be addressed.
> Was not able to find any open comments in the latest series.
> https://lore.kernel.org/linux-arm-msm/8a331c88-c7d4-3a14-0ec3-fd616ea24a99@quicinc.com/

See here:

  https://lore.kernel.org/all/YgK63cI177ZeF5v1@dev-arch.archlinux-ax161/

baruch

>>>> Address comment from Uwe Kleine-König:
>>>>     Drop redundant registers field comments
>>>>     Fix period limit check in .apply
>>>>     Clarify the comment explaining skip of pre_div > pwm_div values
>>>>     Add explicit check for clock rate within limit
>>>>     Add comment explaining the selection of initial pre_div
>>>>     Use pwm_div division with remainder instead of separate diff calculation
>>>>     Round up duty_cycle calculation in .get_state
>>>> v10:
>>>>     Restore round up in pwm_div calculation; otherwise diff is always <=
>>>>     0, so only bingo match works
>>>>     Don't overwrite min_diff on every loop iteration
>>>> v9:
>>>> Address comment from Uwe Kleine-König:
>>>>     Use period_ns*rate in dividers calculation for better accuracy
>>>>     Round down pre_div and pwm_div
>>>>     Add a comment explaining why pwm_div can't underflow
>>>>     Add a comment explaining why pre_div > pwm_div end the search loop
>>>>     Drop 'CFG_' from register macros
>>>>     Rename to_ipq_pwm_chip() to ipq_pwm_from_chip()
>>>>     Change bare 'unsigned' to 'unsigned int'
>>>>     Clarify the comment on separate REG1 write for enable/disable
>>>>     Round up the period value in .get_state
>>>>     Use direct readl/writel so no need to check for regmap errors
>>>> v7:
>>>>     Change 'offset' to 'reg' for the tcsr offset (Rob)
>>>>     Drop clock name; there is only one clock (Bjorn)
>>>>     Simplify probe failure code path (Bjorn)
>>>> v6:
>>>> Address Uwe Kleine-König review comments:
>>>>     Drop IPQ_PWM_MAX_DEVICES
>>>>     Rely on assigned-clock-rates; drop IPQ_PWM_CLK_SRC_FREQ
>>>>     Simplify register offset calculation
>>>>     Calculate duty cycle more precisely
>>>>     Refuse to set inverted polarity
>>>>     Drop redundant IPQ_PWM_REG1_ENABLE bit clear
>>>>     Remove x1000 factor in pwm_div calculation, use rate directly, and round
>>>> up
>>>>     Choose initial pre_div such that pwm_div < IPQ_PWM_MAX_DIV
>>>>     Ensure pre_div <= pwm_div
>>>>     Rename close_ to best_
>>>>     Explain in comment why effective_div doesn't overflow
>>>>     Limit pwm_div to IPQ_PWM_MAX_DIV - 1 to allow 100% duty cycle
>>>>     Disable clock only after pwmchip_remove()
>>>>     const pwm_ops
>>>> Other changes:
>>>>     Add missing linux/bitfield.h header include (kernel test robot)
>>>>     Adjust code for PWM device node under TCSR (Rob Herring)
>>>> v5:
>>>> Use &tcsr_q6 syscon to access registers (Bjorn Andersson)
>>>> Address Uwe Kleine-König review comments:
>>>>     Implement .get_state()
>>>>     Add IPQ_PWM_ prefix to local macros
>>>>     Use GENMASK/BIT/FIELD_PREP for register fields access
>>>>     Make type of config_div_and_duty() parameters consistent
>>>>     Derive IPQ_PWM_MIN_PERIOD_NS from IPQ_PWM_CLK_SRC_FREQ
>>>>     Integrate enable/disable into config_div_and_duty() to save register
>>>> read,
>>>>     and reduce frequency glitch on update
>>>>     Use min() instead of min_t()
>>>>     Fix comment format
>>>>     Use dev_err_probe() to indicate probe step failure
>>>>     Add missing clk_disable_unprepare() in .remove
>>>>     Don't set .owner
>>>> v4:
>>>>     Use div64_u64() to fix link for 32-bit targets ((kernel test robot
>>>>     <lkp@intel.com>, Uwe Kleine-König)
>>>> v3:
>>>>     s/qcom,pwm-ipq6018/qcom,ipq6018-pwm/ (Rob Herring)
>>>>     Fix integer overflow on 32-bit targets (kernel test robot
>>>> <lkp@intel.com>)
>>>> v2:
>>>> Address Uwe Kleine-König review comments:
>>>>     Fix period calculation when out of range
>>>>     Don't set period larger than requested
>>>>     Remove PWM disable on configuration change
>>>>     Implement .apply instead of non-atomic .config/.enable/.disable
>>>>     Don't modify PWM on .request/.free
>>>>     Check pwm_div underflow
>>>>     Fix various code and comment formatting issues
>>>> Other changes:
>>>>     Use u64 divisor safe division
>>>>     Remove now empty .request/.free
>>>> ---
>>>>    drivers/pwm/Kconfig   |  12 ++
>>>>    drivers/pwm/Makefile  |   1 +
>>>>    drivers/pwm/pwm-ipq.c | 281 ++++++++++++++++++++++++++++++++++++++++++
>>>>    3 files changed, 294 insertions(+)
>>>>    create mode 100644 drivers/pwm/pwm-ipq.c
>>>> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
>>>> index 21e3b05a5153..e39718137ecd 100644
>>>> --- a/drivers/pwm/Kconfig
>>>> +++ b/drivers/pwm/Kconfig
>>>> @@ -260,6 +260,18 @@ config PWM_INTEL_LGM
>>>>    	  To compile this driver as a module, choose M here: the module
>>>>    	  will be called pwm-intel-lgm.
>>>>    +config PWM_IPQ
>>>> +	tristate "IPQ PWM support"
>>>> +	depends on ARCH_QCOM || COMPILE_TEST
>>>> +	depends on HAVE_CLK && HAS_IOMEM
>>>> +	help
>>>> +	  Generic PWM framework driver for IPQ PWM block which supports
>>>> +	  4 pwm channels. Each of the these channels can be configured
>>>> +	  independent of each other.
>>>> +
>>>> +	  To compile this driver as a module, choose M here: the module
>>>> +	  will be called pwm-ipq.
>>>> +
>>>>    config PWM_IQS620A
>>>>    	tristate "Azoteq IQS620A PWM support"
>>>>    	depends on MFD_IQS62X || COMPILE_TEST
>>>> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
>>>> index 708840b7fba8..7402feae4b36 100644
>>>> --- a/drivers/pwm/Makefile
>>>> +++ b/drivers/pwm/Makefile
>>>> @@ -22,6 +22,7 @@ obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
>>>>    obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
>>>>    obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
>>>>    obj-$(CONFIG_PWM_INTEL_LGM)	+= pwm-intel-lgm.o
>>>> +obj-$(CONFIG_PWM_IPQ)		+= pwm-ipq.o
>>>>    obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
>>>>    obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>>>>    obj-$(CONFIG_PWM_KEEMBAY)	+= pwm-keembay.o
>>>> diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
>>>> new file mode 100644
>>>> index 000000000000..994027290bcb
>>>> --- /dev/null
>>>> +++ b/drivers/pwm/pwm-ipq.c
>>>> @@ -0,0 +1,281 @@
>>>> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
>>>> +/*
>>>> + * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
>>>> + */
>>>> +
>>>> +#include <linux/module.h>
>>>> +#include <linux/platform_device.h>
>>>> +#include <linux/pwm.h>
>>>> +#include <linux/clk.h>
>>>> +#include <linux/io.h>
>>>> +#include <linux/of.h>
>>>> +#include <linux/math64.h>
>>>> +#include <linux/of_device.h>
>>>> +#include <linux/bitfield.h>
>>>> +#include <linux/units.h>
>>>> +
>>>> +/* The frequency range supported is 1 Hz to clock rate */
>>>> +#define IPQ_PWM_MAX_PERIOD_NS	((u64)NSEC_PER_SEC)
>>>> +
>>>> +/*
>>>> + * The max value specified for each field is based on the number of bits
>>>> + * in the pwm control register for that field
>>>> + */
>>>> +#define IPQ_PWM_MAX_DIV		0xFFFF
>>>> +
>>>> +/*
>>>> + * Two 32-bit registers for each PWM: REG0, and REG1.
>>>> + * Base offset for PWM #i is at 8 * #i.
>>>> + */
>>>> +#define IPQ_PWM_REG0			0
>>>> +#define IPQ_PWM_REG0_PWM_DIV		GENMASK(15, 0)
>>>> +#define IPQ_PWM_REG0_HI_DURATION	GENMASK(31, 16)
>>>> +
>>>> +#define IPQ_PWM_REG1			4
>>>> +#define IPQ_PWM_REG1_PRE_DIV		GENMASK(15, 0)
>>>> +/*
>>>> + * Enable bit is set to enable output toggling in pwm device.
>>>> + * Update bit is set to reflect the changed divider and high duration
>>>> + * values in register.
>>>> + */
>>>> +#define IPQ_PWM_REG1_UPDATE		BIT(30)
>>>> +#define IPQ_PWM_REG1_ENABLE		BIT(31)
>>>> +
>>>> +
>>>> +struct ipq_pwm_chip {
>>>> +	struct pwm_chip chip;
>>>> +	struct clk *clk;
>>>> +	void __iomem *mem;
>>>> +};
>>>> +
>>>> +static struct ipq_pwm_chip *ipq_pwm_from_chip(struct pwm_chip *chip)
>>>> +{
>>>> +	return container_of(chip, struct ipq_pwm_chip, chip);
>>>> +}
>>>> +
>>>> +static unsigned int ipq_pwm_reg_read(struct pwm_device *pwm, unsigned int reg)
>>>> +{
>>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
>>>> +	unsigned int off = 8 * pwm->hwpwm + reg;
>>>> +
>>>> +	return readl(ipq_chip->mem + off);
>>>> +}
>>>> +
>>>> +static void ipq_pwm_reg_write(struct pwm_device *pwm, unsigned int reg,
>>>> +			      unsigned int val)
>>>> +{
>>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
>>>> +	unsigned int off = 8 * pwm->hwpwm + reg;
>>>> +
>>>> +	writel(val, ipq_chip->mem + off);
>>>> +}
>>>> +
>>>> +static void config_div_and_duty(struct pwm_device *pwm, unsigned int pre_div,
>>>> +			unsigned int pwm_div, unsigned long rate, u64 duty_ns,
>>>> +			bool enable)
>>>> +{
>>>> +	unsigned long hi_dur;
>>>> +	unsigned long val = 0;
>>>> +
>>>> +	/*
>>>> +	 * high duration = pwm duty * (pwm div + 1)
>>>> +	 * pwm duty = duty_ns / period_ns
>>>> +	 */
>>>> +	hi_dur = div64_u64(duty_ns * rate, (pre_div + 1) * NSEC_PER_SEC);
>>>> +
>>>> +	val = FIELD_PREP(IPQ_PWM_REG0_HI_DURATION, hi_dur) |
>>>> +		FIELD_PREP(IPQ_PWM_REG0_PWM_DIV, pwm_div);
>>>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG0, val);
>>>> +
>>>> +	val = FIELD_PREP(IPQ_PWM_REG1_PRE_DIV, pre_div);
>>>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
>>>> +
>>>> +	/* PWM enable toggle needs a separate write to REG1 */
>>>> +	val |= IPQ_PWM_REG1_UPDATE;
>>>> +	if (enable)
>>>> +		val |= IPQ_PWM_REG1_ENABLE;
>>>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
>>>> +}
>>>> +
>>>> +static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
>>>> +			 const struct pwm_state *state)
>>>> +{
>>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
>>>> +	unsigned int pre_div, pwm_div, best_pre_div, best_pwm_div;
>>>> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
>>>> +	u64 period_ns, duty_ns, period_rate;
>>>> +	u64 min_diff;
>>>> +
>>>> +	if (state->polarity != PWM_POLARITY_NORMAL)
>>>> +		return -EINVAL;
>>>> +
>>>> +	if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC, rate))
>>>> +		return -ERANGE;
>>>> +
>>>> +	period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
>>>> +	duty_ns = min(state->duty_cycle, period_ns);
>>>> +
>>>> +	/*
>>>> +	 * period_ns is 1G or less. As long as rate is less than 16 GHz,
>>>> +	 * period_rate does not overflow. Make that explicit.
>>>> +	 */
>>>> +	if (rate > 16ULL * GIGA)
>>>> +		return -EINVAL;
>>>> +	period_rate = period_ns * rate;
>>>> +	best_pre_div = IPQ_PWM_MAX_DIV;
>>>> +	best_pwm_div = IPQ_PWM_MAX_DIV;
>>>> +	/*
>>>> +	 * We don't need to consider pre_div values smaller than
>>>> +	 *
>>>> +	 *                              period_rate
>>>> +	 *  pre_div_min := ------------------------------------
>>>> +	 *                 NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1)
>>>> +	 *
>>>> +	 * because pre_div = pre_div_min results in a better
>>>> +	 * approximation.
>>>> +	 */
>>>> +	pre_div = div64_u64(period_rate,
>>>> +			(u64)NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1));
>>>> +	min_diff = period_rate;
>>>> +
>>>> +	for (; pre_div <= IPQ_PWM_MAX_DIV; pre_div++) {
>>>> +		u64 remainder;
>>>> +
>>>> +		pwm_div = div64_u64_rem(period_rate,
>>>> +				(u64)NSEC_PER_SEC * (pre_div + 1), &remainder);
>>>> +		/* pwm_div is unsigned; the check below catches underflow */
>>>> +		pwm_div--;
>>>> +
>>>> +		/*
>>>> +		 * Swapping values for pre_div and pwm_div produces the same
>>>> +		 * period length. So we can skip all settings with pre_div >
>>>> +		 * pwm_div which results in bigger constraints for selecting
>>>> +		 * the duty_cycle than with the two values swapped.
>>>> +		 */
>>>> +		if (pre_div > pwm_div)
>>>> +			break;
>>>> +
>>>> +		/*
>>>> +		 * Make sure we can do 100% duty cycle where
>>>> +		 * hi_dur == pwm_div + 1
>>>> +		 */
>>>> +		if (pwm_div > IPQ_PWM_MAX_DIV - 1)
>>>> +			continue;
>>>> +
>>>> +		if (remainder < min_diff) {
>>>> +			best_pre_div = pre_div;
>>>> +			best_pwm_div = pwm_div;
>>>> +			min_diff = remainder;
>>>> +
>>>> +			if (min_diff == 0) /* bingo */
>>>> +				break;
>>>> +		}
>>>> +	}
>>>> +
>>>> +	/* config divider values for the closest possible frequency */
>>>> +	config_div_and_duty(pwm, best_pre_div, best_pwm_div,
>>>> +			    rate, duty_ns, state->enabled);
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static void ipq_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
>>>> +			      struct pwm_state *state)
>>>> +{
>>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
>>>> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
>>>> +	unsigned int pre_div, pwm_div, hi_dur;
>>>> +	u64 effective_div, hi_div;
>>>> +	u32 reg0, reg1;
>>>> +
>>>> +	reg0 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG0);
>>>> +	reg1 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG1);
>>>> +
>>>> +	state->polarity = PWM_POLARITY_NORMAL;
>>>> +	state->enabled = reg1 & IPQ_PWM_REG1_ENABLE;
>>>> +
>>>> +	pwm_div = FIELD_GET(IPQ_PWM_REG0_PWM_DIV, reg0);
>>>> +	hi_dur = FIELD_GET(IPQ_PWM_REG0_HI_DURATION, reg0);
>>>> +	pre_div = FIELD_GET(IPQ_PWM_REG1_PRE_DIV, reg1);
>>>> +
>>>> +	/* No overflow here, both pre_div and pwm_div <= 0xffff */
>>>> +	effective_div = (u64)(pre_div + 1) * (pwm_div + 1);
>>>> +	state->period = DIV64_U64_ROUND_UP(effective_div * NSEC_PER_SEC, rate);
>>>> +
>>>> +	hi_div = hi_dur * (pre_div + 1);
>>>> +	state->duty_cycle = DIV64_U64_ROUND_UP(hi_div * NSEC_PER_SEC, rate);
>>>> +}
>>>> +
>>>> +static const struct pwm_ops ipq_pwm_ops = {
>>>> +	.apply = ipq_pwm_apply,
>>>> +	.get_state = ipq_pwm_get_state,
>>>> +	.owner = THIS_MODULE,
>>>> +};
>>>> +
>>>> +static int ipq_pwm_probe(struct platform_device *pdev)
>>>> +{
>>>> +	struct ipq_pwm_chip *pwm;
>>>> +	struct device *dev = &pdev->dev;
>>>> +	int ret;
>>>> +
>>>> +	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
>>>> +	if (!pwm)
>>>> +		return -ENOMEM;
>>>> +
>>>> +	platform_set_drvdata(pdev, pwm);
>>>> +
>>>> +	pwm->mem = devm_platform_ioremap_resource(pdev, 0);
>>>> +	if (IS_ERR(pwm->mem))
>>>> +		return dev_err_probe(dev, PTR_ERR(pwm->mem),
>>>> +				"regs map failed");
>>>> +
>>>> +	pwm->clk = devm_clk_get(dev, NULL);
>>>> +	if (IS_ERR(pwm->clk))
>>>> +		return dev_err_probe(dev, PTR_ERR(pwm->clk),
>>>> +				"failed to get clock");
>>>> +
>>>> +	ret = clk_prepare_enable(pwm->clk);
>>>> +	if (ret)
>>>> +		return dev_err_probe(dev, ret, "clock enable failed");
>>>> +
>>>> +	pwm->chip.dev = dev;
>>>> +	pwm->chip.ops = &ipq_pwm_ops;
>>>> +	pwm->chip.npwm = 4;
>>>> +
>>>> +	ret = pwmchip_add(&pwm->chip);
>>>> +	if (ret < 0) {
>>>> +		dev_err_probe(dev, ret, "pwmchip_add() failed\n");
>>>> +		clk_disable_unprepare(pwm->clk);
>>>> +	}
>>>> +
>>>> +	return ret;
>>>> +}
>>>> +
>>>> +static int ipq_pwm_remove(struct platform_device *pdev)
>>>> +{
>>>> +	struct ipq_pwm_chip *pwm = platform_get_drvdata(pdev);
>>>> +
>>>> +	pwmchip_remove(&pwm->chip);
>>>> +	clk_disable_unprepare(pwm->clk);
>>>> +
>>>> +	return 0;
>>>> +}
>>>> +
>>>> +static const struct of_device_id pwm_ipq_dt_match[] = {
>>>> +	{ .compatible = "qcom,ipq6018-pwm", },
>>>> +	{}
>>>> +};
>>>> +MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match);
>>>> +
>>>> +static struct platform_driver ipq_pwm_driver = {
>>>> +	.driver = {
>>>> +		.name = "ipq-pwm",
>>>> +		.of_match_table = pwm_ipq_dt_match,
>>>> +	},
>>>> +	.probe = ipq_pwm_probe,
>>>> +	.remove = ipq_pwm_remove,
>>>> +};
>>>> +
>>>> +module_platform_driver(ipq_pwm_driver);
>>>> +
>>>> +MODULE_LICENSE("Dual BSD/GPL");
>> 


-- 
                                                     ~. .~   Tk Open Systems
=}------------------------------------------------ooO--U--Ooo------------{=
   - baruch@tkos.co.il - tel: +972.52.368.4656, http://www.tkos.co.il -

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
  2023-09-15  6:36     ` Baruch Siach
@ 2023-09-22  6:00       ` Devi Priya
  -1 siblings, 0 replies; 34+ messages in thread
From: Devi Priya @ 2023-09-22  6:00 UTC (permalink / raw)
  To: Baruch Siach
  Cc: Thierry Reding, Uwe Kleine-König, Andy Gross,
	Bjorn Andersson, Rob Herring, Robert Marko, Kathiravan T,
	linux-pwm, devicetree, linux-arm-msm, linux-arm-kernel



On 9/15/2023 12:06 PM, Baruch Siach wrote:
> Hi Devi,
> 
> [ Dropped/updated codeaurora.org addresses ]
> 
> On Fri, Sep 15 2023, Devi Priya wrote:
>> On 2/7/2022 3:00 PM, Baruch Siach wrote:
>>> From: Baruch Siach <baruch.siach@siklu.com>
>>> Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
>>> driver from downstream Codeaurora kernel tree. Removed support for older
>>> (V1) variants because I have no access to that hardware.
>>> Tested on IPQ6010 based hardware.
>>> Signed-off-by: Baruch Siach <baruch.siach@siklu.com>
>>> ---
>>> v11:
>>
>> Just curious to know if you have plans to post the next revision!
> 
> I have been waiting for comments from pwm maintainers before sending the
> next revision.
> 
> Unfortunately since then I lost access to the hardware, so I can't test
> suggested implementation changes.  The only pending issue in v11 is the
> trivial change that Nathan Chancellor suggested, which should be safe.
> 
> If you like to take it from here you are welcome.

Hi Baruch,

Could you pls point me to the pending comment that has to be addressed.
Was not able to find any open comments in the latest series.
https://lore.kernel.org/linux-arm-msm/8a331c88-c7d4-3a14-0ec3-fd616ea24a99@quicinc.com/

Thanks,
Devi Priya
> 
> Thanks,
> baruch
> 
>>> Address comment from Uwe Kleine-König:
>>>     Drop redundant registers field comments
>>>     Fix period limit check in .apply
>>>     Clarify the comment explaining skip of pre_div > pwm_div values
>>>     Add explicit check for clock rate within limit
>>>     Add comment explaining the selection of initial pre_div
>>>     Use pwm_div division with remainder instead of separate diff calculation
>>>     Round up duty_cycle calculation in .get_state
>>> v10:
>>>     Restore round up in pwm_div calculation; otherwise diff is always <=
>>>     0, so only bingo match works
>>>     Don't overwrite min_diff on every loop iteration
>>> v9:
>>> Address comment from Uwe Kleine-König:
>>>     Use period_ns*rate in dividers calculation for better accuracy
>>>     Round down pre_div and pwm_div
>>>     Add a comment explaining why pwm_div can't underflow
>>>     Add a comment explaining why pre_div > pwm_div end the search loop
>>>     Drop 'CFG_' from register macros
>>>     Rename to_ipq_pwm_chip() to ipq_pwm_from_chip()
>>>     Change bare 'unsigned' to 'unsigned int'
>>>     Clarify the comment on separate REG1 write for enable/disable
>>>     Round up the period value in .get_state
>>>     Use direct readl/writel so no need to check for regmap errors
>>> v7:
>>>     Change 'offset' to 'reg' for the tcsr offset (Rob)
>>>     Drop clock name; there is only one clock (Bjorn)
>>>     Simplify probe failure code path (Bjorn)
>>> v6:
>>> Address Uwe Kleine-König review comments:
>>>     Drop IPQ_PWM_MAX_DEVICES
>>>     Rely on assigned-clock-rates; drop IPQ_PWM_CLK_SRC_FREQ
>>>     Simplify register offset calculation
>>>     Calculate duty cycle more precisely
>>>     Refuse to set inverted polarity
>>>     Drop redundant IPQ_PWM_REG1_ENABLE bit clear
>>>     Remove x1000 factor in pwm_div calculation, use rate directly, and round
>>> up
>>>     Choose initial pre_div such that pwm_div < IPQ_PWM_MAX_DIV
>>>     Ensure pre_div <= pwm_div
>>>     Rename close_ to best_
>>>     Explain in comment why effective_div doesn't overflow
>>>     Limit pwm_div to IPQ_PWM_MAX_DIV - 1 to allow 100% duty cycle
>>>     Disable clock only after pwmchip_remove()
>>>     const pwm_ops
>>> Other changes:
>>>     Add missing linux/bitfield.h header include (kernel test robot)
>>>     Adjust code for PWM device node under TCSR (Rob Herring)
>>> v5:
>>> Use &tcsr_q6 syscon to access registers (Bjorn Andersson)
>>> Address Uwe Kleine-König review comments:
>>>     Implement .get_state()
>>>     Add IPQ_PWM_ prefix to local macros
>>>     Use GENMASK/BIT/FIELD_PREP for register fields access
>>>     Make type of config_div_and_duty() parameters consistent
>>>     Derive IPQ_PWM_MIN_PERIOD_NS from IPQ_PWM_CLK_SRC_FREQ
>>>     Integrate enable/disable into config_div_and_duty() to save register
>>> read,
>>>     and reduce frequency glitch on update
>>>     Use min() instead of min_t()
>>>     Fix comment format
>>>     Use dev_err_probe() to indicate probe step failure
>>>     Add missing clk_disable_unprepare() in .remove
>>>     Don't set .owner
>>> v4:
>>>     Use div64_u64() to fix link for 32-bit targets ((kernel test robot
>>>     <lkp@intel.com>, Uwe Kleine-König)
>>> v3:
>>>     s/qcom,pwm-ipq6018/qcom,ipq6018-pwm/ (Rob Herring)
>>>     Fix integer overflow on 32-bit targets (kernel test robot
>>> <lkp@intel.com>)
>>> v2:
>>> Address Uwe Kleine-König review comments:
>>>     Fix period calculation when out of range
>>>     Don't set period larger than requested
>>>     Remove PWM disable on configuration change
>>>     Implement .apply instead of non-atomic .config/.enable/.disable
>>>     Don't modify PWM on .request/.free
>>>     Check pwm_div underflow
>>>     Fix various code and comment formatting issues
>>> Other changes:
>>>     Use u64 divisor safe division
>>>     Remove now empty .request/.free
>>> ---
>>>    drivers/pwm/Kconfig   |  12 ++
>>>    drivers/pwm/Makefile  |   1 +
>>>    drivers/pwm/pwm-ipq.c | 281 ++++++++++++++++++++++++++++++++++++++++++
>>>    3 files changed, 294 insertions(+)
>>>    create mode 100644 drivers/pwm/pwm-ipq.c
>>> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
>>> index 21e3b05a5153..e39718137ecd 100644
>>> --- a/drivers/pwm/Kconfig
>>> +++ b/drivers/pwm/Kconfig
>>> @@ -260,6 +260,18 @@ config PWM_INTEL_LGM
>>>    	  To compile this driver as a module, choose M here: the module
>>>    	  will be called pwm-intel-lgm.
>>>    +config PWM_IPQ
>>> +	tristate "IPQ PWM support"
>>> +	depends on ARCH_QCOM || COMPILE_TEST
>>> +	depends on HAVE_CLK && HAS_IOMEM
>>> +	help
>>> +	  Generic PWM framework driver for IPQ PWM block which supports
>>> +	  4 pwm channels. Each of the these channels can be configured
>>> +	  independent of each other.
>>> +
>>> +	  To compile this driver as a module, choose M here: the module
>>> +	  will be called pwm-ipq.
>>> +
>>>    config PWM_IQS620A
>>>    	tristate "Azoteq IQS620A PWM support"
>>>    	depends on MFD_IQS62X || COMPILE_TEST
>>> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
>>> index 708840b7fba8..7402feae4b36 100644
>>> --- a/drivers/pwm/Makefile
>>> +++ b/drivers/pwm/Makefile
>>> @@ -22,6 +22,7 @@ obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
>>>    obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
>>>    obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
>>>    obj-$(CONFIG_PWM_INTEL_LGM)	+= pwm-intel-lgm.o
>>> +obj-$(CONFIG_PWM_IPQ)		+= pwm-ipq.o
>>>    obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
>>>    obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>>>    obj-$(CONFIG_PWM_KEEMBAY)	+= pwm-keembay.o
>>> diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
>>> new file mode 100644
>>> index 000000000000..994027290bcb
>>> --- /dev/null
>>> +++ b/drivers/pwm/pwm-ipq.c
>>> @@ -0,0 +1,281 @@
>>> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
>>> +/*
>>> + * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
>>> + */
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/pwm.h>
>>> +#include <linux/clk.h>
>>> +#include <linux/io.h>
>>> +#include <linux/of.h>
>>> +#include <linux/math64.h>
>>> +#include <linux/of_device.h>
>>> +#include <linux/bitfield.h>
>>> +#include <linux/units.h>
>>> +
>>> +/* The frequency range supported is 1 Hz to clock rate */
>>> +#define IPQ_PWM_MAX_PERIOD_NS	((u64)NSEC_PER_SEC)
>>> +
>>> +/*
>>> + * The max value specified for each field is based on the number of bits
>>> + * in the pwm control register for that field
>>> + */
>>> +#define IPQ_PWM_MAX_DIV		0xFFFF
>>> +
>>> +/*
>>> + * Two 32-bit registers for each PWM: REG0, and REG1.
>>> + * Base offset for PWM #i is at 8 * #i.
>>> + */
>>> +#define IPQ_PWM_REG0			0
>>> +#define IPQ_PWM_REG0_PWM_DIV		GENMASK(15, 0)
>>> +#define IPQ_PWM_REG0_HI_DURATION	GENMASK(31, 16)
>>> +
>>> +#define IPQ_PWM_REG1			4
>>> +#define IPQ_PWM_REG1_PRE_DIV		GENMASK(15, 0)
>>> +/*
>>> + * Enable bit is set to enable output toggling in pwm device.
>>> + * Update bit is set to reflect the changed divider and high duration
>>> + * values in register.
>>> + */
>>> +#define IPQ_PWM_REG1_UPDATE		BIT(30)
>>> +#define IPQ_PWM_REG1_ENABLE		BIT(31)
>>> +
>>> +
>>> +struct ipq_pwm_chip {
>>> +	struct pwm_chip chip;
>>> +	struct clk *clk;
>>> +	void __iomem *mem;
>>> +};
>>> +
>>> +static struct ipq_pwm_chip *ipq_pwm_from_chip(struct pwm_chip *chip)
>>> +{
>>> +	return container_of(chip, struct ipq_pwm_chip, chip);
>>> +}
>>> +
>>> +static unsigned int ipq_pwm_reg_read(struct pwm_device *pwm, unsigned int reg)
>>> +{
>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
>>> +	unsigned int off = 8 * pwm->hwpwm + reg;
>>> +
>>> +	return readl(ipq_chip->mem + off);
>>> +}
>>> +
>>> +static void ipq_pwm_reg_write(struct pwm_device *pwm, unsigned int reg,
>>> +			      unsigned int val)
>>> +{
>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
>>> +	unsigned int off = 8 * pwm->hwpwm + reg;
>>> +
>>> +	writel(val, ipq_chip->mem + off);
>>> +}
>>> +
>>> +static void config_div_and_duty(struct pwm_device *pwm, unsigned int pre_div,
>>> +			unsigned int pwm_div, unsigned long rate, u64 duty_ns,
>>> +			bool enable)
>>> +{
>>> +	unsigned long hi_dur;
>>> +	unsigned long val = 0;
>>> +
>>> +	/*
>>> +	 * high duration = pwm duty * (pwm div + 1)
>>> +	 * pwm duty = duty_ns / period_ns
>>> +	 */
>>> +	hi_dur = div64_u64(duty_ns * rate, (pre_div + 1) * NSEC_PER_SEC);
>>> +
>>> +	val = FIELD_PREP(IPQ_PWM_REG0_HI_DURATION, hi_dur) |
>>> +		FIELD_PREP(IPQ_PWM_REG0_PWM_DIV, pwm_div);
>>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG0, val);
>>> +
>>> +	val = FIELD_PREP(IPQ_PWM_REG1_PRE_DIV, pre_div);
>>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
>>> +
>>> +	/* PWM enable toggle needs a separate write to REG1 */
>>> +	val |= IPQ_PWM_REG1_UPDATE;
>>> +	if (enable)
>>> +		val |= IPQ_PWM_REG1_ENABLE;
>>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
>>> +}
>>> +
>>> +static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
>>> +			 const struct pwm_state *state)
>>> +{
>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
>>> +	unsigned int pre_div, pwm_div, best_pre_div, best_pwm_div;
>>> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
>>> +	u64 period_ns, duty_ns, period_rate;
>>> +	u64 min_diff;
>>> +
>>> +	if (state->polarity != PWM_POLARITY_NORMAL)
>>> +		return -EINVAL;
>>> +
>>> +	if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC, rate))
>>> +		return -ERANGE;
>>> +
>>> +	period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
>>> +	duty_ns = min(state->duty_cycle, period_ns);
>>> +
>>> +	/*
>>> +	 * period_ns is 1G or less. As long as rate is less than 16 GHz,
>>> +	 * period_rate does not overflow. Make that explicit.
>>> +	 */
>>> +	if (rate > 16ULL * GIGA)
>>> +		return -EINVAL;
>>> +	period_rate = period_ns * rate;
>>> +	best_pre_div = IPQ_PWM_MAX_DIV;
>>> +	best_pwm_div = IPQ_PWM_MAX_DIV;
>>> +	/*
>>> +	 * We don't need to consider pre_div values smaller than
>>> +	 *
>>> +	 *                              period_rate
>>> +	 *  pre_div_min := ------------------------------------
>>> +	 *                 NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1)
>>> +	 *
>>> +	 * because pre_div = pre_div_min results in a better
>>> +	 * approximation.
>>> +	 */
>>> +	pre_div = div64_u64(period_rate,
>>> +			(u64)NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1));
>>> +	min_diff = period_rate;
>>> +
>>> +	for (; pre_div <= IPQ_PWM_MAX_DIV; pre_div++) {
>>> +		u64 remainder;
>>> +
>>> +		pwm_div = div64_u64_rem(period_rate,
>>> +				(u64)NSEC_PER_SEC * (pre_div + 1), &remainder);
>>> +		/* pwm_div is unsigned; the check below catches underflow */
>>> +		pwm_div--;
>>> +
>>> +		/*
>>> +		 * Swapping values for pre_div and pwm_div produces the same
>>> +		 * period length. So we can skip all settings with pre_div >
>>> +		 * pwm_div which results in bigger constraints for selecting
>>> +		 * the duty_cycle than with the two values swapped.
>>> +		 */
>>> +		if (pre_div > pwm_div)
>>> +			break;
>>> +
>>> +		/*
>>> +		 * Make sure we can do 100% duty cycle where
>>> +		 * hi_dur == pwm_div + 1
>>> +		 */
>>> +		if (pwm_div > IPQ_PWM_MAX_DIV - 1)
>>> +			continue;
>>> +
>>> +		if (remainder < min_diff) {
>>> +			best_pre_div = pre_div;
>>> +			best_pwm_div = pwm_div;
>>> +			min_diff = remainder;
>>> +
>>> +			if (min_diff == 0) /* bingo */
>>> +				break;
>>> +		}
>>> +	}
>>> +
>>> +	/* config divider values for the closest possible frequency */
>>> +	config_div_and_duty(pwm, best_pre_div, best_pwm_div,
>>> +			    rate, duty_ns, state->enabled);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static void ipq_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
>>> +			      struct pwm_state *state)
>>> +{
>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
>>> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
>>> +	unsigned int pre_div, pwm_div, hi_dur;
>>> +	u64 effective_div, hi_div;
>>> +	u32 reg0, reg1;
>>> +
>>> +	reg0 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG0);
>>> +	reg1 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG1);
>>> +
>>> +	state->polarity = PWM_POLARITY_NORMAL;
>>> +	state->enabled = reg1 & IPQ_PWM_REG1_ENABLE;
>>> +
>>> +	pwm_div = FIELD_GET(IPQ_PWM_REG0_PWM_DIV, reg0);
>>> +	hi_dur = FIELD_GET(IPQ_PWM_REG0_HI_DURATION, reg0);
>>> +	pre_div = FIELD_GET(IPQ_PWM_REG1_PRE_DIV, reg1);
>>> +
>>> +	/* No overflow here, both pre_div and pwm_div <= 0xffff */
>>> +	effective_div = (u64)(pre_div + 1) * (pwm_div + 1);
>>> +	state->period = DIV64_U64_ROUND_UP(effective_div * NSEC_PER_SEC, rate);
>>> +
>>> +	hi_div = hi_dur * (pre_div + 1);
>>> +	state->duty_cycle = DIV64_U64_ROUND_UP(hi_div * NSEC_PER_SEC, rate);
>>> +}
>>> +
>>> +static const struct pwm_ops ipq_pwm_ops = {
>>> +	.apply = ipq_pwm_apply,
>>> +	.get_state = ipq_pwm_get_state,
>>> +	.owner = THIS_MODULE,
>>> +};
>>> +
>>> +static int ipq_pwm_probe(struct platform_device *pdev)
>>> +{
>>> +	struct ipq_pwm_chip *pwm;
>>> +	struct device *dev = &pdev->dev;
>>> +	int ret;
>>> +
>>> +	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
>>> +	if (!pwm)
>>> +		return -ENOMEM;
>>> +
>>> +	platform_set_drvdata(pdev, pwm);
>>> +
>>> +	pwm->mem = devm_platform_ioremap_resource(pdev, 0);
>>> +	if (IS_ERR(pwm->mem))
>>> +		return dev_err_probe(dev, PTR_ERR(pwm->mem),
>>> +				"regs map failed");
>>> +
>>> +	pwm->clk = devm_clk_get(dev, NULL);
>>> +	if (IS_ERR(pwm->clk))
>>> +		return dev_err_probe(dev, PTR_ERR(pwm->clk),
>>> +				"failed to get clock");
>>> +
>>> +	ret = clk_prepare_enable(pwm->clk);
>>> +	if (ret)
>>> +		return dev_err_probe(dev, ret, "clock enable failed");
>>> +
>>> +	pwm->chip.dev = dev;
>>> +	pwm->chip.ops = &ipq_pwm_ops;
>>> +	pwm->chip.npwm = 4;
>>> +
>>> +	ret = pwmchip_add(&pwm->chip);
>>> +	if (ret < 0) {
>>> +		dev_err_probe(dev, ret, "pwmchip_add() failed\n");
>>> +		clk_disable_unprepare(pwm->clk);
>>> +	}
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int ipq_pwm_remove(struct platform_device *pdev)
>>> +{
>>> +	struct ipq_pwm_chip *pwm = platform_get_drvdata(pdev);
>>> +
>>> +	pwmchip_remove(&pwm->chip);
>>> +	clk_disable_unprepare(pwm->clk);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static const struct of_device_id pwm_ipq_dt_match[] = {
>>> +	{ .compatible = "qcom,ipq6018-pwm", },
>>> +	{}
>>> +};
>>> +MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match);
>>> +
>>> +static struct platform_driver ipq_pwm_driver = {
>>> +	.driver = {
>>> +		.name = "ipq-pwm",
>>> +		.of_match_table = pwm_ipq_dt_match,
>>> +	},
>>> +	.probe = ipq_pwm_probe,
>>> +	.remove = ipq_pwm_remove,
>>> +};
>>> +
>>> +module_platform_driver(ipq_pwm_driver);
>>> +
>>> +MODULE_LICENSE("Dual BSD/GPL");
> 
> 

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
@ 2023-09-22  6:00       ` Devi Priya
  0 siblings, 0 replies; 34+ messages in thread
From: Devi Priya @ 2023-09-22  6:00 UTC (permalink / raw)
  To: Baruch Siach
  Cc: Thierry Reding, Uwe Kleine-König, Andy Gross,
	Bjorn Andersson, Rob Herring, Robert Marko, Kathiravan T,
	linux-pwm, devicetree, linux-arm-msm, linux-arm-kernel



On 9/15/2023 12:06 PM, Baruch Siach wrote:
> Hi Devi,
> 
> [ Dropped/updated codeaurora.org addresses ]
> 
> On Fri, Sep 15 2023, Devi Priya wrote:
>> On 2/7/2022 3:00 PM, Baruch Siach wrote:
>>> From: Baruch Siach <baruch.siach@siklu.com>
>>> Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
>>> driver from downstream Codeaurora kernel tree. Removed support for older
>>> (V1) variants because I have no access to that hardware.
>>> Tested on IPQ6010 based hardware.
>>> Signed-off-by: Baruch Siach <baruch.siach@siklu.com>
>>> ---
>>> v11:
>>
>> Just curious to know if you have plans to post the next revision!
> 
> I have been waiting for comments from pwm maintainers before sending the
> next revision.
> 
> Unfortunately since then I lost access to the hardware, so I can't test
> suggested implementation changes.  The only pending issue in v11 is the
> trivial change that Nathan Chancellor suggested, which should be safe.
> 
> If you like to take it from here you are welcome.

Hi Baruch,

Could you pls point me to the pending comment that has to be addressed.
Was not able to find any open comments in the latest series.
https://lore.kernel.org/linux-arm-msm/8a331c88-c7d4-3a14-0ec3-fd616ea24a99@quicinc.com/

Thanks,
Devi Priya
> 
> Thanks,
> baruch
> 
>>> Address comment from Uwe Kleine-König:
>>>     Drop redundant registers field comments
>>>     Fix period limit check in .apply
>>>     Clarify the comment explaining skip of pre_div > pwm_div values
>>>     Add explicit check for clock rate within limit
>>>     Add comment explaining the selection of initial pre_div
>>>     Use pwm_div division with remainder instead of separate diff calculation
>>>     Round up duty_cycle calculation in .get_state
>>> v10:
>>>     Restore round up in pwm_div calculation; otherwise diff is always <=
>>>     0, so only bingo match works
>>>     Don't overwrite min_diff on every loop iteration
>>> v9:
>>> Address comment from Uwe Kleine-König:
>>>     Use period_ns*rate in dividers calculation for better accuracy
>>>     Round down pre_div and pwm_div
>>>     Add a comment explaining why pwm_div can't underflow
>>>     Add a comment explaining why pre_div > pwm_div end the search loop
>>>     Drop 'CFG_' from register macros
>>>     Rename to_ipq_pwm_chip() to ipq_pwm_from_chip()
>>>     Change bare 'unsigned' to 'unsigned int'
>>>     Clarify the comment on separate REG1 write for enable/disable
>>>     Round up the period value in .get_state
>>>     Use direct readl/writel so no need to check for regmap errors
>>> v7:
>>>     Change 'offset' to 'reg' for the tcsr offset (Rob)
>>>     Drop clock name; there is only one clock (Bjorn)
>>>     Simplify probe failure code path (Bjorn)
>>> v6:
>>> Address Uwe Kleine-König review comments:
>>>     Drop IPQ_PWM_MAX_DEVICES
>>>     Rely on assigned-clock-rates; drop IPQ_PWM_CLK_SRC_FREQ
>>>     Simplify register offset calculation
>>>     Calculate duty cycle more precisely
>>>     Refuse to set inverted polarity
>>>     Drop redundant IPQ_PWM_REG1_ENABLE bit clear
>>>     Remove x1000 factor in pwm_div calculation, use rate directly, and round
>>> up
>>>     Choose initial pre_div such that pwm_div < IPQ_PWM_MAX_DIV
>>>     Ensure pre_div <= pwm_div
>>>     Rename close_ to best_
>>>     Explain in comment why effective_div doesn't overflow
>>>     Limit pwm_div to IPQ_PWM_MAX_DIV - 1 to allow 100% duty cycle
>>>     Disable clock only after pwmchip_remove()
>>>     const pwm_ops
>>> Other changes:
>>>     Add missing linux/bitfield.h header include (kernel test robot)
>>>     Adjust code for PWM device node under TCSR (Rob Herring)
>>> v5:
>>> Use &tcsr_q6 syscon to access registers (Bjorn Andersson)
>>> Address Uwe Kleine-König review comments:
>>>     Implement .get_state()
>>>     Add IPQ_PWM_ prefix to local macros
>>>     Use GENMASK/BIT/FIELD_PREP for register fields access
>>>     Make type of config_div_and_duty() parameters consistent
>>>     Derive IPQ_PWM_MIN_PERIOD_NS from IPQ_PWM_CLK_SRC_FREQ
>>>     Integrate enable/disable into config_div_and_duty() to save register
>>> read,
>>>     and reduce frequency glitch on update
>>>     Use min() instead of min_t()
>>>     Fix comment format
>>>     Use dev_err_probe() to indicate probe step failure
>>>     Add missing clk_disable_unprepare() in .remove
>>>     Don't set .owner
>>> v4:
>>>     Use div64_u64() to fix link for 32-bit targets ((kernel test robot
>>>     <lkp@intel.com>, Uwe Kleine-König)
>>> v3:
>>>     s/qcom,pwm-ipq6018/qcom,ipq6018-pwm/ (Rob Herring)
>>>     Fix integer overflow on 32-bit targets (kernel test robot
>>> <lkp@intel.com>)
>>> v2:
>>> Address Uwe Kleine-König review comments:
>>>     Fix period calculation when out of range
>>>     Don't set period larger than requested
>>>     Remove PWM disable on configuration change
>>>     Implement .apply instead of non-atomic .config/.enable/.disable
>>>     Don't modify PWM on .request/.free
>>>     Check pwm_div underflow
>>>     Fix various code and comment formatting issues
>>> Other changes:
>>>     Use u64 divisor safe division
>>>     Remove now empty .request/.free
>>> ---
>>>    drivers/pwm/Kconfig   |  12 ++
>>>    drivers/pwm/Makefile  |   1 +
>>>    drivers/pwm/pwm-ipq.c | 281 ++++++++++++++++++++++++++++++++++++++++++
>>>    3 files changed, 294 insertions(+)
>>>    create mode 100644 drivers/pwm/pwm-ipq.c
>>> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
>>> index 21e3b05a5153..e39718137ecd 100644
>>> --- a/drivers/pwm/Kconfig
>>> +++ b/drivers/pwm/Kconfig
>>> @@ -260,6 +260,18 @@ config PWM_INTEL_LGM
>>>    	  To compile this driver as a module, choose M here: the module
>>>    	  will be called pwm-intel-lgm.
>>>    +config PWM_IPQ
>>> +	tristate "IPQ PWM support"
>>> +	depends on ARCH_QCOM || COMPILE_TEST
>>> +	depends on HAVE_CLK && HAS_IOMEM
>>> +	help
>>> +	  Generic PWM framework driver for IPQ PWM block which supports
>>> +	  4 pwm channels. Each of the these channels can be configured
>>> +	  independent of each other.
>>> +
>>> +	  To compile this driver as a module, choose M here: the module
>>> +	  will be called pwm-ipq.
>>> +
>>>    config PWM_IQS620A
>>>    	tristate "Azoteq IQS620A PWM support"
>>>    	depends on MFD_IQS62X || COMPILE_TEST
>>> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
>>> index 708840b7fba8..7402feae4b36 100644
>>> --- a/drivers/pwm/Makefile
>>> +++ b/drivers/pwm/Makefile
>>> @@ -22,6 +22,7 @@ obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
>>>    obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
>>>    obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
>>>    obj-$(CONFIG_PWM_INTEL_LGM)	+= pwm-intel-lgm.o
>>> +obj-$(CONFIG_PWM_IPQ)		+= pwm-ipq.o
>>>    obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
>>>    obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>>>    obj-$(CONFIG_PWM_KEEMBAY)	+= pwm-keembay.o
>>> diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
>>> new file mode 100644
>>> index 000000000000..994027290bcb
>>> --- /dev/null
>>> +++ b/drivers/pwm/pwm-ipq.c
>>> @@ -0,0 +1,281 @@
>>> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
>>> +/*
>>> + * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
>>> + */
>>> +
>>> +#include <linux/module.h>
>>> +#include <linux/platform_device.h>
>>> +#include <linux/pwm.h>
>>> +#include <linux/clk.h>
>>> +#include <linux/io.h>
>>> +#include <linux/of.h>
>>> +#include <linux/math64.h>
>>> +#include <linux/of_device.h>
>>> +#include <linux/bitfield.h>
>>> +#include <linux/units.h>
>>> +
>>> +/* The frequency range supported is 1 Hz to clock rate */
>>> +#define IPQ_PWM_MAX_PERIOD_NS	((u64)NSEC_PER_SEC)
>>> +
>>> +/*
>>> + * The max value specified for each field is based on the number of bits
>>> + * in the pwm control register for that field
>>> + */
>>> +#define IPQ_PWM_MAX_DIV		0xFFFF
>>> +
>>> +/*
>>> + * Two 32-bit registers for each PWM: REG0, and REG1.
>>> + * Base offset for PWM #i is at 8 * #i.
>>> + */
>>> +#define IPQ_PWM_REG0			0
>>> +#define IPQ_PWM_REG0_PWM_DIV		GENMASK(15, 0)
>>> +#define IPQ_PWM_REG0_HI_DURATION	GENMASK(31, 16)
>>> +
>>> +#define IPQ_PWM_REG1			4
>>> +#define IPQ_PWM_REG1_PRE_DIV		GENMASK(15, 0)
>>> +/*
>>> + * Enable bit is set to enable output toggling in pwm device.
>>> + * Update bit is set to reflect the changed divider and high duration
>>> + * values in register.
>>> + */
>>> +#define IPQ_PWM_REG1_UPDATE		BIT(30)
>>> +#define IPQ_PWM_REG1_ENABLE		BIT(31)
>>> +
>>> +
>>> +struct ipq_pwm_chip {
>>> +	struct pwm_chip chip;
>>> +	struct clk *clk;
>>> +	void __iomem *mem;
>>> +};
>>> +
>>> +static struct ipq_pwm_chip *ipq_pwm_from_chip(struct pwm_chip *chip)
>>> +{
>>> +	return container_of(chip, struct ipq_pwm_chip, chip);
>>> +}
>>> +
>>> +static unsigned int ipq_pwm_reg_read(struct pwm_device *pwm, unsigned int reg)
>>> +{
>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
>>> +	unsigned int off = 8 * pwm->hwpwm + reg;
>>> +
>>> +	return readl(ipq_chip->mem + off);
>>> +}
>>> +
>>> +static void ipq_pwm_reg_write(struct pwm_device *pwm, unsigned int reg,
>>> +			      unsigned int val)
>>> +{
>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
>>> +	unsigned int off = 8 * pwm->hwpwm + reg;
>>> +
>>> +	writel(val, ipq_chip->mem + off);
>>> +}
>>> +
>>> +static void config_div_and_duty(struct pwm_device *pwm, unsigned int pre_div,
>>> +			unsigned int pwm_div, unsigned long rate, u64 duty_ns,
>>> +			bool enable)
>>> +{
>>> +	unsigned long hi_dur;
>>> +	unsigned long val = 0;
>>> +
>>> +	/*
>>> +	 * high duration = pwm duty * (pwm div + 1)
>>> +	 * pwm duty = duty_ns / period_ns
>>> +	 */
>>> +	hi_dur = div64_u64(duty_ns * rate, (pre_div + 1) * NSEC_PER_SEC);
>>> +
>>> +	val = FIELD_PREP(IPQ_PWM_REG0_HI_DURATION, hi_dur) |
>>> +		FIELD_PREP(IPQ_PWM_REG0_PWM_DIV, pwm_div);
>>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG0, val);
>>> +
>>> +	val = FIELD_PREP(IPQ_PWM_REG1_PRE_DIV, pre_div);
>>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
>>> +
>>> +	/* PWM enable toggle needs a separate write to REG1 */
>>> +	val |= IPQ_PWM_REG1_UPDATE;
>>> +	if (enable)
>>> +		val |= IPQ_PWM_REG1_ENABLE;
>>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
>>> +}
>>> +
>>> +static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
>>> +			 const struct pwm_state *state)
>>> +{
>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
>>> +	unsigned int pre_div, pwm_div, best_pre_div, best_pwm_div;
>>> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
>>> +	u64 period_ns, duty_ns, period_rate;
>>> +	u64 min_diff;
>>> +
>>> +	if (state->polarity != PWM_POLARITY_NORMAL)
>>> +		return -EINVAL;
>>> +
>>> +	if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC, rate))
>>> +		return -ERANGE;
>>> +
>>> +	period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
>>> +	duty_ns = min(state->duty_cycle, period_ns);
>>> +
>>> +	/*
>>> +	 * period_ns is 1G or less. As long as rate is less than 16 GHz,
>>> +	 * period_rate does not overflow. Make that explicit.
>>> +	 */
>>> +	if (rate > 16ULL * GIGA)
>>> +		return -EINVAL;
>>> +	period_rate = period_ns * rate;
>>> +	best_pre_div = IPQ_PWM_MAX_DIV;
>>> +	best_pwm_div = IPQ_PWM_MAX_DIV;
>>> +	/*
>>> +	 * We don't need to consider pre_div values smaller than
>>> +	 *
>>> +	 *                              period_rate
>>> +	 *  pre_div_min := ------------------------------------
>>> +	 *                 NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1)
>>> +	 *
>>> +	 * because pre_div = pre_div_min results in a better
>>> +	 * approximation.
>>> +	 */
>>> +	pre_div = div64_u64(period_rate,
>>> +			(u64)NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1));
>>> +	min_diff = period_rate;
>>> +
>>> +	for (; pre_div <= IPQ_PWM_MAX_DIV; pre_div++) {
>>> +		u64 remainder;
>>> +
>>> +		pwm_div = div64_u64_rem(period_rate,
>>> +				(u64)NSEC_PER_SEC * (pre_div + 1), &remainder);
>>> +		/* pwm_div is unsigned; the check below catches underflow */
>>> +		pwm_div--;
>>> +
>>> +		/*
>>> +		 * Swapping values for pre_div and pwm_div produces the same
>>> +		 * period length. So we can skip all settings with pre_div >
>>> +		 * pwm_div which results in bigger constraints for selecting
>>> +		 * the duty_cycle than with the two values swapped.
>>> +		 */
>>> +		if (pre_div > pwm_div)
>>> +			break;
>>> +
>>> +		/*
>>> +		 * Make sure we can do 100% duty cycle where
>>> +		 * hi_dur == pwm_div + 1
>>> +		 */
>>> +		if (pwm_div > IPQ_PWM_MAX_DIV - 1)
>>> +			continue;
>>> +
>>> +		if (remainder < min_diff) {
>>> +			best_pre_div = pre_div;
>>> +			best_pwm_div = pwm_div;
>>> +			min_diff = remainder;
>>> +
>>> +			if (min_diff == 0) /* bingo */
>>> +				break;
>>> +		}
>>> +	}
>>> +
>>> +	/* config divider values for the closest possible frequency */
>>> +	config_div_and_duty(pwm, best_pre_div, best_pwm_div,
>>> +			    rate, duty_ns, state->enabled);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static void ipq_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
>>> +			      struct pwm_state *state)
>>> +{
>>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
>>> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
>>> +	unsigned int pre_div, pwm_div, hi_dur;
>>> +	u64 effective_div, hi_div;
>>> +	u32 reg0, reg1;
>>> +
>>> +	reg0 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG0);
>>> +	reg1 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG1);
>>> +
>>> +	state->polarity = PWM_POLARITY_NORMAL;
>>> +	state->enabled = reg1 & IPQ_PWM_REG1_ENABLE;
>>> +
>>> +	pwm_div = FIELD_GET(IPQ_PWM_REG0_PWM_DIV, reg0);
>>> +	hi_dur = FIELD_GET(IPQ_PWM_REG0_HI_DURATION, reg0);
>>> +	pre_div = FIELD_GET(IPQ_PWM_REG1_PRE_DIV, reg1);
>>> +
>>> +	/* No overflow here, both pre_div and pwm_div <= 0xffff */
>>> +	effective_div = (u64)(pre_div + 1) * (pwm_div + 1);
>>> +	state->period = DIV64_U64_ROUND_UP(effective_div * NSEC_PER_SEC, rate);
>>> +
>>> +	hi_div = hi_dur * (pre_div + 1);
>>> +	state->duty_cycle = DIV64_U64_ROUND_UP(hi_div * NSEC_PER_SEC, rate);
>>> +}
>>> +
>>> +static const struct pwm_ops ipq_pwm_ops = {
>>> +	.apply = ipq_pwm_apply,
>>> +	.get_state = ipq_pwm_get_state,
>>> +	.owner = THIS_MODULE,
>>> +};
>>> +
>>> +static int ipq_pwm_probe(struct platform_device *pdev)
>>> +{
>>> +	struct ipq_pwm_chip *pwm;
>>> +	struct device *dev = &pdev->dev;
>>> +	int ret;
>>> +
>>> +	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
>>> +	if (!pwm)
>>> +		return -ENOMEM;
>>> +
>>> +	platform_set_drvdata(pdev, pwm);
>>> +
>>> +	pwm->mem = devm_platform_ioremap_resource(pdev, 0);
>>> +	if (IS_ERR(pwm->mem))
>>> +		return dev_err_probe(dev, PTR_ERR(pwm->mem),
>>> +				"regs map failed");
>>> +
>>> +	pwm->clk = devm_clk_get(dev, NULL);
>>> +	if (IS_ERR(pwm->clk))
>>> +		return dev_err_probe(dev, PTR_ERR(pwm->clk),
>>> +				"failed to get clock");
>>> +
>>> +	ret = clk_prepare_enable(pwm->clk);
>>> +	if (ret)
>>> +		return dev_err_probe(dev, ret, "clock enable failed");
>>> +
>>> +	pwm->chip.dev = dev;
>>> +	pwm->chip.ops = &ipq_pwm_ops;
>>> +	pwm->chip.npwm = 4;
>>> +
>>> +	ret = pwmchip_add(&pwm->chip);
>>> +	if (ret < 0) {
>>> +		dev_err_probe(dev, ret, "pwmchip_add() failed\n");
>>> +		clk_disable_unprepare(pwm->clk);
>>> +	}
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int ipq_pwm_remove(struct platform_device *pdev)
>>> +{
>>> +	struct ipq_pwm_chip *pwm = platform_get_drvdata(pdev);
>>> +
>>> +	pwmchip_remove(&pwm->chip);
>>> +	clk_disable_unprepare(pwm->clk);
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static const struct of_device_id pwm_ipq_dt_match[] = {
>>> +	{ .compatible = "qcom,ipq6018-pwm", },
>>> +	{}
>>> +};
>>> +MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match);
>>> +
>>> +static struct platform_driver ipq_pwm_driver = {
>>> +	.driver = {
>>> +		.name = "ipq-pwm",
>>> +		.of_match_table = pwm_ipq_dt_match,
>>> +	},
>>> +	.probe = ipq_pwm_probe,
>>> +	.remove = ipq_pwm_remove,
>>> +};
>>> +
>>> +module_platform_driver(ipq_pwm_driver);
>>> +
>>> +MODULE_LICENSE("Dual BSD/GPL");
> 
> 

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
  2023-09-15  6:36     ` Baruch Siach
@ 2023-09-20  4:58       ` Devi Priya
  -1 siblings, 0 replies; 34+ messages in thread
From: Devi Priya @ 2023-09-20  4:58 UTC (permalink / raw)
  To: Baruch Siach
  Cc: Thierry Reding, Uwe Kleine-König, Andy Gross,
	Bjorn Andersson, Rob Herring, Robert Marko, Kathiravan T,
	linux-pwm, devicetree, linux-arm-msm, linux-arm-kernel



On 9/15/2023 12:06 PM, Baruch Siach wrote:
>> Just curious to know if you have plans to post the next revision!
> I have been waiting for comments from pwm maintainers before sending the
> next revision.
> 
> Unfortunately since then I lost access to the hardware, so I can't test
> suggested implementation changes.  The only pending issue in v11 is the
> trivial change that Nathan Chancellor suggested, which should be safe.
> 
> If you like to take it from here you are welcome.

Sure, Thanks Baruch!

Regards,
Devi Priya
> 
> Thanks,
> baruch
> 

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
@ 2023-09-20  4:58       ` Devi Priya
  0 siblings, 0 replies; 34+ messages in thread
From: Devi Priya @ 2023-09-20  4:58 UTC (permalink / raw)
  To: Baruch Siach
  Cc: Thierry Reding, Uwe Kleine-König, Andy Gross,
	Bjorn Andersson, Rob Herring, Robert Marko, Kathiravan T,
	linux-pwm, devicetree, linux-arm-msm, linux-arm-kernel



On 9/15/2023 12:06 PM, Baruch Siach wrote:
>> Just curious to know if you have plans to post the next revision!
> I have been waiting for comments from pwm maintainers before sending the
> next revision.
> 
> Unfortunately since then I lost access to the hardware, so I can't test
> suggested implementation changes.  The only pending issue in v11 is the
> trivial change that Nathan Chancellor suggested, which should be safe.
> 
> If you like to take it from here you are welcome.

Sure, Thanks Baruch!

Regards,
Devi Priya
> 
> Thanks,
> baruch
> 

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
  2023-09-15  6:25   ` Devi Priya
@ 2023-09-15  6:36     ` Baruch Siach
  -1 siblings, 0 replies; 34+ messages in thread
From: Baruch Siach @ 2023-09-15  6:36 UTC (permalink / raw)
  To: Devi Priya
  Cc: Thierry Reding, Uwe Kleine-König, Andy Gross,
	Bjorn Andersson, Rob Herring, Robert Marko, Kathiravan T,
	linux-pwm, devicetree, linux-arm-msm, linux-arm-kernel

Hi Devi,

[ Dropped/updated codeaurora.org addresses ]

On Fri, Sep 15 2023, Devi Priya wrote:
> On 2/7/2022 3:00 PM, Baruch Siach wrote:
>> From: Baruch Siach <baruch.siach@siklu.com>
>> Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
>> driver from downstream Codeaurora kernel tree. Removed support for older
>> (V1) variants because I have no access to that hardware.
>> Tested on IPQ6010 based hardware.
>> Signed-off-by: Baruch Siach <baruch.siach@siklu.com>
>> ---
>> v11:
>
> Just curious to know if you have plans to post the next revision!

I have been waiting for comments from pwm maintainers before sending the
next revision.

Unfortunately since then I lost access to the hardware, so I can't test
suggested implementation changes.  The only pending issue in v11 is the
trivial change that Nathan Chancellor suggested, which should be safe.

If you like to take it from here you are welcome.

Thanks,
baruch

>> Address comment from Uwe Kleine-König:
>>    Drop redundant registers field comments
>>    Fix period limit check in .apply
>>    Clarify the comment explaining skip of pre_div > pwm_div values
>>    Add explicit check for clock rate within limit
>>    Add comment explaining the selection of initial pre_div
>>    Use pwm_div division with remainder instead of separate diff calculation
>>    Round up duty_cycle calculation in .get_state
>> v10:
>>    Restore round up in pwm_div calculation; otherwise diff is always <=
>>    0, so only bingo match works
>>    Don't overwrite min_diff on every loop iteration
>> v9:
>> Address comment from Uwe Kleine-König:
>>    Use period_ns*rate in dividers calculation for better accuracy
>>    Round down pre_div and pwm_div
>>    Add a comment explaining why pwm_div can't underflow
>>    Add a comment explaining why pre_div > pwm_div end the search loop
>>    Drop 'CFG_' from register macros
>>    Rename to_ipq_pwm_chip() to ipq_pwm_from_chip()
>>    Change bare 'unsigned' to 'unsigned int'
>>    Clarify the comment on separate REG1 write for enable/disable
>>    Round up the period value in .get_state
>>    Use direct readl/writel so no need to check for regmap errors
>> v7:
>>    Change 'offset' to 'reg' for the tcsr offset (Rob)
>>    Drop clock name; there is only one clock (Bjorn)
>>    Simplify probe failure code path (Bjorn)
>> v6:
>> Address Uwe Kleine-König review comments:
>>    Drop IPQ_PWM_MAX_DEVICES
>>    Rely on assigned-clock-rates; drop IPQ_PWM_CLK_SRC_FREQ
>>    Simplify register offset calculation
>>    Calculate duty cycle more precisely
>>    Refuse to set inverted polarity
>>    Drop redundant IPQ_PWM_REG1_ENABLE bit clear
>>    Remove x1000 factor in pwm_div calculation, use rate directly, and round
>> up
>>    Choose initial pre_div such that pwm_div < IPQ_PWM_MAX_DIV
>>    Ensure pre_div <= pwm_div
>>    Rename close_ to best_
>>    Explain in comment why effective_div doesn't overflow
>>    Limit pwm_div to IPQ_PWM_MAX_DIV - 1 to allow 100% duty cycle
>>    Disable clock only after pwmchip_remove()
>>    const pwm_ops
>> Other changes:
>>    Add missing linux/bitfield.h header include (kernel test robot)
>>    Adjust code for PWM device node under TCSR (Rob Herring)
>> v5:
>> Use &tcsr_q6 syscon to access registers (Bjorn Andersson)
>> Address Uwe Kleine-König review comments:
>>    Implement .get_state()
>>    Add IPQ_PWM_ prefix to local macros
>>    Use GENMASK/BIT/FIELD_PREP for register fields access
>>    Make type of config_div_and_duty() parameters consistent
>>    Derive IPQ_PWM_MIN_PERIOD_NS from IPQ_PWM_CLK_SRC_FREQ
>>    Integrate enable/disable into config_div_and_duty() to save register
>> read,
>>    and reduce frequency glitch on update
>>    Use min() instead of min_t()
>>    Fix comment format
>>    Use dev_err_probe() to indicate probe step failure
>>    Add missing clk_disable_unprepare() in .remove
>>    Don't set .owner
>> v4:
>>    Use div64_u64() to fix link for 32-bit targets ((kernel test robot
>>    <lkp@intel.com>, Uwe Kleine-König)
>> v3:
>>    s/qcom,pwm-ipq6018/qcom,ipq6018-pwm/ (Rob Herring)
>>    Fix integer overflow on 32-bit targets (kernel test robot
>> <lkp@intel.com>)
>> v2:
>> Address Uwe Kleine-König review comments:
>>    Fix period calculation when out of range
>>    Don't set period larger than requested
>>    Remove PWM disable on configuration change
>>    Implement .apply instead of non-atomic .config/.enable/.disable
>>    Don't modify PWM on .request/.free
>>    Check pwm_div underflow
>>    Fix various code and comment formatting issues
>> Other changes:
>>    Use u64 divisor safe division
>>    Remove now empty .request/.free
>> ---
>>   drivers/pwm/Kconfig   |  12 ++
>>   drivers/pwm/Makefile  |   1 +
>>   drivers/pwm/pwm-ipq.c | 281 ++++++++++++++++++++++++++++++++++++++++++
>>   3 files changed, 294 insertions(+)
>>   create mode 100644 drivers/pwm/pwm-ipq.c
>> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
>> index 21e3b05a5153..e39718137ecd 100644
>> --- a/drivers/pwm/Kconfig
>> +++ b/drivers/pwm/Kconfig
>> @@ -260,6 +260,18 @@ config PWM_INTEL_LGM
>>   	  To compile this driver as a module, choose M here: the module
>>   	  will be called pwm-intel-lgm.
>>   +config PWM_IPQ
>> +	tristate "IPQ PWM support"
>> +	depends on ARCH_QCOM || COMPILE_TEST
>> +	depends on HAVE_CLK && HAS_IOMEM
>> +	help
>> +	  Generic PWM framework driver for IPQ PWM block which supports
>> +	  4 pwm channels. Each of the these channels can be configured
>> +	  independent of each other.
>> +
>> +	  To compile this driver as a module, choose M here: the module
>> +	  will be called pwm-ipq.
>> +
>>   config PWM_IQS620A
>>   	tristate "Azoteq IQS620A PWM support"
>>   	depends on MFD_IQS62X || COMPILE_TEST
>> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
>> index 708840b7fba8..7402feae4b36 100644
>> --- a/drivers/pwm/Makefile
>> +++ b/drivers/pwm/Makefile
>> @@ -22,6 +22,7 @@ obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
>>   obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
>>   obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
>>   obj-$(CONFIG_PWM_INTEL_LGM)	+= pwm-intel-lgm.o
>> +obj-$(CONFIG_PWM_IPQ)		+= pwm-ipq.o
>>   obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
>>   obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>>   obj-$(CONFIG_PWM_KEEMBAY)	+= pwm-keembay.o
>> diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
>> new file mode 100644
>> index 000000000000..994027290bcb
>> --- /dev/null
>> +++ b/drivers/pwm/pwm-ipq.c
>> @@ -0,0 +1,281 @@
>> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
>> +/*
>> + * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pwm.h>
>> +#include <linux/clk.h>
>> +#include <linux/io.h>
>> +#include <linux/of.h>
>> +#include <linux/math64.h>
>> +#include <linux/of_device.h>
>> +#include <linux/bitfield.h>
>> +#include <linux/units.h>
>> +
>> +/* The frequency range supported is 1 Hz to clock rate */
>> +#define IPQ_PWM_MAX_PERIOD_NS	((u64)NSEC_PER_SEC)
>> +
>> +/*
>> + * The max value specified for each field is based on the number of bits
>> + * in the pwm control register for that field
>> + */
>> +#define IPQ_PWM_MAX_DIV		0xFFFF
>> +
>> +/*
>> + * Two 32-bit registers for each PWM: REG0, and REG1.
>> + * Base offset for PWM #i is at 8 * #i.
>> + */
>> +#define IPQ_PWM_REG0			0
>> +#define IPQ_PWM_REG0_PWM_DIV		GENMASK(15, 0)
>> +#define IPQ_PWM_REG0_HI_DURATION	GENMASK(31, 16)
>> +
>> +#define IPQ_PWM_REG1			4
>> +#define IPQ_PWM_REG1_PRE_DIV		GENMASK(15, 0)
>> +/*
>> + * Enable bit is set to enable output toggling in pwm device.
>> + * Update bit is set to reflect the changed divider and high duration
>> + * values in register.
>> + */
>> +#define IPQ_PWM_REG1_UPDATE		BIT(30)
>> +#define IPQ_PWM_REG1_ENABLE		BIT(31)
>> +
>> +
>> +struct ipq_pwm_chip {
>> +	struct pwm_chip chip;
>> +	struct clk *clk;
>> +	void __iomem *mem;
>> +};
>> +
>> +static struct ipq_pwm_chip *ipq_pwm_from_chip(struct pwm_chip *chip)
>> +{
>> +	return container_of(chip, struct ipq_pwm_chip, chip);
>> +}
>> +
>> +static unsigned int ipq_pwm_reg_read(struct pwm_device *pwm, unsigned int reg)
>> +{
>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
>> +	unsigned int off = 8 * pwm->hwpwm + reg;
>> +
>> +	return readl(ipq_chip->mem + off);
>> +}
>> +
>> +static void ipq_pwm_reg_write(struct pwm_device *pwm, unsigned int reg,
>> +			      unsigned int val)
>> +{
>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
>> +	unsigned int off = 8 * pwm->hwpwm + reg;
>> +
>> +	writel(val, ipq_chip->mem + off);
>> +}
>> +
>> +static void config_div_and_duty(struct pwm_device *pwm, unsigned int pre_div,
>> +			unsigned int pwm_div, unsigned long rate, u64 duty_ns,
>> +			bool enable)
>> +{
>> +	unsigned long hi_dur;
>> +	unsigned long val = 0;
>> +
>> +	/*
>> +	 * high duration = pwm duty * (pwm div + 1)
>> +	 * pwm duty = duty_ns / period_ns
>> +	 */
>> +	hi_dur = div64_u64(duty_ns * rate, (pre_div + 1) * NSEC_PER_SEC);
>> +
>> +	val = FIELD_PREP(IPQ_PWM_REG0_HI_DURATION, hi_dur) |
>> +		FIELD_PREP(IPQ_PWM_REG0_PWM_DIV, pwm_div);
>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG0, val);
>> +
>> +	val = FIELD_PREP(IPQ_PWM_REG1_PRE_DIV, pre_div);
>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
>> +
>> +	/* PWM enable toggle needs a separate write to REG1 */
>> +	val |= IPQ_PWM_REG1_UPDATE;
>> +	if (enable)
>> +		val |= IPQ_PWM_REG1_ENABLE;
>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
>> +}
>> +
>> +static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
>> +			 const struct pwm_state *state)
>> +{
>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
>> +	unsigned int pre_div, pwm_div, best_pre_div, best_pwm_div;
>> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
>> +	u64 period_ns, duty_ns, period_rate;
>> +	u64 min_diff;
>> +
>> +	if (state->polarity != PWM_POLARITY_NORMAL)
>> +		return -EINVAL;
>> +
>> +	if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC, rate))
>> +		return -ERANGE;
>> +
>> +	period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
>> +	duty_ns = min(state->duty_cycle, period_ns);
>> +
>> +	/*
>> +	 * period_ns is 1G or less. As long as rate is less than 16 GHz,
>> +	 * period_rate does not overflow. Make that explicit.
>> +	 */
>> +	if (rate > 16ULL * GIGA)
>> +		return -EINVAL;
>> +	period_rate = period_ns * rate;
>> +	best_pre_div = IPQ_PWM_MAX_DIV;
>> +	best_pwm_div = IPQ_PWM_MAX_DIV;
>> +	/*
>> +	 * We don't need to consider pre_div values smaller than
>> +	 *
>> +	 *                              period_rate
>> +	 *  pre_div_min := ------------------------------------
>> +	 *                 NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1)
>> +	 *
>> +	 * because pre_div = pre_div_min results in a better
>> +	 * approximation.
>> +	 */
>> +	pre_div = div64_u64(period_rate,
>> +			(u64)NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1));
>> +	min_diff = period_rate;
>> +
>> +	for (; pre_div <= IPQ_PWM_MAX_DIV; pre_div++) {
>> +		u64 remainder;
>> +
>> +		pwm_div = div64_u64_rem(period_rate,
>> +				(u64)NSEC_PER_SEC * (pre_div + 1), &remainder);
>> +		/* pwm_div is unsigned; the check below catches underflow */
>> +		pwm_div--;
>> +
>> +		/*
>> +		 * Swapping values for pre_div and pwm_div produces the same
>> +		 * period length. So we can skip all settings with pre_div >
>> +		 * pwm_div which results in bigger constraints for selecting
>> +		 * the duty_cycle than with the two values swapped.
>> +		 */
>> +		if (pre_div > pwm_div)
>> +			break;
>> +
>> +		/*
>> +		 * Make sure we can do 100% duty cycle where
>> +		 * hi_dur == pwm_div + 1
>> +		 */
>> +		if (pwm_div > IPQ_PWM_MAX_DIV - 1)
>> +			continue;
>> +
>> +		if (remainder < min_diff) {
>> +			best_pre_div = pre_div;
>> +			best_pwm_div = pwm_div;
>> +			min_diff = remainder;
>> +
>> +			if (min_diff == 0) /* bingo */
>> +				break;
>> +		}
>> +	}
>> +
>> +	/* config divider values for the closest possible frequency */
>> +	config_div_and_duty(pwm, best_pre_div, best_pwm_div,
>> +			    rate, duty_ns, state->enabled);
>> +
>> +	return 0;
>> +}
>> +
>> +static void ipq_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
>> +			      struct pwm_state *state)
>> +{
>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
>> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
>> +	unsigned int pre_div, pwm_div, hi_dur;
>> +	u64 effective_div, hi_div;
>> +	u32 reg0, reg1;
>> +
>> +	reg0 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG0);
>> +	reg1 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG1);
>> +
>> +	state->polarity = PWM_POLARITY_NORMAL;
>> +	state->enabled = reg1 & IPQ_PWM_REG1_ENABLE;
>> +
>> +	pwm_div = FIELD_GET(IPQ_PWM_REG0_PWM_DIV, reg0);
>> +	hi_dur = FIELD_GET(IPQ_PWM_REG0_HI_DURATION, reg0);
>> +	pre_div = FIELD_GET(IPQ_PWM_REG1_PRE_DIV, reg1);
>> +
>> +	/* No overflow here, both pre_div and pwm_div <= 0xffff */
>> +	effective_div = (u64)(pre_div + 1) * (pwm_div + 1);
>> +	state->period = DIV64_U64_ROUND_UP(effective_div * NSEC_PER_SEC, rate);
>> +
>> +	hi_div = hi_dur * (pre_div + 1);
>> +	state->duty_cycle = DIV64_U64_ROUND_UP(hi_div * NSEC_PER_SEC, rate);
>> +}
>> +
>> +static const struct pwm_ops ipq_pwm_ops = {
>> +	.apply = ipq_pwm_apply,
>> +	.get_state = ipq_pwm_get_state,
>> +	.owner = THIS_MODULE,
>> +};
>> +
>> +static int ipq_pwm_probe(struct platform_device *pdev)
>> +{
>> +	struct ipq_pwm_chip *pwm;
>> +	struct device *dev = &pdev->dev;
>> +	int ret;
>> +
>> +	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
>> +	if (!pwm)
>> +		return -ENOMEM;
>> +
>> +	platform_set_drvdata(pdev, pwm);
>> +
>> +	pwm->mem = devm_platform_ioremap_resource(pdev, 0);
>> +	if (IS_ERR(pwm->mem))
>> +		return dev_err_probe(dev, PTR_ERR(pwm->mem),
>> +				"regs map failed");
>> +
>> +	pwm->clk = devm_clk_get(dev, NULL);
>> +	if (IS_ERR(pwm->clk))
>> +		return dev_err_probe(dev, PTR_ERR(pwm->clk),
>> +				"failed to get clock");
>> +
>> +	ret = clk_prepare_enable(pwm->clk);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret, "clock enable failed");
>> +
>> +	pwm->chip.dev = dev;
>> +	pwm->chip.ops = &ipq_pwm_ops;
>> +	pwm->chip.npwm = 4;
>> +
>> +	ret = pwmchip_add(&pwm->chip);
>> +	if (ret < 0) {
>> +		dev_err_probe(dev, ret, "pwmchip_add() failed\n");
>> +		clk_disable_unprepare(pwm->clk);
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static int ipq_pwm_remove(struct platform_device *pdev)
>> +{
>> +	struct ipq_pwm_chip *pwm = platform_get_drvdata(pdev);
>> +
>> +	pwmchip_remove(&pwm->chip);
>> +	clk_disable_unprepare(pwm->clk);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id pwm_ipq_dt_match[] = {
>> +	{ .compatible = "qcom,ipq6018-pwm", },
>> +	{}
>> +};
>> +MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match);
>> +
>> +static struct platform_driver ipq_pwm_driver = {
>> +	.driver = {
>> +		.name = "ipq-pwm",
>> +		.of_match_table = pwm_ipq_dt_match,
>> +	},
>> +	.probe = ipq_pwm_probe,
>> +	.remove = ipq_pwm_remove,
>> +};
>> +
>> +module_platform_driver(ipq_pwm_driver);
>> +
>> +MODULE_LICENSE("Dual BSD/GPL");


-- 
                                                     ~. .~   Tk Open Systems
=}------------------------------------------------ooO--U--Ooo------------{=
   - baruch@tkos.co.il - tel: +972.52.368.4656, http://www.tkos.co.il -

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
@ 2023-09-15  6:36     ` Baruch Siach
  0 siblings, 0 replies; 34+ messages in thread
From: Baruch Siach @ 2023-09-15  6:36 UTC (permalink / raw)
  To: Devi Priya
  Cc: Thierry Reding, Uwe Kleine-König, Andy Gross,
	Bjorn Andersson, Rob Herring, Robert Marko, Kathiravan T,
	linux-pwm, devicetree, linux-arm-msm, linux-arm-kernel

Hi Devi,

[ Dropped/updated codeaurora.org addresses ]

On Fri, Sep 15 2023, Devi Priya wrote:
> On 2/7/2022 3:00 PM, Baruch Siach wrote:
>> From: Baruch Siach <baruch.siach@siklu.com>
>> Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
>> driver from downstream Codeaurora kernel tree. Removed support for older
>> (V1) variants because I have no access to that hardware.
>> Tested on IPQ6010 based hardware.
>> Signed-off-by: Baruch Siach <baruch.siach@siklu.com>
>> ---
>> v11:
>
> Just curious to know if you have plans to post the next revision!

I have been waiting for comments from pwm maintainers before sending the
next revision.

Unfortunately since then I lost access to the hardware, so I can't test
suggested implementation changes.  The only pending issue in v11 is the
trivial change that Nathan Chancellor suggested, which should be safe.

If you like to take it from here you are welcome.

Thanks,
baruch

>> Address comment from Uwe Kleine-König:
>>    Drop redundant registers field comments
>>    Fix period limit check in .apply
>>    Clarify the comment explaining skip of pre_div > pwm_div values
>>    Add explicit check for clock rate within limit
>>    Add comment explaining the selection of initial pre_div
>>    Use pwm_div division with remainder instead of separate diff calculation
>>    Round up duty_cycle calculation in .get_state
>> v10:
>>    Restore round up in pwm_div calculation; otherwise diff is always <=
>>    0, so only bingo match works
>>    Don't overwrite min_diff on every loop iteration
>> v9:
>> Address comment from Uwe Kleine-König:
>>    Use period_ns*rate in dividers calculation for better accuracy
>>    Round down pre_div and pwm_div
>>    Add a comment explaining why pwm_div can't underflow
>>    Add a comment explaining why pre_div > pwm_div end the search loop
>>    Drop 'CFG_' from register macros
>>    Rename to_ipq_pwm_chip() to ipq_pwm_from_chip()
>>    Change bare 'unsigned' to 'unsigned int'
>>    Clarify the comment on separate REG1 write for enable/disable
>>    Round up the period value in .get_state
>>    Use direct readl/writel so no need to check for regmap errors
>> v7:
>>    Change 'offset' to 'reg' for the tcsr offset (Rob)
>>    Drop clock name; there is only one clock (Bjorn)
>>    Simplify probe failure code path (Bjorn)
>> v6:
>> Address Uwe Kleine-König review comments:
>>    Drop IPQ_PWM_MAX_DEVICES
>>    Rely on assigned-clock-rates; drop IPQ_PWM_CLK_SRC_FREQ
>>    Simplify register offset calculation
>>    Calculate duty cycle more precisely
>>    Refuse to set inverted polarity
>>    Drop redundant IPQ_PWM_REG1_ENABLE bit clear
>>    Remove x1000 factor in pwm_div calculation, use rate directly, and round
>> up
>>    Choose initial pre_div such that pwm_div < IPQ_PWM_MAX_DIV
>>    Ensure pre_div <= pwm_div
>>    Rename close_ to best_
>>    Explain in comment why effective_div doesn't overflow
>>    Limit pwm_div to IPQ_PWM_MAX_DIV - 1 to allow 100% duty cycle
>>    Disable clock only after pwmchip_remove()
>>    const pwm_ops
>> Other changes:
>>    Add missing linux/bitfield.h header include (kernel test robot)
>>    Adjust code for PWM device node under TCSR (Rob Herring)
>> v5:
>> Use &tcsr_q6 syscon to access registers (Bjorn Andersson)
>> Address Uwe Kleine-König review comments:
>>    Implement .get_state()
>>    Add IPQ_PWM_ prefix to local macros
>>    Use GENMASK/BIT/FIELD_PREP for register fields access
>>    Make type of config_div_and_duty() parameters consistent
>>    Derive IPQ_PWM_MIN_PERIOD_NS from IPQ_PWM_CLK_SRC_FREQ
>>    Integrate enable/disable into config_div_and_duty() to save register
>> read,
>>    and reduce frequency glitch on update
>>    Use min() instead of min_t()
>>    Fix comment format
>>    Use dev_err_probe() to indicate probe step failure
>>    Add missing clk_disable_unprepare() in .remove
>>    Don't set .owner
>> v4:
>>    Use div64_u64() to fix link for 32-bit targets ((kernel test robot
>>    <lkp@intel.com>, Uwe Kleine-König)
>> v3:
>>    s/qcom,pwm-ipq6018/qcom,ipq6018-pwm/ (Rob Herring)
>>    Fix integer overflow on 32-bit targets (kernel test robot
>> <lkp@intel.com>)
>> v2:
>> Address Uwe Kleine-König review comments:
>>    Fix period calculation when out of range
>>    Don't set period larger than requested
>>    Remove PWM disable on configuration change
>>    Implement .apply instead of non-atomic .config/.enable/.disable
>>    Don't modify PWM on .request/.free
>>    Check pwm_div underflow
>>    Fix various code and comment formatting issues
>> Other changes:
>>    Use u64 divisor safe division
>>    Remove now empty .request/.free
>> ---
>>   drivers/pwm/Kconfig   |  12 ++
>>   drivers/pwm/Makefile  |   1 +
>>   drivers/pwm/pwm-ipq.c | 281 ++++++++++++++++++++++++++++++++++++++++++
>>   3 files changed, 294 insertions(+)
>>   create mode 100644 drivers/pwm/pwm-ipq.c
>> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
>> index 21e3b05a5153..e39718137ecd 100644
>> --- a/drivers/pwm/Kconfig
>> +++ b/drivers/pwm/Kconfig
>> @@ -260,6 +260,18 @@ config PWM_INTEL_LGM
>>   	  To compile this driver as a module, choose M here: the module
>>   	  will be called pwm-intel-lgm.
>>   +config PWM_IPQ
>> +	tristate "IPQ PWM support"
>> +	depends on ARCH_QCOM || COMPILE_TEST
>> +	depends on HAVE_CLK && HAS_IOMEM
>> +	help
>> +	  Generic PWM framework driver for IPQ PWM block which supports
>> +	  4 pwm channels. Each of the these channels can be configured
>> +	  independent of each other.
>> +
>> +	  To compile this driver as a module, choose M here: the module
>> +	  will be called pwm-ipq.
>> +
>>   config PWM_IQS620A
>>   	tristate "Azoteq IQS620A PWM support"
>>   	depends on MFD_IQS62X || COMPILE_TEST
>> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
>> index 708840b7fba8..7402feae4b36 100644
>> --- a/drivers/pwm/Makefile
>> +++ b/drivers/pwm/Makefile
>> @@ -22,6 +22,7 @@ obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
>>   obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
>>   obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
>>   obj-$(CONFIG_PWM_INTEL_LGM)	+= pwm-intel-lgm.o
>> +obj-$(CONFIG_PWM_IPQ)		+= pwm-ipq.o
>>   obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
>>   obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>>   obj-$(CONFIG_PWM_KEEMBAY)	+= pwm-keembay.o
>> diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
>> new file mode 100644
>> index 000000000000..994027290bcb
>> --- /dev/null
>> +++ b/drivers/pwm/pwm-ipq.c
>> @@ -0,0 +1,281 @@
>> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
>> +/*
>> + * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/pwm.h>
>> +#include <linux/clk.h>
>> +#include <linux/io.h>
>> +#include <linux/of.h>
>> +#include <linux/math64.h>
>> +#include <linux/of_device.h>
>> +#include <linux/bitfield.h>
>> +#include <linux/units.h>
>> +
>> +/* The frequency range supported is 1 Hz to clock rate */
>> +#define IPQ_PWM_MAX_PERIOD_NS	((u64)NSEC_PER_SEC)
>> +
>> +/*
>> + * The max value specified for each field is based on the number of bits
>> + * in the pwm control register for that field
>> + */
>> +#define IPQ_PWM_MAX_DIV		0xFFFF
>> +
>> +/*
>> + * Two 32-bit registers for each PWM: REG0, and REG1.
>> + * Base offset for PWM #i is at 8 * #i.
>> + */
>> +#define IPQ_PWM_REG0			0
>> +#define IPQ_PWM_REG0_PWM_DIV		GENMASK(15, 0)
>> +#define IPQ_PWM_REG0_HI_DURATION	GENMASK(31, 16)
>> +
>> +#define IPQ_PWM_REG1			4
>> +#define IPQ_PWM_REG1_PRE_DIV		GENMASK(15, 0)
>> +/*
>> + * Enable bit is set to enable output toggling in pwm device.
>> + * Update bit is set to reflect the changed divider and high duration
>> + * values in register.
>> + */
>> +#define IPQ_PWM_REG1_UPDATE		BIT(30)
>> +#define IPQ_PWM_REG1_ENABLE		BIT(31)
>> +
>> +
>> +struct ipq_pwm_chip {
>> +	struct pwm_chip chip;
>> +	struct clk *clk;
>> +	void __iomem *mem;
>> +};
>> +
>> +static struct ipq_pwm_chip *ipq_pwm_from_chip(struct pwm_chip *chip)
>> +{
>> +	return container_of(chip, struct ipq_pwm_chip, chip);
>> +}
>> +
>> +static unsigned int ipq_pwm_reg_read(struct pwm_device *pwm, unsigned int reg)
>> +{
>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
>> +	unsigned int off = 8 * pwm->hwpwm + reg;
>> +
>> +	return readl(ipq_chip->mem + off);
>> +}
>> +
>> +static void ipq_pwm_reg_write(struct pwm_device *pwm, unsigned int reg,
>> +			      unsigned int val)
>> +{
>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
>> +	unsigned int off = 8 * pwm->hwpwm + reg;
>> +
>> +	writel(val, ipq_chip->mem + off);
>> +}
>> +
>> +static void config_div_and_duty(struct pwm_device *pwm, unsigned int pre_div,
>> +			unsigned int pwm_div, unsigned long rate, u64 duty_ns,
>> +			bool enable)
>> +{
>> +	unsigned long hi_dur;
>> +	unsigned long val = 0;
>> +
>> +	/*
>> +	 * high duration = pwm duty * (pwm div + 1)
>> +	 * pwm duty = duty_ns / period_ns
>> +	 */
>> +	hi_dur = div64_u64(duty_ns * rate, (pre_div + 1) * NSEC_PER_SEC);
>> +
>> +	val = FIELD_PREP(IPQ_PWM_REG0_HI_DURATION, hi_dur) |
>> +		FIELD_PREP(IPQ_PWM_REG0_PWM_DIV, pwm_div);
>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG0, val);
>> +
>> +	val = FIELD_PREP(IPQ_PWM_REG1_PRE_DIV, pre_div);
>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
>> +
>> +	/* PWM enable toggle needs a separate write to REG1 */
>> +	val |= IPQ_PWM_REG1_UPDATE;
>> +	if (enable)
>> +		val |= IPQ_PWM_REG1_ENABLE;
>> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
>> +}
>> +
>> +static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
>> +			 const struct pwm_state *state)
>> +{
>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
>> +	unsigned int pre_div, pwm_div, best_pre_div, best_pwm_div;
>> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
>> +	u64 period_ns, duty_ns, period_rate;
>> +	u64 min_diff;
>> +
>> +	if (state->polarity != PWM_POLARITY_NORMAL)
>> +		return -EINVAL;
>> +
>> +	if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC, rate))
>> +		return -ERANGE;
>> +
>> +	period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
>> +	duty_ns = min(state->duty_cycle, period_ns);
>> +
>> +	/*
>> +	 * period_ns is 1G or less. As long as rate is less than 16 GHz,
>> +	 * period_rate does not overflow. Make that explicit.
>> +	 */
>> +	if (rate > 16ULL * GIGA)
>> +		return -EINVAL;
>> +	period_rate = period_ns * rate;
>> +	best_pre_div = IPQ_PWM_MAX_DIV;
>> +	best_pwm_div = IPQ_PWM_MAX_DIV;
>> +	/*
>> +	 * We don't need to consider pre_div values smaller than
>> +	 *
>> +	 *                              period_rate
>> +	 *  pre_div_min := ------------------------------------
>> +	 *                 NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1)
>> +	 *
>> +	 * because pre_div = pre_div_min results in a better
>> +	 * approximation.
>> +	 */
>> +	pre_div = div64_u64(period_rate,
>> +			(u64)NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1));
>> +	min_diff = period_rate;
>> +
>> +	for (; pre_div <= IPQ_PWM_MAX_DIV; pre_div++) {
>> +		u64 remainder;
>> +
>> +		pwm_div = div64_u64_rem(period_rate,
>> +				(u64)NSEC_PER_SEC * (pre_div + 1), &remainder);
>> +		/* pwm_div is unsigned; the check below catches underflow */
>> +		pwm_div--;
>> +
>> +		/*
>> +		 * Swapping values for pre_div and pwm_div produces the same
>> +		 * period length. So we can skip all settings with pre_div >
>> +		 * pwm_div which results in bigger constraints for selecting
>> +		 * the duty_cycle than with the two values swapped.
>> +		 */
>> +		if (pre_div > pwm_div)
>> +			break;
>> +
>> +		/*
>> +		 * Make sure we can do 100% duty cycle where
>> +		 * hi_dur == pwm_div + 1
>> +		 */
>> +		if (pwm_div > IPQ_PWM_MAX_DIV - 1)
>> +			continue;
>> +
>> +		if (remainder < min_diff) {
>> +			best_pre_div = pre_div;
>> +			best_pwm_div = pwm_div;
>> +			min_diff = remainder;
>> +
>> +			if (min_diff == 0) /* bingo */
>> +				break;
>> +		}
>> +	}
>> +
>> +	/* config divider values for the closest possible frequency */
>> +	config_div_and_duty(pwm, best_pre_div, best_pwm_div,
>> +			    rate, duty_ns, state->enabled);
>> +
>> +	return 0;
>> +}
>> +
>> +static void ipq_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
>> +			      struct pwm_state *state)
>> +{
>> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
>> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
>> +	unsigned int pre_div, pwm_div, hi_dur;
>> +	u64 effective_div, hi_div;
>> +	u32 reg0, reg1;
>> +
>> +	reg0 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG0);
>> +	reg1 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG1);
>> +
>> +	state->polarity = PWM_POLARITY_NORMAL;
>> +	state->enabled = reg1 & IPQ_PWM_REG1_ENABLE;
>> +
>> +	pwm_div = FIELD_GET(IPQ_PWM_REG0_PWM_DIV, reg0);
>> +	hi_dur = FIELD_GET(IPQ_PWM_REG0_HI_DURATION, reg0);
>> +	pre_div = FIELD_GET(IPQ_PWM_REG1_PRE_DIV, reg1);
>> +
>> +	/* No overflow here, both pre_div and pwm_div <= 0xffff */
>> +	effective_div = (u64)(pre_div + 1) * (pwm_div + 1);
>> +	state->period = DIV64_U64_ROUND_UP(effective_div * NSEC_PER_SEC, rate);
>> +
>> +	hi_div = hi_dur * (pre_div + 1);
>> +	state->duty_cycle = DIV64_U64_ROUND_UP(hi_div * NSEC_PER_SEC, rate);
>> +}
>> +
>> +static const struct pwm_ops ipq_pwm_ops = {
>> +	.apply = ipq_pwm_apply,
>> +	.get_state = ipq_pwm_get_state,
>> +	.owner = THIS_MODULE,
>> +};
>> +
>> +static int ipq_pwm_probe(struct platform_device *pdev)
>> +{
>> +	struct ipq_pwm_chip *pwm;
>> +	struct device *dev = &pdev->dev;
>> +	int ret;
>> +
>> +	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
>> +	if (!pwm)
>> +		return -ENOMEM;
>> +
>> +	platform_set_drvdata(pdev, pwm);
>> +
>> +	pwm->mem = devm_platform_ioremap_resource(pdev, 0);
>> +	if (IS_ERR(pwm->mem))
>> +		return dev_err_probe(dev, PTR_ERR(pwm->mem),
>> +				"regs map failed");
>> +
>> +	pwm->clk = devm_clk_get(dev, NULL);
>> +	if (IS_ERR(pwm->clk))
>> +		return dev_err_probe(dev, PTR_ERR(pwm->clk),
>> +				"failed to get clock");
>> +
>> +	ret = clk_prepare_enable(pwm->clk);
>> +	if (ret)
>> +		return dev_err_probe(dev, ret, "clock enable failed");
>> +
>> +	pwm->chip.dev = dev;
>> +	pwm->chip.ops = &ipq_pwm_ops;
>> +	pwm->chip.npwm = 4;
>> +
>> +	ret = pwmchip_add(&pwm->chip);
>> +	if (ret < 0) {
>> +		dev_err_probe(dev, ret, "pwmchip_add() failed\n");
>> +		clk_disable_unprepare(pwm->clk);
>> +	}
>> +
>> +	return ret;
>> +}
>> +
>> +static int ipq_pwm_remove(struct platform_device *pdev)
>> +{
>> +	struct ipq_pwm_chip *pwm = platform_get_drvdata(pdev);
>> +
>> +	pwmchip_remove(&pwm->chip);
>> +	clk_disable_unprepare(pwm->clk);
>> +
>> +	return 0;
>> +}
>> +
>> +static const struct of_device_id pwm_ipq_dt_match[] = {
>> +	{ .compatible = "qcom,ipq6018-pwm", },
>> +	{}
>> +};
>> +MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match);
>> +
>> +static struct platform_driver ipq_pwm_driver = {
>> +	.driver = {
>> +		.name = "ipq-pwm",
>> +		.of_match_table = pwm_ipq_dt_match,
>> +	},
>> +	.probe = ipq_pwm_probe,
>> +	.remove = ipq_pwm_remove,
>> +};
>> +
>> +module_platform_driver(ipq_pwm_driver);
>> +
>> +MODULE_LICENSE("Dual BSD/GPL");


-- 
                                                     ~. .~   Tk Open Systems
=}------------------------------------------------ooO--U--Ooo------------{=
   - baruch@tkos.co.il - tel: +972.52.368.4656, http://www.tkos.co.il -

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
  2022-02-07  9:30 ` Baruch Siach
@ 2023-09-15  6:25   ` Devi Priya
  -1 siblings, 0 replies; 34+ messages in thread
From: Devi Priya @ 2023-09-15  6:25 UTC (permalink / raw)
  To: Baruch Siach, Thierry Reding, Uwe Kleine-König, Andy Gross,
	Bjorn Andersson
  Cc: Baruch Siach, Balaji Prakash J, Rob Herring, Robert Marko,
	Kathiravan T, linux-pwm, devicetree, linux-arm-msm,
	linux-arm-kernel



On 2/7/2022 3:00 PM, Baruch Siach wrote:
> From: Baruch Siach <baruch.siach@siklu.com>
> 
> Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
> driver from downstream Codeaurora kernel tree. Removed support for older
> (V1) variants because I have no access to that hardware.
> 
> Tested on IPQ6010 based hardware.
> 
> Signed-off-by: Baruch Siach <baruch.siach@siklu.com>
> ---
> v11:
> 
Hi Baruch,

Just curious to know if you have plans to post the next revision!

Thanks,
Devi Priya

> Address comment from Uwe Kleine-König:
> 
>    Drop redundant registers field comments
> 
>    Fix period limit check in .apply
> 
>    Clarify the comment explaining skip of pre_div > pwm_div values
> 
>    Add explicit check for clock rate within limit
> 
>    Add comment explaining the selection of initial pre_div
> 
>    Use pwm_div division with remainder instead of separate diff calculation
> 
>    Round up duty_cycle calculation in .get_state
> 
> v10:
> 
>    Restore round up in pwm_div calculation; otherwise diff is always <=
>    0, so only bingo match works
> 
>    Don't overwrite min_diff on every loop iteration
> 
> v9:
> 
> Address comment from Uwe Kleine-König:
> 
>    Use period_ns*rate in dividers calculation for better accuracy
> 
>    Round down pre_div and pwm_div
> 
>    Add a comment explaining why pwm_div can't underflow
> 
>    Add a comment explaining why pre_div > pwm_div end the search loop
> 
>    Drop 'CFG_' from register macros
> 
>    Rename to_ipq_pwm_chip() to ipq_pwm_from_chip()
> 
>    Change bare 'unsigned' to 'unsigned int'
> 
>    Clarify the comment on separate REG1 write for enable/disable
> 
>    Round up the period value in .get_state
> 
>    Use direct readl/writel so no need to check for regmap errors
> 
> v7:
> 
>    Change 'offset' to 'reg' for the tcsr offset (Rob)
> 
>    Drop clock name; there is only one clock (Bjorn)
> 
>    Simplify probe failure code path (Bjorn)
> 
> v6:
> 
> Address Uwe Kleine-König review comments:
> 
>    Drop IPQ_PWM_MAX_DEVICES
> 
>    Rely on assigned-clock-rates; drop IPQ_PWM_CLK_SRC_FREQ
> 
>    Simplify register offset calculation
> 
>    Calculate duty cycle more precisely
> 
>    Refuse to set inverted polarity
> 
>    Drop redundant IPQ_PWM_REG1_ENABLE bit clear
> 
>    Remove x1000 factor in pwm_div calculation, use rate directly, and round up
> 
>    Choose initial pre_div such that pwm_div < IPQ_PWM_MAX_DIV
> 
>    Ensure pre_div <= pwm_div
> 
>    Rename close_ to best_
> 
>    Explain in comment why effective_div doesn't overflow
> 
>    Limit pwm_div to IPQ_PWM_MAX_DIV - 1 to allow 100% duty cycle
> 
>    Disable clock only after pwmchip_remove()
> 
>    const pwm_ops
> 
> Other changes:
> 
>    Add missing linux/bitfield.h header include (kernel test robot)
> 
>    Adjust code for PWM device node under TCSR (Rob Herring)
> 
> v5:
> 
> Use &tcsr_q6 syscon to access registers (Bjorn Andersson)
> 
> Address Uwe Kleine-König review comments:
> 
>    Implement .get_state()
> 
>    Add IPQ_PWM_ prefix to local macros
> 
>    Use GENMASK/BIT/FIELD_PREP for register fields access
> 
>    Make type of config_div_and_duty() parameters consistent
> 
>    Derive IPQ_PWM_MIN_PERIOD_NS from IPQ_PWM_CLK_SRC_FREQ
> 
>    Integrate enable/disable into config_div_and_duty() to save register read,
>    and reduce frequency glitch on update
> 
>    Use min() instead of min_t()
> 
>    Fix comment format
> 
>    Use dev_err_probe() to indicate probe step failure
> 
>    Add missing clk_disable_unprepare() in .remove
> 
>    Don't set .owner
> 
> v4:
> 
>    Use div64_u64() to fix link for 32-bit targets ((kernel test robot
>    <lkp@intel.com>, Uwe Kleine-König)
> 
> v3:
> 
>    s/qcom,pwm-ipq6018/qcom,ipq6018-pwm/ (Rob Herring)
> 
>    Fix integer overflow on 32-bit targets (kernel test robot <lkp@intel.com>)
> 
> v2:
> 
> Address Uwe Kleine-König review comments:
> 
>    Fix period calculation when out of range
> 
>    Don't set period larger than requested
> 
>    Remove PWM disable on configuration change
> 
>    Implement .apply instead of non-atomic .config/.enable/.disable
> 
>    Don't modify PWM on .request/.free
> 
>    Check pwm_div underflow
> 
>    Fix various code and comment formatting issues
> 
> Other changes:
> 
>    Use u64 divisor safe division
> 
>    Remove now empty .request/.free
> ---
>   drivers/pwm/Kconfig   |  12 ++
>   drivers/pwm/Makefile  |   1 +
>   drivers/pwm/pwm-ipq.c | 281 ++++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 294 insertions(+)
>   create mode 100644 drivers/pwm/pwm-ipq.c
> 
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 21e3b05a5153..e39718137ecd 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -260,6 +260,18 @@ config PWM_INTEL_LGM
>   	  To compile this driver as a module, choose M here: the module
>   	  will be called pwm-intel-lgm.
>   
> +config PWM_IPQ
> +	tristate "IPQ PWM support"
> +	depends on ARCH_QCOM || COMPILE_TEST
> +	depends on HAVE_CLK && HAS_IOMEM
> +	help
> +	  Generic PWM framework driver for IPQ PWM block which supports
> +	  4 pwm channels. Each of the these channels can be configured
> +	  independent of each other.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called pwm-ipq.
> +
>   config PWM_IQS620A
>   	tristate "Azoteq IQS620A PWM support"
>   	depends on MFD_IQS62X || COMPILE_TEST
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 708840b7fba8..7402feae4b36 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -22,6 +22,7 @@ obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
>   obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
>   obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
>   obj-$(CONFIG_PWM_INTEL_LGM)	+= pwm-intel-lgm.o
> +obj-$(CONFIG_PWM_IPQ)		+= pwm-ipq.o
>   obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
>   obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>   obj-$(CONFIG_PWM_KEEMBAY)	+= pwm-keembay.o
> diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
> new file mode 100644
> index 000000000000..994027290bcb
> --- /dev/null
> +++ b/drivers/pwm/pwm-ipq.c
> @@ -0,0 +1,281 @@
> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
> +/*
> + * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +#include <linux/of.h>
> +#include <linux/math64.h>
> +#include <linux/of_device.h>
> +#include <linux/bitfield.h>
> +#include <linux/units.h>
> +
> +/* The frequency range supported is 1 Hz to clock rate */
> +#define IPQ_PWM_MAX_PERIOD_NS	((u64)NSEC_PER_SEC)
> +
> +/*
> + * The max value specified for each field is based on the number of bits
> + * in the pwm control register for that field
> + */
> +#define IPQ_PWM_MAX_DIV		0xFFFF
> +
> +/*
> + * Two 32-bit registers for each PWM: REG0, and REG1.
> + * Base offset for PWM #i is at 8 * #i.
> + */
> +#define IPQ_PWM_REG0			0
> +#define IPQ_PWM_REG0_PWM_DIV		GENMASK(15, 0)
> +#define IPQ_PWM_REG0_HI_DURATION	GENMASK(31, 16)
> +
> +#define IPQ_PWM_REG1			4
> +#define IPQ_PWM_REG1_PRE_DIV		GENMASK(15, 0)
> +/*
> + * Enable bit is set to enable output toggling in pwm device.
> + * Update bit is set to reflect the changed divider and high duration
> + * values in register.
> + */
> +#define IPQ_PWM_REG1_UPDATE		BIT(30)
> +#define IPQ_PWM_REG1_ENABLE		BIT(31)
> +
> +
> +struct ipq_pwm_chip {
> +	struct pwm_chip chip;
> +	struct clk *clk;
> +	void __iomem *mem;
> +};
> +
> +static struct ipq_pwm_chip *ipq_pwm_from_chip(struct pwm_chip *chip)
> +{
> +	return container_of(chip, struct ipq_pwm_chip, chip);
> +}
> +
> +static unsigned int ipq_pwm_reg_read(struct pwm_device *pwm, unsigned int reg)
> +{
> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
> +	unsigned int off = 8 * pwm->hwpwm + reg;
> +
> +	return readl(ipq_chip->mem + off);
> +}
> +
> +static void ipq_pwm_reg_write(struct pwm_device *pwm, unsigned int reg,
> +			      unsigned int val)
> +{
> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
> +	unsigned int off = 8 * pwm->hwpwm + reg;
> +
> +	writel(val, ipq_chip->mem + off);
> +}
> +
> +static void config_div_and_duty(struct pwm_device *pwm, unsigned int pre_div,
> +			unsigned int pwm_div, unsigned long rate, u64 duty_ns,
> +			bool enable)
> +{
> +	unsigned long hi_dur;
> +	unsigned long val = 0;
> +
> +	/*
> +	 * high duration = pwm duty * (pwm div + 1)
> +	 * pwm duty = duty_ns / period_ns
> +	 */
> +	hi_dur = div64_u64(duty_ns * rate, (pre_div + 1) * NSEC_PER_SEC);
> +
> +	val = FIELD_PREP(IPQ_PWM_REG0_HI_DURATION, hi_dur) |
> +		FIELD_PREP(IPQ_PWM_REG0_PWM_DIV, pwm_div);
> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG0, val);
> +
> +	val = FIELD_PREP(IPQ_PWM_REG1_PRE_DIV, pre_div);
> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
> +
> +	/* PWM enable toggle needs a separate write to REG1 */
> +	val |= IPQ_PWM_REG1_UPDATE;
> +	if (enable)
> +		val |= IPQ_PWM_REG1_ENABLE;
> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
> +}
> +
> +static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> +			 const struct pwm_state *state)
> +{
> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
> +	unsigned int pre_div, pwm_div, best_pre_div, best_pwm_div;
> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
> +	u64 period_ns, duty_ns, period_rate;
> +	u64 min_diff;
> +
> +	if (state->polarity != PWM_POLARITY_NORMAL)
> +		return -EINVAL;
> +
> +	if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC, rate))
> +		return -ERANGE;
> +
> +	period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
> +	duty_ns = min(state->duty_cycle, period_ns);
> +
> +	/*
> +	 * period_ns is 1G or less. As long as rate is less than 16 GHz,
> +	 * period_rate does not overflow. Make that explicit.
> +	 */
> +	if (rate > 16ULL * GIGA)
> +		return -EINVAL;
> +	period_rate = period_ns * rate;
> +	best_pre_div = IPQ_PWM_MAX_DIV;
> +	best_pwm_div = IPQ_PWM_MAX_DIV;
> +	/*
> +	 * We don't need to consider pre_div values smaller than
> +	 *
> +	 *                              period_rate
> +	 *  pre_div_min := ------------------------------------
> +	 *                 NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1)
> +	 *
> +	 * because pre_div = pre_div_min results in a better
> +	 * approximation.
> +	 */
> +	pre_div = div64_u64(period_rate,
> +			(u64)NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1));
> +	min_diff = period_rate;
> +
> +	for (; pre_div <= IPQ_PWM_MAX_DIV; pre_div++) {
> +		u64 remainder;
> +
> +		pwm_div = div64_u64_rem(period_rate,
> +				(u64)NSEC_PER_SEC * (pre_div + 1), &remainder);
> +		/* pwm_div is unsigned; the check below catches underflow */
> +		pwm_div--;
> +
> +		/*
> +		 * Swapping values for pre_div and pwm_div produces the same
> +		 * period length. So we can skip all settings with pre_div >
> +		 * pwm_div which results in bigger constraints for selecting
> +		 * the duty_cycle than with the two values swapped.
> +		 */
> +		if (pre_div > pwm_div)
> +			break;
> +
> +		/*
> +		 * Make sure we can do 100% duty cycle where
> +		 * hi_dur == pwm_div + 1
> +		 */
> +		if (pwm_div > IPQ_PWM_MAX_DIV - 1)
> +			continue;
> +
> +		if (remainder < min_diff) {
> +			best_pre_div = pre_div;
> +			best_pwm_div = pwm_div;
> +			min_diff = remainder;
> +
> +			if (min_diff == 0) /* bingo */
> +				break;
> +		}
> +	}
> +
> +	/* config divider values for the closest possible frequency */
> +	config_div_and_duty(pwm, best_pre_div, best_pwm_div,
> +			    rate, duty_ns, state->enabled);
> +
> +	return 0;
> +}
> +
> +static void ipq_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> +			      struct pwm_state *state)
> +{
> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
> +	unsigned int pre_div, pwm_div, hi_dur;
> +	u64 effective_div, hi_div;
> +	u32 reg0, reg1;
> +
> +	reg0 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG0);
> +	reg1 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG1);
> +
> +	state->polarity = PWM_POLARITY_NORMAL;
> +	state->enabled = reg1 & IPQ_PWM_REG1_ENABLE;
> +
> +	pwm_div = FIELD_GET(IPQ_PWM_REG0_PWM_DIV, reg0);
> +	hi_dur = FIELD_GET(IPQ_PWM_REG0_HI_DURATION, reg0);
> +	pre_div = FIELD_GET(IPQ_PWM_REG1_PRE_DIV, reg1);
> +
> +	/* No overflow here, both pre_div and pwm_div <= 0xffff */
> +	effective_div = (u64)(pre_div + 1) * (pwm_div + 1);
> +	state->period = DIV64_U64_ROUND_UP(effective_div * NSEC_PER_SEC, rate);
> +
> +	hi_div = hi_dur * (pre_div + 1);
> +	state->duty_cycle = DIV64_U64_ROUND_UP(hi_div * NSEC_PER_SEC, rate);
> +}
> +
> +static const struct pwm_ops ipq_pwm_ops = {
> +	.apply = ipq_pwm_apply,
> +	.get_state = ipq_pwm_get_state,
> +	.owner = THIS_MODULE,
> +};
> +
> +static int ipq_pwm_probe(struct platform_device *pdev)
> +{
> +	struct ipq_pwm_chip *pwm;
> +	struct device *dev = &pdev->dev;
> +	int ret;
> +
> +	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
> +	if (!pwm)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, pwm);
> +
> +	pwm->mem = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(pwm->mem))
> +		return dev_err_probe(dev, PTR_ERR(pwm->mem),
> +				"regs map failed");
> +
> +	pwm->clk = devm_clk_get(dev, NULL);
> +	if (IS_ERR(pwm->clk))
> +		return dev_err_probe(dev, PTR_ERR(pwm->clk),
> +				"failed to get clock");
> +
> +	ret = clk_prepare_enable(pwm->clk);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "clock enable failed");
> +
> +	pwm->chip.dev = dev;
> +	pwm->chip.ops = &ipq_pwm_ops;
> +	pwm->chip.npwm = 4;
> +
> +	ret = pwmchip_add(&pwm->chip);
> +	if (ret < 0) {
> +		dev_err_probe(dev, ret, "pwmchip_add() failed\n");
> +		clk_disable_unprepare(pwm->clk);
> +	}
> +
> +	return ret;
> +}
> +
> +static int ipq_pwm_remove(struct platform_device *pdev)
> +{
> +	struct ipq_pwm_chip *pwm = platform_get_drvdata(pdev);
> +
> +	pwmchip_remove(&pwm->chip);
> +	clk_disable_unprepare(pwm->clk);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id pwm_ipq_dt_match[] = {
> +	{ .compatible = "qcom,ipq6018-pwm", },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match);
> +
> +static struct platform_driver ipq_pwm_driver = {
> +	.driver = {
> +		.name = "ipq-pwm",
> +		.of_match_table = pwm_ipq_dt_match,
> +	},
> +	.probe = ipq_pwm_probe,
> +	.remove = ipq_pwm_remove,
> +};
> +
> +module_platform_driver(ipq_pwm_driver);
> +
> +MODULE_LICENSE("Dual BSD/GPL");

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
@ 2023-09-15  6:25   ` Devi Priya
  0 siblings, 0 replies; 34+ messages in thread
From: Devi Priya @ 2023-09-15  6:25 UTC (permalink / raw)
  To: Baruch Siach, Thierry Reding, Uwe Kleine-König, Andy Gross,
	Bjorn Andersson
  Cc: Baruch Siach, Balaji Prakash J, Rob Herring, Robert Marko,
	Kathiravan T, linux-pwm, devicetree, linux-arm-msm,
	linux-arm-kernel



On 2/7/2022 3:00 PM, Baruch Siach wrote:
> From: Baruch Siach <baruch.siach@siklu.com>
> 
> Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
> driver from downstream Codeaurora kernel tree. Removed support for older
> (V1) variants because I have no access to that hardware.
> 
> Tested on IPQ6010 based hardware.
> 
> Signed-off-by: Baruch Siach <baruch.siach@siklu.com>
> ---
> v11:
> 
Hi Baruch,

Just curious to know if you have plans to post the next revision!

Thanks,
Devi Priya

> Address comment from Uwe Kleine-König:
> 
>    Drop redundant registers field comments
> 
>    Fix period limit check in .apply
> 
>    Clarify the comment explaining skip of pre_div > pwm_div values
> 
>    Add explicit check for clock rate within limit
> 
>    Add comment explaining the selection of initial pre_div
> 
>    Use pwm_div division with remainder instead of separate diff calculation
> 
>    Round up duty_cycle calculation in .get_state
> 
> v10:
> 
>    Restore round up in pwm_div calculation; otherwise diff is always <=
>    0, so only bingo match works
> 
>    Don't overwrite min_diff on every loop iteration
> 
> v9:
> 
> Address comment from Uwe Kleine-König:
> 
>    Use period_ns*rate in dividers calculation for better accuracy
> 
>    Round down pre_div and pwm_div
> 
>    Add a comment explaining why pwm_div can't underflow
> 
>    Add a comment explaining why pre_div > pwm_div end the search loop
> 
>    Drop 'CFG_' from register macros
> 
>    Rename to_ipq_pwm_chip() to ipq_pwm_from_chip()
> 
>    Change bare 'unsigned' to 'unsigned int'
> 
>    Clarify the comment on separate REG1 write for enable/disable
> 
>    Round up the period value in .get_state
> 
>    Use direct readl/writel so no need to check for regmap errors
> 
> v7:
> 
>    Change 'offset' to 'reg' for the tcsr offset (Rob)
> 
>    Drop clock name; there is only one clock (Bjorn)
> 
>    Simplify probe failure code path (Bjorn)
> 
> v6:
> 
> Address Uwe Kleine-König review comments:
> 
>    Drop IPQ_PWM_MAX_DEVICES
> 
>    Rely on assigned-clock-rates; drop IPQ_PWM_CLK_SRC_FREQ
> 
>    Simplify register offset calculation
> 
>    Calculate duty cycle more precisely
> 
>    Refuse to set inverted polarity
> 
>    Drop redundant IPQ_PWM_REG1_ENABLE bit clear
> 
>    Remove x1000 factor in pwm_div calculation, use rate directly, and round up
> 
>    Choose initial pre_div such that pwm_div < IPQ_PWM_MAX_DIV
> 
>    Ensure pre_div <= pwm_div
> 
>    Rename close_ to best_
> 
>    Explain in comment why effective_div doesn't overflow
> 
>    Limit pwm_div to IPQ_PWM_MAX_DIV - 1 to allow 100% duty cycle
> 
>    Disable clock only after pwmchip_remove()
> 
>    const pwm_ops
> 
> Other changes:
> 
>    Add missing linux/bitfield.h header include (kernel test robot)
> 
>    Adjust code for PWM device node under TCSR (Rob Herring)
> 
> v5:
> 
> Use &tcsr_q6 syscon to access registers (Bjorn Andersson)
> 
> Address Uwe Kleine-König review comments:
> 
>    Implement .get_state()
> 
>    Add IPQ_PWM_ prefix to local macros
> 
>    Use GENMASK/BIT/FIELD_PREP for register fields access
> 
>    Make type of config_div_and_duty() parameters consistent
> 
>    Derive IPQ_PWM_MIN_PERIOD_NS from IPQ_PWM_CLK_SRC_FREQ
> 
>    Integrate enable/disable into config_div_and_duty() to save register read,
>    and reduce frequency glitch on update
> 
>    Use min() instead of min_t()
> 
>    Fix comment format
> 
>    Use dev_err_probe() to indicate probe step failure
> 
>    Add missing clk_disable_unprepare() in .remove
> 
>    Don't set .owner
> 
> v4:
> 
>    Use div64_u64() to fix link for 32-bit targets ((kernel test robot
>    <lkp@intel.com>, Uwe Kleine-König)
> 
> v3:
> 
>    s/qcom,pwm-ipq6018/qcom,ipq6018-pwm/ (Rob Herring)
> 
>    Fix integer overflow on 32-bit targets (kernel test robot <lkp@intel.com>)
> 
> v2:
> 
> Address Uwe Kleine-König review comments:
> 
>    Fix period calculation when out of range
> 
>    Don't set period larger than requested
> 
>    Remove PWM disable on configuration change
> 
>    Implement .apply instead of non-atomic .config/.enable/.disable
> 
>    Don't modify PWM on .request/.free
> 
>    Check pwm_div underflow
> 
>    Fix various code and comment formatting issues
> 
> Other changes:
> 
>    Use u64 divisor safe division
> 
>    Remove now empty .request/.free
> ---
>   drivers/pwm/Kconfig   |  12 ++
>   drivers/pwm/Makefile  |   1 +
>   drivers/pwm/pwm-ipq.c | 281 ++++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 294 insertions(+)
>   create mode 100644 drivers/pwm/pwm-ipq.c
> 
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index 21e3b05a5153..e39718137ecd 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -260,6 +260,18 @@ config PWM_INTEL_LGM
>   	  To compile this driver as a module, choose M here: the module
>   	  will be called pwm-intel-lgm.
>   
> +config PWM_IPQ
> +	tristate "IPQ PWM support"
> +	depends on ARCH_QCOM || COMPILE_TEST
> +	depends on HAVE_CLK && HAS_IOMEM
> +	help
> +	  Generic PWM framework driver for IPQ PWM block which supports
> +	  4 pwm channels. Each of the these channels can be configured
> +	  independent of each other.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called pwm-ipq.
> +
>   config PWM_IQS620A
>   	tristate "Azoteq IQS620A PWM support"
>   	depends on MFD_IQS62X || COMPILE_TEST
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 708840b7fba8..7402feae4b36 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -22,6 +22,7 @@ obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
>   obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
>   obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
>   obj-$(CONFIG_PWM_INTEL_LGM)	+= pwm-intel-lgm.o
> +obj-$(CONFIG_PWM_IPQ)		+= pwm-ipq.o
>   obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
>   obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>   obj-$(CONFIG_PWM_KEEMBAY)	+= pwm-keembay.o
> diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
> new file mode 100644
> index 000000000000..994027290bcb
> --- /dev/null
> +++ b/drivers/pwm/pwm-ipq.c
> @@ -0,0 +1,281 @@
> +// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
> +/*
> + * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/clk.h>
> +#include <linux/io.h>
> +#include <linux/of.h>
> +#include <linux/math64.h>
> +#include <linux/of_device.h>
> +#include <linux/bitfield.h>
> +#include <linux/units.h>
> +
> +/* The frequency range supported is 1 Hz to clock rate */
> +#define IPQ_PWM_MAX_PERIOD_NS	((u64)NSEC_PER_SEC)
> +
> +/*
> + * The max value specified for each field is based on the number of bits
> + * in the pwm control register for that field
> + */
> +#define IPQ_PWM_MAX_DIV		0xFFFF
> +
> +/*
> + * Two 32-bit registers for each PWM: REG0, and REG1.
> + * Base offset for PWM #i is at 8 * #i.
> + */
> +#define IPQ_PWM_REG0			0
> +#define IPQ_PWM_REG0_PWM_DIV		GENMASK(15, 0)
> +#define IPQ_PWM_REG0_HI_DURATION	GENMASK(31, 16)
> +
> +#define IPQ_PWM_REG1			4
> +#define IPQ_PWM_REG1_PRE_DIV		GENMASK(15, 0)
> +/*
> + * Enable bit is set to enable output toggling in pwm device.
> + * Update bit is set to reflect the changed divider and high duration
> + * values in register.
> + */
> +#define IPQ_PWM_REG1_UPDATE		BIT(30)
> +#define IPQ_PWM_REG1_ENABLE		BIT(31)
> +
> +
> +struct ipq_pwm_chip {
> +	struct pwm_chip chip;
> +	struct clk *clk;
> +	void __iomem *mem;
> +};
> +
> +static struct ipq_pwm_chip *ipq_pwm_from_chip(struct pwm_chip *chip)
> +{
> +	return container_of(chip, struct ipq_pwm_chip, chip);
> +}
> +
> +static unsigned int ipq_pwm_reg_read(struct pwm_device *pwm, unsigned int reg)
> +{
> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
> +	unsigned int off = 8 * pwm->hwpwm + reg;
> +
> +	return readl(ipq_chip->mem + off);
> +}
> +
> +static void ipq_pwm_reg_write(struct pwm_device *pwm, unsigned int reg,
> +			      unsigned int val)
> +{
> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
> +	unsigned int off = 8 * pwm->hwpwm + reg;
> +
> +	writel(val, ipq_chip->mem + off);
> +}
> +
> +static void config_div_and_duty(struct pwm_device *pwm, unsigned int pre_div,
> +			unsigned int pwm_div, unsigned long rate, u64 duty_ns,
> +			bool enable)
> +{
> +	unsigned long hi_dur;
> +	unsigned long val = 0;
> +
> +	/*
> +	 * high duration = pwm duty * (pwm div + 1)
> +	 * pwm duty = duty_ns / period_ns
> +	 */
> +	hi_dur = div64_u64(duty_ns * rate, (pre_div + 1) * NSEC_PER_SEC);
> +
> +	val = FIELD_PREP(IPQ_PWM_REG0_HI_DURATION, hi_dur) |
> +		FIELD_PREP(IPQ_PWM_REG0_PWM_DIV, pwm_div);
> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG0, val);
> +
> +	val = FIELD_PREP(IPQ_PWM_REG1_PRE_DIV, pre_div);
> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
> +
> +	/* PWM enable toggle needs a separate write to REG1 */
> +	val |= IPQ_PWM_REG1_UPDATE;
> +	if (enable)
> +		val |= IPQ_PWM_REG1_ENABLE;
> +	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
> +}
> +
> +static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> +			 const struct pwm_state *state)
> +{
> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
> +	unsigned int pre_div, pwm_div, best_pre_div, best_pwm_div;
> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
> +	u64 period_ns, duty_ns, period_rate;
> +	u64 min_diff;
> +
> +	if (state->polarity != PWM_POLARITY_NORMAL)
> +		return -EINVAL;
> +
> +	if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC, rate))
> +		return -ERANGE;
> +
> +	period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
> +	duty_ns = min(state->duty_cycle, period_ns);
> +
> +	/*
> +	 * period_ns is 1G or less. As long as rate is less than 16 GHz,
> +	 * period_rate does not overflow. Make that explicit.
> +	 */
> +	if (rate > 16ULL * GIGA)
> +		return -EINVAL;
> +	period_rate = period_ns * rate;
> +	best_pre_div = IPQ_PWM_MAX_DIV;
> +	best_pwm_div = IPQ_PWM_MAX_DIV;
> +	/*
> +	 * We don't need to consider pre_div values smaller than
> +	 *
> +	 *                              period_rate
> +	 *  pre_div_min := ------------------------------------
> +	 *                 NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1)
> +	 *
> +	 * because pre_div = pre_div_min results in a better
> +	 * approximation.
> +	 */
> +	pre_div = div64_u64(period_rate,
> +			(u64)NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1));
> +	min_diff = period_rate;
> +
> +	for (; pre_div <= IPQ_PWM_MAX_DIV; pre_div++) {
> +		u64 remainder;
> +
> +		pwm_div = div64_u64_rem(period_rate,
> +				(u64)NSEC_PER_SEC * (pre_div + 1), &remainder);
> +		/* pwm_div is unsigned; the check below catches underflow */
> +		pwm_div--;
> +
> +		/*
> +		 * Swapping values for pre_div and pwm_div produces the same
> +		 * period length. So we can skip all settings with pre_div >
> +		 * pwm_div which results in bigger constraints for selecting
> +		 * the duty_cycle than with the two values swapped.
> +		 */
> +		if (pre_div > pwm_div)
> +			break;
> +
> +		/*
> +		 * Make sure we can do 100% duty cycle where
> +		 * hi_dur == pwm_div + 1
> +		 */
> +		if (pwm_div > IPQ_PWM_MAX_DIV - 1)
> +			continue;
> +
> +		if (remainder < min_diff) {
> +			best_pre_div = pre_div;
> +			best_pwm_div = pwm_div;
> +			min_diff = remainder;
> +
> +			if (min_diff == 0) /* bingo */
> +				break;
> +		}
> +	}
> +
> +	/* config divider values for the closest possible frequency */
> +	config_div_and_duty(pwm, best_pre_div, best_pwm_div,
> +			    rate, duty_ns, state->enabled);
> +
> +	return 0;
> +}
> +
> +static void ipq_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
> +			      struct pwm_state *state)
> +{
> +	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
> +	unsigned long rate = clk_get_rate(ipq_chip->clk);
> +	unsigned int pre_div, pwm_div, hi_dur;
> +	u64 effective_div, hi_div;
> +	u32 reg0, reg1;
> +
> +	reg0 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG0);
> +	reg1 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG1);
> +
> +	state->polarity = PWM_POLARITY_NORMAL;
> +	state->enabled = reg1 & IPQ_PWM_REG1_ENABLE;
> +
> +	pwm_div = FIELD_GET(IPQ_PWM_REG0_PWM_DIV, reg0);
> +	hi_dur = FIELD_GET(IPQ_PWM_REG0_HI_DURATION, reg0);
> +	pre_div = FIELD_GET(IPQ_PWM_REG1_PRE_DIV, reg1);
> +
> +	/* No overflow here, both pre_div and pwm_div <= 0xffff */
> +	effective_div = (u64)(pre_div + 1) * (pwm_div + 1);
> +	state->period = DIV64_U64_ROUND_UP(effective_div * NSEC_PER_SEC, rate);
> +
> +	hi_div = hi_dur * (pre_div + 1);
> +	state->duty_cycle = DIV64_U64_ROUND_UP(hi_div * NSEC_PER_SEC, rate);
> +}
> +
> +static const struct pwm_ops ipq_pwm_ops = {
> +	.apply = ipq_pwm_apply,
> +	.get_state = ipq_pwm_get_state,
> +	.owner = THIS_MODULE,
> +};
> +
> +static int ipq_pwm_probe(struct platform_device *pdev)
> +{
> +	struct ipq_pwm_chip *pwm;
> +	struct device *dev = &pdev->dev;
> +	int ret;
> +
> +	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
> +	if (!pwm)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, pwm);
> +
> +	pwm->mem = devm_platform_ioremap_resource(pdev, 0);
> +	if (IS_ERR(pwm->mem))
> +		return dev_err_probe(dev, PTR_ERR(pwm->mem),
> +				"regs map failed");
> +
> +	pwm->clk = devm_clk_get(dev, NULL);
> +	if (IS_ERR(pwm->clk))
> +		return dev_err_probe(dev, PTR_ERR(pwm->clk),
> +				"failed to get clock");
> +
> +	ret = clk_prepare_enable(pwm->clk);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "clock enable failed");
> +
> +	pwm->chip.dev = dev;
> +	pwm->chip.ops = &ipq_pwm_ops;
> +	pwm->chip.npwm = 4;
> +
> +	ret = pwmchip_add(&pwm->chip);
> +	if (ret < 0) {
> +		dev_err_probe(dev, ret, "pwmchip_add() failed\n");
> +		clk_disable_unprepare(pwm->clk);
> +	}
> +
> +	return ret;
> +}
> +
> +static int ipq_pwm_remove(struct platform_device *pdev)
> +{
> +	struct ipq_pwm_chip *pwm = platform_get_drvdata(pdev);
> +
> +	pwmchip_remove(&pwm->chip);
> +	clk_disable_unprepare(pwm->clk);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id pwm_ipq_dt_match[] = {
> +	{ .compatible = "qcom,ipq6018-pwm", },
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match);
> +
> +static struct platform_driver ipq_pwm_driver = {
> +	.driver = {
> +		.name = "ipq-pwm",
> +		.of_match_table = pwm_ipq_dt_match,
> +	},
> +	.probe = ipq_pwm_probe,
> +	.remove = ipq_pwm_remove,
> +};
> +
> +module_platform_driver(ipq_pwm_driver);
> +
> +MODULE_LICENSE("Dual BSD/GPL");

_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
  2022-02-08  6:51     ` Baruch Siach
@ 2022-02-08 18:47       ` Nathan Chancellor
  -1 siblings, 0 replies; 34+ messages in thread
From: Nathan Chancellor @ 2022-02-08 18:47 UTC (permalink / raw)
  To: Baruch Siach
  Cc: kernel test robot, Thierry Reding, Uwe Kleine-König,
	Andy Gross, Bjorn Andersson, llvm, kbuild-all, Balaji Prakash J,
	Rob Herring, Robert Marko, Kathiravan T, linux-pwm

Hi Baruch,

On Tue, Feb 08, 2022 at 08:51:40AM +0200, Baruch Siach wrote:
> Hi test robot,
> 
> Thanks for testing and reporting.
> 
> On Tue, Feb 08 2022, kernel test robot wrote:
> 
> [snip]
> 
> >>> drivers/pwm/pwm-ipq.c:122:11: warning: result of comparison of constant 16000000000 with expression of type 'unsigned long' is always false [-Wtautological-constant-out-of-range-compare]
> >            if (rate > 16ULL * GIGA)
> >                ~~~~ ^ ~~~~~~~~~~~~
> >    1 warning generated.
> 
> This clang warning is only enabled with W=1 (see commit
> afe956c577b). Not sure how to avoid it.
> 
> Is there a way to express this condition without making clang warn on
> platforms where ULONG_MAX == 2^32? Maybe cast to unsigned long long? Or
> should we just ignore this W=1 warning?

As far as I am aware, casting to unsigned long long would be an
appropriate way to fix this warning, as has been done in the following
patches in mainline:

c9ae8eed4463 ("media: omap3isp: avoid warnings at IS_OUT_OF_BOUNDS()")
4853396f03c3 ("memstick: avoid out-of-range warning")
7ff4034e910f ("staging: vc04_services: shut up out-of-range warning")
a2fa9e57a68c ("ARM: mvebu: avoid clang -Wtautological-constant warning")

The below diff fixes the warning for me with ARCH=hexagon allyesconfig:

diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
index 994027290bcb..7ea29468e76e 100644
--- a/drivers/pwm/pwm-ipq.c
+++ b/drivers/pwm/pwm-ipq.c
@@ -119,7 +119,7 @@ static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 	 * period_ns is 1G or less. As long as rate is less than 16 GHz,
 	 * period_rate does not overflow. Make that explicit.
 	 */
-	if (rate > 16ULL * GIGA)
+	if ((unsigned long long)rate > 16ULL * GIGA)
 		return -EINVAL;
 	period_rate = period_ns * rate;
 	best_pre_div = IPQ_PWM_MAX_DIV;

Alternatively, you could widen rate to unsigned long long / u64 but I
don't know what kind of implications that has in this function but it
has been done in other places:

95c58291ee70 ("drm/msm/submit: fix overflow check on 64-bit architectures")
cfd6fb45cfaf ("crypto: ccree - avoid out-of-range warnings from clang")
335aea75b0d9 ("drm/amdgpu: fix warning for overflow check")
844b85dda2f5 ("ARM: keystone: fix integer overflow warning")

While the warning is currently under W=1, I think it is one that we
would like to turn on at some point so fixing instances as they come up
helps us get closer to that goal.

Cheers,
Nathan

> > vim +122 drivers/pwm/pwm-ipq.c
> >
> >     99	
> >    100	static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> >    101				 const struct pwm_state *state)
> >    102	{
> >    103		struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
> >    104		unsigned int pre_div, pwm_div, best_pre_div, best_pwm_div;
> >    105		unsigned long rate = clk_get_rate(ipq_chip->clk);
> >    106		u64 period_ns, duty_ns, period_rate;
> >    107		u64 min_diff;
> >    108	
> >    109		if (state->polarity != PWM_POLARITY_NORMAL)
> >    110			return -EINVAL;
> >    111	
> >    112		if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC, rate))
> >    113			return -ERANGE;
> >    114	
> >    115		period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
> >    116		duty_ns = min(state->duty_cycle, period_ns);
> >    117	
> >    118		/*
> >    119		 * period_ns is 1G or less. As long as rate is less than 16 GHz,
> >    120		 * period_rate does not overflow. Make that explicit.
> >    121		 */
> >  > 122		if (rate > 16ULL * GIGA)
> >    123			return -EINVAL;
> >    124		period_rate = period_ns * rate;
> >    125		best_pre_div = IPQ_PWM_MAX_DIV;
> >    126		best_pwm_div = IPQ_PWM_MAX_DIV;
> >    127		/*
> >    128		 * We don't need to consider pre_div values smaller than
> >    129		 *
> >    130		 *                              period_rate
> >    131		 *  pre_div_min := ------------------------------------
> >    132		 *                 NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1)
> >    133		 *
> >    134		 * because pre_div = pre_div_min results in a better
> >    135		 * approximation.
> >    136		 */
> >    137		pre_div = div64_u64(period_rate,
> >    138				(u64)NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1));
> >    139		min_diff = period_rate;
> >    140	
> >    141		for (; pre_div <= IPQ_PWM_MAX_DIV; pre_div++) {
> >    142			u64 remainder;
> >    143	
> >    144			pwm_div = div64_u64_rem(period_rate,
> >    145					(u64)NSEC_PER_SEC * (pre_div + 1), &remainder);
> >    146			/* pwm_div is unsigned; the check below catches underflow */
> >    147			pwm_div--;
> >    148	
> >    149			/*
> >    150			 * Swapping values for pre_div and pwm_div produces the same
> >    151			 * period length. So we can skip all settings with pre_div >
> >    152			 * pwm_div which results in bigger constraints for selecting
> >    153			 * the duty_cycle than with the two values swapped.
> >    154			 */
> >    155			if (pre_div > pwm_div)
> >    156				break;
> >    157	
> >    158			/*
> >    159			 * Make sure we can do 100% duty cycle where
> >    160			 * hi_dur == pwm_div + 1
> >    161			 */
> >    162			if (pwm_div > IPQ_PWM_MAX_DIV - 1)
> >    163				continue;
> >    164	
> >    165			if (remainder < min_diff) {
> >    166				best_pre_div = pre_div;
> >    167				best_pwm_div = pwm_div;
> >    168				min_diff = remainder;
> >    169	
> >    170				if (min_diff == 0) /* bingo */
> >    171					break;
> >    172			}
> >    173		}
> >    174	
> >    175		/* config divider values for the closest possible frequency */
> >    176		config_div_and_duty(pwm, best_pre_div, best_pwm_div,
> >    177				    rate, duty_ns, state->enabled);
> >    178	
> >    179		return 0;
> >    180	}
> >    181	
> >
> > ---
> > 0-DAY CI Kernel Test Service, Intel Corporation
> > https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org
> 
> 
> -- 
>                                                      ~. .~   Tk Open Systems
> =}------------------------------------------------ooO--U--Ooo------------{=
>    - baruch@tkos.co.il - tel: +972.52.368.4656, http://www.tkos.co.il -
> 

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
@ 2022-02-08 18:47       ` Nathan Chancellor
  0 siblings, 0 replies; 34+ messages in thread
From: Nathan Chancellor @ 2022-02-08 18:47 UTC (permalink / raw)
  To: kbuild-all

[-- Attachment #1: Type: text/plain, Size: 6213 bytes --]

Hi Baruch,

On Tue, Feb 08, 2022 at 08:51:40AM +0200, Baruch Siach wrote:
> Hi test robot,
> 
> Thanks for testing and reporting.
> 
> On Tue, Feb 08 2022, kernel test robot wrote:
> 
> [snip]
> 
> >>> drivers/pwm/pwm-ipq.c:122:11: warning: result of comparison of constant 16000000000 with expression of type 'unsigned long' is always false [-Wtautological-constant-out-of-range-compare]
> >            if (rate > 16ULL * GIGA)
> >                ~~~~ ^ ~~~~~~~~~~~~
> >    1 warning generated.
> 
> This clang warning is only enabled with W=1 (see commit
> afe956c577b). Not sure how to avoid it.
> 
> Is there a way to express this condition without making clang warn on
> platforms where ULONG_MAX == 2^32? Maybe cast to unsigned long long? Or
> should we just ignore this W=1 warning?

As far as I am aware, casting to unsigned long long would be an
appropriate way to fix this warning, as has been done in the following
patches in mainline:

c9ae8eed4463 ("media: omap3isp: avoid warnings at IS_OUT_OF_BOUNDS()")
4853396f03c3 ("memstick: avoid out-of-range warning")
7ff4034e910f ("staging: vc04_services: shut up out-of-range warning")
a2fa9e57a68c ("ARM: mvebu: avoid clang -Wtautological-constant warning")

The below diff fixes the warning for me with ARCH=hexagon allyesconfig:

diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
index 994027290bcb..7ea29468e76e 100644
--- a/drivers/pwm/pwm-ipq.c
+++ b/drivers/pwm/pwm-ipq.c
@@ -119,7 +119,7 @@ static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
 	 * period_ns is 1G or less. As long as rate is less than 16 GHz,
 	 * period_rate does not overflow. Make that explicit.
 	 */
-	if (rate > 16ULL * GIGA)
+	if ((unsigned long long)rate > 16ULL * GIGA)
 		return -EINVAL;
 	period_rate = period_ns * rate;
 	best_pre_div = IPQ_PWM_MAX_DIV;

Alternatively, you could widen rate to unsigned long long / u64 but I
don't know what kind of implications that has in this function but it
has been done in other places:

95c58291ee70 ("drm/msm/submit: fix overflow check on 64-bit architectures")
cfd6fb45cfaf ("crypto: ccree - avoid out-of-range warnings from clang")
335aea75b0d9 ("drm/amdgpu: fix warning for overflow check")
844b85dda2f5 ("ARM: keystone: fix integer overflow warning")

While the warning is currently under W=1, I think it is one that we
would like to turn on at some point so fixing instances as they come up
helps us get closer to that goal.

Cheers,
Nathan

> > vim +122 drivers/pwm/pwm-ipq.c
> >
> >     99	
> >    100	static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> >    101				 const struct pwm_state *state)
> >    102	{
> >    103		struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
> >    104		unsigned int pre_div, pwm_div, best_pre_div, best_pwm_div;
> >    105		unsigned long rate = clk_get_rate(ipq_chip->clk);
> >    106		u64 period_ns, duty_ns, period_rate;
> >    107		u64 min_diff;
> >    108	
> >    109		if (state->polarity != PWM_POLARITY_NORMAL)
> >    110			return -EINVAL;
> >    111	
> >    112		if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC, rate))
> >    113			return -ERANGE;
> >    114	
> >    115		period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
> >    116		duty_ns = min(state->duty_cycle, period_ns);
> >    117	
> >    118		/*
> >    119		 * period_ns is 1G or less. As long as rate is less than 16 GHz,
> >    120		 * period_rate does not overflow. Make that explicit.
> >    121		 */
> >  > 122		if (rate > 16ULL * GIGA)
> >    123			return -EINVAL;
> >    124		period_rate = period_ns * rate;
> >    125		best_pre_div = IPQ_PWM_MAX_DIV;
> >    126		best_pwm_div = IPQ_PWM_MAX_DIV;
> >    127		/*
> >    128		 * We don't need to consider pre_div values smaller than
> >    129		 *
> >    130		 *                              period_rate
> >    131		 *  pre_div_min := ------------------------------------
> >    132		 *                 NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1)
> >    133		 *
> >    134		 * because pre_div = pre_div_min results in a better
> >    135		 * approximation.
> >    136		 */
> >    137		pre_div = div64_u64(period_rate,
> >    138				(u64)NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1));
> >    139		min_diff = period_rate;
> >    140	
> >    141		for (; pre_div <= IPQ_PWM_MAX_DIV; pre_div++) {
> >    142			u64 remainder;
> >    143	
> >    144			pwm_div = div64_u64_rem(period_rate,
> >    145					(u64)NSEC_PER_SEC * (pre_div + 1), &remainder);
> >    146			/* pwm_div is unsigned; the check below catches underflow */
> >    147			pwm_div--;
> >    148	
> >    149			/*
> >    150			 * Swapping values for pre_div and pwm_div produces the same
> >    151			 * period length. So we can skip all settings with pre_div >
> >    152			 * pwm_div which results in bigger constraints for selecting
> >    153			 * the duty_cycle than with the two values swapped.
> >    154			 */
> >    155			if (pre_div > pwm_div)
> >    156				break;
> >    157	
> >    158			/*
> >    159			 * Make sure we can do 100% duty cycle where
> >    160			 * hi_dur == pwm_div + 1
> >    161			 */
> >    162			if (pwm_div > IPQ_PWM_MAX_DIV - 1)
> >    163				continue;
> >    164	
> >    165			if (remainder < min_diff) {
> >    166				best_pre_div = pre_div;
> >    167				best_pwm_div = pwm_div;
> >    168				min_diff = remainder;
> >    169	
> >    170				if (min_diff == 0) /* bingo */
> >    171					break;
> >    172			}
> >    173		}
> >    174	
> >    175		/* config divider values for the closest possible frequency */
> >    176		config_div_and_duty(pwm, best_pre_div, best_pwm_div,
> >    177				    rate, duty_ns, state->enabled);
> >    178	
> >    179		return 0;
> >    180	}
> >    181	
> >
> > ---
> > 0-DAY CI Kernel Test Service, Intel Corporation
> > https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org
> 
> 
> -- 
>                                                      ~. .~   Tk Open Systems
> =}------------------------------------------------ooO--U--Ooo------------{=
>    - baruch(a)tkos.co.il - tel: +972.52.368.4656, http://www.tkos.co.il -
> 

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
  2022-02-07 20:22   ` kernel test robot
@ 2022-02-08  6:51     ` Baruch Siach
  -1 siblings, 0 replies; 34+ messages in thread
From: Baruch Siach @ 2022-02-08  6:51 UTC (permalink / raw)
  To: kernel test robot
  Cc: Thierry Reding, Uwe Kleine-König, Andy Gross,
	Bjorn Andersson, llvm, kbuild-all, Balaji Prakash J, Rob Herring,
	Robert Marko, Kathiravan T, linux-pwm

Hi test robot,

Thanks for testing and reporting.

On Tue, Feb 08 2022, kernel test robot wrote:

[snip]

>>> drivers/pwm/pwm-ipq.c:122:11: warning: result of comparison of constant 16000000000 with expression of type 'unsigned long' is always false [-Wtautological-constant-out-of-range-compare]
>            if (rate > 16ULL * GIGA)
>                ~~~~ ^ ~~~~~~~~~~~~
>    1 warning generated.

This clang warning is only enabled with W=1 (see commit
afe956c577b). Not sure how to avoid it.

Is there a way to express this condition without making clang warn on
platforms where ULONG_MAX == 2^32? Maybe cast to unsigned long long? Or
should we just ignore this W=1 warning?

baruch

> vim +122 drivers/pwm/pwm-ipq.c
>
>     99	
>    100	static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
>    101				 const struct pwm_state *state)
>    102	{
>    103		struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
>    104		unsigned int pre_div, pwm_div, best_pre_div, best_pwm_div;
>    105		unsigned long rate = clk_get_rate(ipq_chip->clk);
>    106		u64 period_ns, duty_ns, period_rate;
>    107		u64 min_diff;
>    108	
>    109		if (state->polarity != PWM_POLARITY_NORMAL)
>    110			return -EINVAL;
>    111	
>    112		if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC, rate))
>    113			return -ERANGE;
>    114	
>    115		period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
>    116		duty_ns = min(state->duty_cycle, period_ns);
>    117	
>    118		/*
>    119		 * period_ns is 1G or less. As long as rate is less than 16 GHz,
>    120		 * period_rate does not overflow. Make that explicit.
>    121		 */
>  > 122		if (rate > 16ULL * GIGA)
>    123			return -EINVAL;
>    124		period_rate = period_ns * rate;
>    125		best_pre_div = IPQ_PWM_MAX_DIV;
>    126		best_pwm_div = IPQ_PWM_MAX_DIV;
>    127		/*
>    128		 * We don't need to consider pre_div values smaller than
>    129		 *
>    130		 *                              period_rate
>    131		 *  pre_div_min := ------------------------------------
>    132		 *                 NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1)
>    133		 *
>    134		 * because pre_div = pre_div_min results in a better
>    135		 * approximation.
>    136		 */
>    137		pre_div = div64_u64(period_rate,
>    138				(u64)NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1));
>    139		min_diff = period_rate;
>    140	
>    141		for (; pre_div <= IPQ_PWM_MAX_DIV; pre_div++) {
>    142			u64 remainder;
>    143	
>    144			pwm_div = div64_u64_rem(period_rate,
>    145					(u64)NSEC_PER_SEC * (pre_div + 1), &remainder);
>    146			/* pwm_div is unsigned; the check below catches underflow */
>    147			pwm_div--;
>    148	
>    149			/*
>    150			 * Swapping values for pre_div and pwm_div produces the same
>    151			 * period length. So we can skip all settings with pre_div >
>    152			 * pwm_div which results in bigger constraints for selecting
>    153			 * the duty_cycle than with the two values swapped.
>    154			 */
>    155			if (pre_div > pwm_div)
>    156				break;
>    157	
>    158			/*
>    159			 * Make sure we can do 100% duty cycle where
>    160			 * hi_dur == pwm_div + 1
>    161			 */
>    162			if (pwm_div > IPQ_PWM_MAX_DIV - 1)
>    163				continue;
>    164	
>    165			if (remainder < min_diff) {
>    166				best_pre_div = pre_div;
>    167				best_pwm_div = pwm_div;
>    168				min_diff = remainder;
>    169	
>    170				if (min_diff == 0) /* bingo */
>    171					break;
>    172			}
>    173		}
>    174	
>    175		/* config divider values for the closest possible frequency */
>    176		config_div_and_duty(pwm, best_pre_div, best_pwm_div,
>    177				    rate, duty_ns, state->enabled);
>    178	
>    179		return 0;
>    180	}
>    181	
>
> ---
> 0-DAY CI Kernel Test Service, Intel Corporation
> https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org


-- 
                                                     ~. .~   Tk Open Systems
=}------------------------------------------------ooO--U--Ooo------------{=
   - baruch@tkos.co.il - tel: +972.52.368.4656, http://www.tkos.co.il -

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
@ 2022-02-08  6:51     ` Baruch Siach
  0 siblings, 0 replies; 34+ messages in thread
From: Baruch Siach @ 2022-02-08  6:51 UTC (permalink / raw)
  To: kbuild-all

[-- Attachment #1: Type: text/plain, Size: 4203 bytes --]

Hi test robot,

Thanks for testing and reporting.

On Tue, Feb 08 2022, kernel test robot wrote:

[snip]

>>> drivers/pwm/pwm-ipq.c:122:11: warning: result of comparison of constant 16000000000 with expression of type 'unsigned long' is always false [-Wtautological-constant-out-of-range-compare]
>            if (rate > 16ULL * GIGA)
>                ~~~~ ^ ~~~~~~~~~~~~
>    1 warning generated.

This clang warning is only enabled with W=1 (see commit
afe956c577b). Not sure how to avoid it.

Is there a way to express this condition without making clang warn on
platforms where ULONG_MAX == 2^32? Maybe cast to unsigned long long? Or
should we just ignore this W=1 warning?

baruch

> vim +122 drivers/pwm/pwm-ipq.c
>
>     99	
>    100	static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
>    101				 const struct pwm_state *state)
>    102	{
>    103		struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
>    104		unsigned int pre_div, pwm_div, best_pre_div, best_pwm_div;
>    105		unsigned long rate = clk_get_rate(ipq_chip->clk);
>    106		u64 period_ns, duty_ns, period_rate;
>    107		u64 min_diff;
>    108	
>    109		if (state->polarity != PWM_POLARITY_NORMAL)
>    110			return -EINVAL;
>    111	
>    112		if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC, rate))
>    113			return -ERANGE;
>    114	
>    115		period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
>    116		duty_ns = min(state->duty_cycle, period_ns);
>    117	
>    118		/*
>    119		 * period_ns is 1G or less. As long as rate is less than 16 GHz,
>    120		 * period_rate does not overflow. Make that explicit.
>    121		 */
>  > 122		if (rate > 16ULL * GIGA)
>    123			return -EINVAL;
>    124		period_rate = period_ns * rate;
>    125		best_pre_div = IPQ_PWM_MAX_DIV;
>    126		best_pwm_div = IPQ_PWM_MAX_DIV;
>    127		/*
>    128		 * We don't need to consider pre_div values smaller than
>    129		 *
>    130		 *                              period_rate
>    131		 *  pre_div_min := ------------------------------------
>    132		 *                 NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1)
>    133		 *
>    134		 * because pre_div = pre_div_min results in a better
>    135		 * approximation.
>    136		 */
>    137		pre_div = div64_u64(period_rate,
>    138				(u64)NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1));
>    139		min_diff = period_rate;
>    140	
>    141		for (; pre_div <= IPQ_PWM_MAX_DIV; pre_div++) {
>    142			u64 remainder;
>    143	
>    144			pwm_div = div64_u64_rem(period_rate,
>    145					(u64)NSEC_PER_SEC * (pre_div + 1), &remainder);
>    146			/* pwm_div is unsigned; the check below catches underflow */
>    147			pwm_div--;
>    148	
>    149			/*
>    150			 * Swapping values for pre_div and pwm_div produces the same
>    151			 * period length. So we can skip all settings with pre_div >
>    152			 * pwm_div which results in bigger constraints for selecting
>    153			 * the duty_cycle than with the two values swapped.
>    154			 */
>    155			if (pre_div > pwm_div)
>    156				break;
>    157	
>    158			/*
>    159			 * Make sure we can do 100% duty cycle where
>    160			 * hi_dur == pwm_div + 1
>    161			 */
>    162			if (pwm_div > IPQ_PWM_MAX_DIV - 1)
>    163				continue;
>    164	
>    165			if (remainder < min_diff) {
>    166				best_pre_div = pre_div;
>    167				best_pwm_div = pwm_div;
>    168				min_diff = remainder;
>    169	
>    170				if (min_diff == 0) /* bingo */
>    171					break;
>    172			}
>    173		}
>    174	
>    175		/* config divider values for the closest possible frequency */
>    176		config_div_and_duty(pwm, best_pre_div, best_pwm_div,
>    177				    rate, duty_ns, state->enabled);
>    178	
>    179		return 0;
>    180	}
>    181	
>
> ---
> 0-DAY CI Kernel Test Service, Intel Corporation
> https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org


-- 
                                                     ~. .~   Tk Open Systems
=}------------------------------------------------ooO--U--Ooo------------{=
   - baruch(a)tkos.co.il - tel: +972.52.368.4656, http://www.tkos.co.il -

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
  2022-02-07  9:30 ` Baruch Siach
@ 2022-02-07 20:22   ` kernel test robot
  -1 siblings, 0 replies; 34+ messages in thread
From: kernel test robot @ 2022-02-07 20:22 UTC (permalink / raw)
  To: Baruch Siach, Thierry Reding, Uwe Kleine-König, Andy Gross,
	Bjorn Andersson
  Cc: llvm, kbuild-all, Baruch Siach, Balaji Prakash J, Rob Herring,
	Robert Marko, Kathiravan T, linux-pwm

Hi Baruch,

I love your patch! Perhaps something to improve:

[auto build test WARNING on thierry-reding-pwm/for-next]
[also build test WARNING on robh/for-next v5.17-rc3 next-20220207]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Baruch-Siach/pwm-driver-for-qualcomm-ipq6018-pwm-block/20220207-175605
base:   https://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm.git for-next
config: hexagon-allyesconfig (https://download.01.org/0day-ci/archive/20220208/202202080410.R0qwqtXx-lkp@intel.com/config)
compiler: clang version 15.0.0 (https://github.com/llvm/llvm-project 0d8850ae2cae85d49bea6ae0799fa41c7202c05c)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/0day-ci/linux/commit/71e449eb6d19b141b4527caae529e16c52bcfeea
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Baruch-Siach/pwm-driver-for-qualcomm-ipq6018-pwm-block/20220207-175605
        git checkout 71e449eb6d19b141b4527caae529e16c52bcfeea
        # save the config file to linux build tree
        mkdir build_dir
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=hexagon SHELL=/bin/bash drivers/pwm/

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

>> drivers/pwm/pwm-ipq.c:122:11: warning: result of comparison of constant 16000000000 with expression of type 'unsigned long' is always false [-Wtautological-constant-out-of-range-compare]
           if (rate > 16ULL * GIGA)
               ~~~~ ^ ~~~~~~~~~~~~
   1 warning generated.


vim +122 drivers/pwm/pwm-ipq.c

    99	
   100	static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
   101				 const struct pwm_state *state)
   102	{
   103		struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
   104		unsigned int pre_div, pwm_div, best_pre_div, best_pwm_div;
   105		unsigned long rate = clk_get_rate(ipq_chip->clk);
   106		u64 period_ns, duty_ns, period_rate;
   107		u64 min_diff;
   108	
   109		if (state->polarity != PWM_POLARITY_NORMAL)
   110			return -EINVAL;
   111	
   112		if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC, rate))
   113			return -ERANGE;
   114	
   115		period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
   116		duty_ns = min(state->duty_cycle, period_ns);
   117	
   118		/*
   119		 * period_ns is 1G or less. As long as rate is less than 16 GHz,
   120		 * period_rate does not overflow. Make that explicit.
   121		 */
 > 122		if (rate > 16ULL * GIGA)
   123			return -EINVAL;
   124		period_rate = period_ns * rate;
   125		best_pre_div = IPQ_PWM_MAX_DIV;
   126		best_pwm_div = IPQ_PWM_MAX_DIV;
   127		/*
   128		 * We don't need to consider pre_div values smaller than
   129		 *
   130		 *                              period_rate
   131		 *  pre_div_min := ------------------------------------
   132		 *                 NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1)
   133		 *
   134		 * because pre_div = pre_div_min results in a better
   135		 * approximation.
   136		 */
   137		pre_div = div64_u64(period_rate,
   138				(u64)NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1));
   139		min_diff = period_rate;
   140	
   141		for (; pre_div <= IPQ_PWM_MAX_DIV; pre_div++) {
   142			u64 remainder;
   143	
   144			pwm_div = div64_u64_rem(period_rate,
   145					(u64)NSEC_PER_SEC * (pre_div + 1), &remainder);
   146			/* pwm_div is unsigned; the check below catches underflow */
   147			pwm_div--;
   148	
   149			/*
   150			 * Swapping values for pre_div and pwm_div produces the same
   151			 * period length. So we can skip all settings with pre_div >
   152			 * pwm_div which results in bigger constraints for selecting
   153			 * the duty_cycle than with the two values swapped.
   154			 */
   155			if (pre_div > pwm_div)
   156				break;
   157	
   158			/*
   159			 * Make sure we can do 100% duty cycle where
   160			 * hi_dur == pwm_div + 1
   161			 */
   162			if (pwm_div > IPQ_PWM_MAX_DIV - 1)
   163				continue;
   164	
   165			if (remainder < min_diff) {
   166				best_pre_div = pre_div;
   167				best_pwm_div = pwm_div;
   168				min_diff = remainder;
   169	
   170				if (min_diff == 0) /* bingo */
   171					break;
   172			}
   173		}
   174	
   175		/* config divider values for the closest possible frequency */
   176		config_div_and_duty(pwm, best_pre_div, best_pwm_div,
   177				    rate, duty_ns, state->enabled);
   178	
   179		return 0;
   180	}
   181	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all@lists.01.org

^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
@ 2022-02-07 20:22   ` kernel test robot
  0 siblings, 0 replies; 34+ messages in thread
From: kernel test robot @ 2022-02-07 20:22 UTC (permalink / raw)
  To: kbuild-all

[-- Attachment #1: Type: text/plain, Size: 5084 bytes --]

Hi Baruch,

I love your patch! Perhaps something to improve:

[auto build test WARNING on thierry-reding-pwm/for-next]
[also build test WARNING on robh/for-next v5.17-rc3 next-20220207]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch]

url:    https://github.com/0day-ci/linux/commits/Baruch-Siach/pwm-driver-for-qualcomm-ipq6018-pwm-block/20220207-175605
base:   https://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm.git for-next
config: hexagon-allyesconfig (https://download.01.org/0day-ci/archive/20220208/202202080410.R0qwqtXx-lkp(a)intel.com/config)
compiler: clang version 15.0.0 (https://github.com/llvm/llvm-project 0d8850ae2cae85d49bea6ae0799fa41c7202c05c)
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/0day-ci/linux/commit/71e449eb6d19b141b4527caae529e16c52bcfeea
        git remote add linux-review https://github.com/0day-ci/linux
        git fetch --no-tags linux-review Baruch-Siach/pwm-driver-for-qualcomm-ipq6018-pwm-block/20220207-175605
        git checkout 71e449eb6d19b141b4527caae529e16c52bcfeea
        # save the config file to linux build tree
        mkdir build_dir
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=hexagon SHELL=/bin/bash drivers/pwm/

If you fix the issue, kindly add following tag as appropriate
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

>> drivers/pwm/pwm-ipq.c:122:11: warning: result of comparison of constant 16000000000 with expression of type 'unsigned long' is always false [-Wtautological-constant-out-of-range-compare]
           if (rate > 16ULL * GIGA)
               ~~~~ ^ ~~~~~~~~~~~~
   1 warning generated.


vim +122 drivers/pwm/pwm-ipq.c

    99	
   100	static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
   101				 const struct pwm_state *state)
   102	{
   103		struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
   104		unsigned int pre_div, pwm_div, best_pre_div, best_pwm_div;
   105		unsigned long rate = clk_get_rate(ipq_chip->clk);
   106		u64 period_ns, duty_ns, period_rate;
   107		u64 min_diff;
   108	
   109		if (state->polarity != PWM_POLARITY_NORMAL)
   110			return -EINVAL;
   111	
   112		if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC, rate))
   113			return -ERANGE;
   114	
   115		period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
   116		duty_ns = min(state->duty_cycle, period_ns);
   117	
   118		/*
   119		 * period_ns is 1G or less. As long as rate is less than 16 GHz,
   120		 * period_rate does not overflow. Make that explicit.
   121		 */
 > 122		if (rate > 16ULL * GIGA)
   123			return -EINVAL;
   124		period_rate = period_ns * rate;
   125		best_pre_div = IPQ_PWM_MAX_DIV;
   126		best_pwm_div = IPQ_PWM_MAX_DIV;
   127		/*
   128		 * We don't need to consider pre_div values smaller than
   129		 *
   130		 *                              period_rate
   131		 *  pre_div_min := ------------------------------------
   132		 *                 NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1)
   133		 *
   134		 * because pre_div = pre_div_min results in a better
   135		 * approximation.
   136		 */
   137		pre_div = div64_u64(period_rate,
   138				(u64)NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1));
   139		min_diff = period_rate;
   140	
   141		for (; pre_div <= IPQ_PWM_MAX_DIV; pre_div++) {
   142			u64 remainder;
   143	
   144			pwm_div = div64_u64_rem(period_rate,
   145					(u64)NSEC_PER_SEC * (pre_div + 1), &remainder);
   146			/* pwm_div is unsigned; the check below catches underflow */
   147			pwm_div--;
   148	
   149			/*
   150			 * Swapping values for pre_div and pwm_div produces the same
   151			 * period length. So we can skip all settings with pre_div >
   152			 * pwm_div which results in bigger constraints for selecting
   153			 * the duty_cycle than with the two values swapped.
   154			 */
   155			if (pre_div > pwm_div)
   156				break;
   157	
   158			/*
   159			 * Make sure we can do 100% duty cycle where
   160			 * hi_dur == pwm_div + 1
   161			 */
   162			if (pwm_div > IPQ_PWM_MAX_DIV - 1)
   163				continue;
   164	
   165			if (remainder < min_diff) {
   166				best_pre_div = pre_div;
   167				best_pwm_div = pwm_div;
   168				min_diff = remainder;
   169	
   170				if (min_diff == 0) /* bingo */
   171					break;
   172			}
   173		}
   174	
   175		/* config divider values for the closest possible frequency */
   176		config_div_and_duty(pwm, best_pre_div, best_pwm_div,
   177				    rate, duty_ns, state->enabled);
   178	
   179		return 0;
   180	}
   181	

---
0-DAY CI Kernel Test Service, Intel Corporation
https://lists.01.org/hyperkitty/list/kbuild-all(a)lists.01.org

^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
@ 2022-02-07  9:30 ` Baruch Siach
  0 siblings, 0 replies; 34+ messages in thread
From: Baruch Siach @ 2022-02-07  9:30 UTC (permalink / raw)
  To: Thierry Reding, Uwe Kleine-König, Andy Gross, Bjorn Andersson
  Cc: Baruch Siach, Balaji Prakash J, Rob Herring, Robert Marko,
	Kathiravan T, linux-pwm, devicetree, linux-arm-msm,
	linux-arm-kernel

From: Baruch Siach <baruch.siach@siklu.com>

Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
driver from downstream Codeaurora kernel tree. Removed support for older
(V1) variants because I have no access to that hardware.

Tested on IPQ6010 based hardware.

Signed-off-by: Baruch Siach <baruch.siach@siklu.com>
---
v11:

Address comment from Uwe Kleine-König:

  Drop redundant registers field comments

  Fix period limit check in .apply

  Clarify the comment explaining skip of pre_div > pwm_div values

  Add explicit check for clock rate within limit

  Add comment explaining the selection of initial pre_div

  Use pwm_div division with remainder instead of separate diff calculation

  Round up duty_cycle calculation in .get_state

v10:

  Restore round up in pwm_div calculation; otherwise diff is always <=
  0, so only bingo match works

  Don't overwrite min_diff on every loop iteration

v9:

Address comment from Uwe Kleine-König:

  Use period_ns*rate in dividers calculation for better accuracy

  Round down pre_div and pwm_div

  Add a comment explaining why pwm_div can't underflow

  Add a comment explaining why pre_div > pwm_div end the search loop

  Drop 'CFG_' from register macros

  Rename to_ipq_pwm_chip() to ipq_pwm_from_chip()

  Change bare 'unsigned' to 'unsigned int'

  Clarify the comment on separate REG1 write for enable/disable

  Round up the period value in .get_state

  Use direct readl/writel so no need to check for regmap errors

v7:

  Change 'offset' to 'reg' for the tcsr offset (Rob)

  Drop clock name; there is only one clock (Bjorn)

  Simplify probe failure code path (Bjorn)

v6:

Address Uwe Kleine-König review comments:

  Drop IPQ_PWM_MAX_DEVICES

  Rely on assigned-clock-rates; drop IPQ_PWM_CLK_SRC_FREQ

  Simplify register offset calculation

  Calculate duty cycle more precisely

  Refuse to set inverted polarity

  Drop redundant IPQ_PWM_REG1_ENABLE bit clear

  Remove x1000 factor in pwm_div calculation, use rate directly, and round up

  Choose initial pre_div such that pwm_div < IPQ_PWM_MAX_DIV

  Ensure pre_div <= pwm_div

  Rename close_ to best_

  Explain in comment why effective_div doesn't overflow

  Limit pwm_div to IPQ_PWM_MAX_DIV - 1 to allow 100% duty cycle

  Disable clock only after pwmchip_remove()

  const pwm_ops

Other changes:

  Add missing linux/bitfield.h header include (kernel test robot)

  Adjust code for PWM device node under TCSR (Rob Herring)

v5:

Use &tcsr_q6 syscon to access registers (Bjorn Andersson)

Address Uwe Kleine-König review comments:

  Implement .get_state()

  Add IPQ_PWM_ prefix to local macros

  Use GENMASK/BIT/FIELD_PREP for register fields access

  Make type of config_div_and_duty() parameters consistent

  Derive IPQ_PWM_MIN_PERIOD_NS from IPQ_PWM_CLK_SRC_FREQ

  Integrate enable/disable into config_div_and_duty() to save register read,
  and reduce frequency glitch on update

  Use min() instead of min_t()

  Fix comment format

  Use dev_err_probe() to indicate probe step failure

  Add missing clk_disable_unprepare() in .remove

  Don't set .owner

v4:

  Use div64_u64() to fix link for 32-bit targets ((kernel test robot
  <lkp@intel.com>, Uwe Kleine-König)

v3:

  s/qcom,pwm-ipq6018/qcom,ipq6018-pwm/ (Rob Herring)

  Fix integer overflow on 32-bit targets (kernel test robot <lkp@intel.com>)

v2:

Address Uwe Kleine-König review comments:

  Fix period calculation when out of range

  Don't set period larger than requested

  Remove PWM disable on configuration change

  Implement .apply instead of non-atomic .config/.enable/.disable

  Don't modify PWM on .request/.free

  Check pwm_div underflow

  Fix various code and comment formatting issues

Other changes:

  Use u64 divisor safe division

  Remove now empty .request/.free
---
 drivers/pwm/Kconfig   |  12 ++
 drivers/pwm/Makefile  |   1 +
 drivers/pwm/pwm-ipq.c | 281 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 294 insertions(+)
 create mode 100644 drivers/pwm/pwm-ipq.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 21e3b05a5153..e39718137ecd 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -260,6 +260,18 @@ config PWM_INTEL_LGM
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-intel-lgm.
 
+config PWM_IPQ
+	tristate "IPQ PWM support"
+	depends on ARCH_QCOM || COMPILE_TEST
+	depends on HAVE_CLK && HAS_IOMEM
+	help
+	  Generic PWM framework driver for IPQ PWM block which supports
+	  4 pwm channels. Each of the these channels can be configured
+	  independent of each other.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called pwm-ipq.
+
 config PWM_IQS620A
 	tristate "Azoteq IQS620A PWM support"
 	depends on MFD_IQS62X || COMPILE_TEST
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 708840b7fba8..7402feae4b36 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
 obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
 obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
 obj-$(CONFIG_PWM_INTEL_LGM)	+= pwm-intel-lgm.o
+obj-$(CONFIG_PWM_IPQ)		+= pwm-ipq.o
 obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
 obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
 obj-$(CONFIG_PWM_KEEMBAY)	+= pwm-keembay.o
diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
new file mode 100644
index 000000000000..994027290bcb
--- /dev/null
+++ b/drivers/pwm/pwm-ipq.c
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
+/*
+ * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/math64.h>
+#include <linux/of_device.h>
+#include <linux/bitfield.h>
+#include <linux/units.h>
+
+/* The frequency range supported is 1 Hz to clock rate */
+#define IPQ_PWM_MAX_PERIOD_NS	((u64)NSEC_PER_SEC)
+
+/*
+ * The max value specified for each field is based on the number of bits
+ * in the pwm control register for that field
+ */
+#define IPQ_PWM_MAX_DIV		0xFFFF
+
+/*
+ * Two 32-bit registers for each PWM: REG0, and REG1.
+ * Base offset for PWM #i is at 8 * #i.
+ */
+#define IPQ_PWM_REG0			0
+#define IPQ_PWM_REG0_PWM_DIV		GENMASK(15, 0)
+#define IPQ_PWM_REG0_HI_DURATION	GENMASK(31, 16)
+
+#define IPQ_PWM_REG1			4
+#define IPQ_PWM_REG1_PRE_DIV		GENMASK(15, 0)
+/*
+ * Enable bit is set to enable output toggling in pwm device.
+ * Update bit is set to reflect the changed divider and high duration
+ * values in register.
+ */
+#define IPQ_PWM_REG1_UPDATE		BIT(30)
+#define IPQ_PWM_REG1_ENABLE		BIT(31)
+
+
+struct ipq_pwm_chip {
+	struct pwm_chip chip;
+	struct clk *clk;
+	void __iomem *mem;
+};
+
+static struct ipq_pwm_chip *ipq_pwm_from_chip(struct pwm_chip *chip)
+{
+	return container_of(chip, struct ipq_pwm_chip, chip);
+}
+
+static unsigned int ipq_pwm_reg_read(struct pwm_device *pwm, unsigned int reg)
+{
+	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
+	unsigned int off = 8 * pwm->hwpwm + reg;
+
+	return readl(ipq_chip->mem + off);
+}
+
+static void ipq_pwm_reg_write(struct pwm_device *pwm, unsigned int reg,
+			      unsigned int val)
+{
+	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
+	unsigned int off = 8 * pwm->hwpwm + reg;
+
+	writel(val, ipq_chip->mem + off);
+}
+
+static void config_div_and_duty(struct pwm_device *pwm, unsigned int pre_div,
+			unsigned int pwm_div, unsigned long rate, u64 duty_ns,
+			bool enable)
+{
+	unsigned long hi_dur;
+	unsigned long val = 0;
+
+	/*
+	 * high duration = pwm duty * (pwm div + 1)
+	 * pwm duty = duty_ns / period_ns
+	 */
+	hi_dur = div64_u64(duty_ns * rate, (pre_div + 1) * NSEC_PER_SEC);
+
+	val = FIELD_PREP(IPQ_PWM_REG0_HI_DURATION, hi_dur) |
+		FIELD_PREP(IPQ_PWM_REG0_PWM_DIV, pwm_div);
+	ipq_pwm_reg_write(pwm, IPQ_PWM_REG0, val);
+
+	val = FIELD_PREP(IPQ_PWM_REG1_PRE_DIV, pre_div);
+	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
+
+	/* PWM enable toggle needs a separate write to REG1 */
+	val |= IPQ_PWM_REG1_UPDATE;
+	if (enable)
+		val |= IPQ_PWM_REG1_ENABLE;
+	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
+}
+
+static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+			 const struct pwm_state *state)
+{
+	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
+	unsigned int pre_div, pwm_div, best_pre_div, best_pwm_div;
+	unsigned long rate = clk_get_rate(ipq_chip->clk);
+	u64 period_ns, duty_ns, period_rate;
+	u64 min_diff;
+
+	if (state->polarity != PWM_POLARITY_NORMAL)
+		return -EINVAL;
+
+	if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC, rate))
+		return -ERANGE;
+
+	period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
+	duty_ns = min(state->duty_cycle, period_ns);
+
+	/*
+	 * period_ns is 1G or less. As long as rate is less than 16 GHz,
+	 * period_rate does not overflow. Make that explicit.
+	 */
+	if (rate > 16ULL * GIGA)
+		return -EINVAL;
+	period_rate = period_ns * rate;
+	best_pre_div = IPQ_PWM_MAX_DIV;
+	best_pwm_div = IPQ_PWM_MAX_DIV;
+	/*
+	 * We don't need to consider pre_div values smaller than
+	 *
+	 *                              period_rate
+	 *  pre_div_min := ------------------------------------
+	 *                 NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1)
+	 *
+	 * because pre_div = pre_div_min results in a better
+	 * approximation.
+	 */
+	pre_div = div64_u64(period_rate,
+			(u64)NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1));
+	min_diff = period_rate;
+
+	for (; pre_div <= IPQ_PWM_MAX_DIV; pre_div++) {
+		u64 remainder;
+
+		pwm_div = div64_u64_rem(period_rate,
+				(u64)NSEC_PER_SEC * (pre_div + 1), &remainder);
+		/* pwm_div is unsigned; the check below catches underflow */
+		pwm_div--;
+
+		/*
+		 * Swapping values for pre_div and pwm_div produces the same
+		 * period length. So we can skip all settings with pre_div >
+		 * pwm_div which results in bigger constraints for selecting
+		 * the duty_cycle than with the two values swapped.
+		 */
+		if (pre_div > pwm_div)
+			break;
+
+		/*
+		 * Make sure we can do 100% duty cycle where
+		 * hi_dur == pwm_div + 1
+		 */
+		if (pwm_div > IPQ_PWM_MAX_DIV - 1)
+			continue;
+
+		if (remainder < min_diff) {
+			best_pre_div = pre_div;
+			best_pwm_div = pwm_div;
+			min_diff = remainder;
+
+			if (min_diff == 0) /* bingo */
+				break;
+		}
+	}
+
+	/* config divider values for the closest possible frequency */
+	config_div_and_duty(pwm, best_pre_div, best_pwm_div,
+			    rate, duty_ns, state->enabled);
+
+	return 0;
+}
+
+static void ipq_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+			      struct pwm_state *state)
+{
+	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
+	unsigned long rate = clk_get_rate(ipq_chip->clk);
+	unsigned int pre_div, pwm_div, hi_dur;
+	u64 effective_div, hi_div;
+	u32 reg0, reg1;
+
+	reg0 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG0);
+	reg1 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG1);
+
+	state->polarity = PWM_POLARITY_NORMAL;
+	state->enabled = reg1 & IPQ_PWM_REG1_ENABLE;
+
+	pwm_div = FIELD_GET(IPQ_PWM_REG0_PWM_DIV, reg0);
+	hi_dur = FIELD_GET(IPQ_PWM_REG0_HI_DURATION, reg0);
+	pre_div = FIELD_GET(IPQ_PWM_REG1_PRE_DIV, reg1);
+
+	/* No overflow here, both pre_div and pwm_div <= 0xffff */
+	effective_div = (u64)(pre_div + 1) * (pwm_div + 1);
+	state->period = DIV64_U64_ROUND_UP(effective_div * NSEC_PER_SEC, rate);
+
+	hi_div = hi_dur * (pre_div + 1);
+	state->duty_cycle = DIV64_U64_ROUND_UP(hi_div * NSEC_PER_SEC, rate);
+}
+
+static const struct pwm_ops ipq_pwm_ops = {
+	.apply = ipq_pwm_apply,
+	.get_state = ipq_pwm_get_state,
+	.owner = THIS_MODULE,
+};
+
+static int ipq_pwm_probe(struct platform_device *pdev)
+{
+	struct ipq_pwm_chip *pwm;
+	struct device *dev = &pdev->dev;
+	int ret;
+
+	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
+	if (!pwm)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, pwm);
+
+	pwm->mem = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(pwm->mem))
+		return dev_err_probe(dev, PTR_ERR(pwm->mem),
+				"regs map failed");
+
+	pwm->clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(pwm->clk))
+		return dev_err_probe(dev, PTR_ERR(pwm->clk),
+				"failed to get clock");
+
+	ret = clk_prepare_enable(pwm->clk);
+	if (ret)
+		return dev_err_probe(dev, ret, "clock enable failed");
+
+	pwm->chip.dev = dev;
+	pwm->chip.ops = &ipq_pwm_ops;
+	pwm->chip.npwm = 4;
+
+	ret = pwmchip_add(&pwm->chip);
+	if (ret < 0) {
+		dev_err_probe(dev, ret, "pwmchip_add() failed\n");
+		clk_disable_unprepare(pwm->clk);
+	}
+
+	return ret;
+}
+
+static int ipq_pwm_remove(struct platform_device *pdev)
+{
+	struct ipq_pwm_chip *pwm = platform_get_drvdata(pdev);
+
+	pwmchip_remove(&pwm->chip);
+	clk_disable_unprepare(pwm->clk);
+
+	return 0;
+}
+
+static const struct of_device_id pwm_ipq_dt_match[] = {
+	{ .compatible = "qcom,ipq6018-pwm", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match);
+
+static struct platform_driver ipq_pwm_driver = {
+	.driver = {
+		.name = "ipq-pwm",
+		.of_match_table = pwm_ipq_dt_match,
+	},
+	.probe = ipq_pwm_probe,
+	.remove = ipq_pwm_remove,
+};
+
+module_platform_driver(ipq_pwm_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
-- 
2.34.1


_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel

^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block
@ 2022-02-07  9:30 ` Baruch Siach
  0 siblings, 0 replies; 34+ messages in thread
From: Baruch Siach @ 2022-02-07  9:30 UTC (permalink / raw)
  To: Thierry Reding, Uwe Kleine-König, Andy Gross, Bjorn Andersson
  Cc: Baruch Siach, Balaji Prakash J, Rob Herring, Robert Marko,
	Kathiravan T, linux-pwm, devicetree, linux-arm-msm,
	linux-arm-kernel

From: Baruch Siach <baruch.siach@siklu.com>

Driver for the PWM block in Qualcomm IPQ6018 line of SoCs. Based on
driver from downstream Codeaurora kernel tree. Removed support for older
(V1) variants because I have no access to that hardware.

Tested on IPQ6010 based hardware.

Signed-off-by: Baruch Siach <baruch.siach@siklu.com>
---
v11:

Address comment from Uwe Kleine-König:

  Drop redundant registers field comments

  Fix period limit check in .apply

  Clarify the comment explaining skip of pre_div > pwm_div values

  Add explicit check for clock rate within limit

  Add comment explaining the selection of initial pre_div

  Use pwm_div division with remainder instead of separate diff calculation

  Round up duty_cycle calculation in .get_state

v10:

  Restore round up in pwm_div calculation; otherwise diff is always <=
  0, so only bingo match works

  Don't overwrite min_diff on every loop iteration

v9:

Address comment from Uwe Kleine-König:

  Use period_ns*rate in dividers calculation for better accuracy

  Round down pre_div and pwm_div

  Add a comment explaining why pwm_div can't underflow

  Add a comment explaining why pre_div > pwm_div end the search loop

  Drop 'CFG_' from register macros

  Rename to_ipq_pwm_chip() to ipq_pwm_from_chip()

  Change bare 'unsigned' to 'unsigned int'

  Clarify the comment on separate REG1 write for enable/disable

  Round up the period value in .get_state

  Use direct readl/writel so no need to check for regmap errors

v7:

  Change 'offset' to 'reg' for the tcsr offset (Rob)

  Drop clock name; there is only one clock (Bjorn)

  Simplify probe failure code path (Bjorn)

v6:

Address Uwe Kleine-König review comments:

  Drop IPQ_PWM_MAX_DEVICES

  Rely on assigned-clock-rates; drop IPQ_PWM_CLK_SRC_FREQ

  Simplify register offset calculation

  Calculate duty cycle more precisely

  Refuse to set inverted polarity

  Drop redundant IPQ_PWM_REG1_ENABLE bit clear

  Remove x1000 factor in pwm_div calculation, use rate directly, and round up

  Choose initial pre_div such that pwm_div < IPQ_PWM_MAX_DIV

  Ensure pre_div <= pwm_div

  Rename close_ to best_

  Explain in comment why effective_div doesn't overflow

  Limit pwm_div to IPQ_PWM_MAX_DIV - 1 to allow 100% duty cycle

  Disable clock only after pwmchip_remove()

  const pwm_ops

Other changes:

  Add missing linux/bitfield.h header include (kernel test robot)

  Adjust code for PWM device node under TCSR (Rob Herring)

v5:

Use &tcsr_q6 syscon to access registers (Bjorn Andersson)

Address Uwe Kleine-König review comments:

  Implement .get_state()

  Add IPQ_PWM_ prefix to local macros

  Use GENMASK/BIT/FIELD_PREP for register fields access

  Make type of config_div_and_duty() parameters consistent

  Derive IPQ_PWM_MIN_PERIOD_NS from IPQ_PWM_CLK_SRC_FREQ

  Integrate enable/disable into config_div_and_duty() to save register read,
  and reduce frequency glitch on update

  Use min() instead of min_t()

  Fix comment format

  Use dev_err_probe() to indicate probe step failure

  Add missing clk_disable_unprepare() in .remove

  Don't set .owner

v4:

  Use div64_u64() to fix link for 32-bit targets ((kernel test robot
  <lkp@intel.com>, Uwe Kleine-König)

v3:

  s/qcom,pwm-ipq6018/qcom,ipq6018-pwm/ (Rob Herring)

  Fix integer overflow on 32-bit targets (kernel test robot <lkp@intel.com>)

v2:

Address Uwe Kleine-König review comments:

  Fix period calculation when out of range

  Don't set period larger than requested

  Remove PWM disable on configuration change

  Implement .apply instead of non-atomic .config/.enable/.disable

  Don't modify PWM on .request/.free

  Check pwm_div underflow

  Fix various code and comment formatting issues

Other changes:

  Use u64 divisor safe division

  Remove now empty .request/.free
---
 drivers/pwm/Kconfig   |  12 ++
 drivers/pwm/Makefile  |   1 +
 drivers/pwm/pwm-ipq.c | 281 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 294 insertions(+)
 create mode 100644 drivers/pwm/pwm-ipq.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 21e3b05a5153..e39718137ecd 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -260,6 +260,18 @@ config PWM_INTEL_LGM
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-intel-lgm.
 
+config PWM_IPQ
+	tristate "IPQ PWM support"
+	depends on ARCH_QCOM || COMPILE_TEST
+	depends on HAVE_CLK && HAS_IOMEM
+	help
+	  Generic PWM framework driver for IPQ PWM block which supports
+	  4 pwm channels. Each of the these channels can be configured
+	  independent of each other.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called pwm-ipq.
+
 config PWM_IQS620A
 	tristate "Azoteq IQS620A PWM support"
 	depends on MFD_IQS62X || COMPILE_TEST
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 708840b7fba8..7402feae4b36 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_PWM_IMX1)		+= pwm-imx1.o
 obj-$(CONFIG_PWM_IMX27)		+= pwm-imx27.o
 obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
 obj-$(CONFIG_PWM_INTEL_LGM)	+= pwm-intel-lgm.o
+obj-$(CONFIG_PWM_IPQ)		+= pwm-ipq.o
 obj-$(CONFIG_PWM_IQS620A)	+= pwm-iqs620a.o
 obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
 obj-$(CONFIG_PWM_KEEMBAY)	+= pwm-keembay.o
diff --git a/drivers/pwm/pwm-ipq.c b/drivers/pwm/pwm-ipq.c
new file mode 100644
index 000000000000..994027290bcb
--- /dev/null
+++ b/drivers/pwm/pwm-ipq.c
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
+/*
+ * Copyright (c) 2016-2017, 2020 The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/math64.h>
+#include <linux/of_device.h>
+#include <linux/bitfield.h>
+#include <linux/units.h>
+
+/* The frequency range supported is 1 Hz to clock rate */
+#define IPQ_PWM_MAX_PERIOD_NS	((u64)NSEC_PER_SEC)
+
+/*
+ * The max value specified for each field is based on the number of bits
+ * in the pwm control register for that field
+ */
+#define IPQ_PWM_MAX_DIV		0xFFFF
+
+/*
+ * Two 32-bit registers for each PWM: REG0, and REG1.
+ * Base offset for PWM #i is at 8 * #i.
+ */
+#define IPQ_PWM_REG0			0
+#define IPQ_PWM_REG0_PWM_DIV		GENMASK(15, 0)
+#define IPQ_PWM_REG0_HI_DURATION	GENMASK(31, 16)
+
+#define IPQ_PWM_REG1			4
+#define IPQ_PWM_REG1_PRE_DIV		GENMASK(15, 0)
+/*
+ * Enable bit is set to enable output toggling in pwm device.
+ * Update bit is set to reflect the changed divider and high duration
+ * values in register.
+ */
+#define IPQ_PWM_REG1_UPDATE		BIT(30)
+#define IPQ_PWM_REG1_ENABLE		BIT(31)
+
+
+struct ipq_pwm_chip {
+	struct pwm_chip chip;
+	struct clk *clk;
+	void __iomem *mem;
+};
+
+static struct ipq_pwm_chip *ipq_pwm_from_chip(struct pwm_chip *chip)
+{
+	return container_of(chip, struct ipq_pwm_chip, chip);
+}
+
+static unsigned int ipq_pwm_reg_read(struct pwm_device *pwm, unsigned int reg)
+{
+	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
+	unsigned int off = 8 * pwm->hwpwm + reg;
+
+	return readl(ipq_chip->mem + off);
+}
+
+static void ipq_pwm_reg_write(struct pwm_device *pwm, unsigned int reg,
+			      unsigned int val)
+{
+	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(pwm->chip);
+	unsigned int off = 8 * pwm->hwpwm + reg;
+
+	writel(val, ipq_chip->mem + off);
+}
+
+static void config_div_and_duty(struct pwm_device *pwm, unsigned int pre_div,
+			unsigned int pwm_div, unsigned long rate, u64 duty_ns,
+			bool enable)
+{
+	unsigned long hi_dur;
+	unsigned long val = 0;
+
+	/*
+	 * high duration = pwm duty * (pwm div + 1)
+	 * pwm duty = duty_ns / period_ns
+	 */
+	hi_dur = div64_u64(duty_ns * rate, (pre_div + 1) * NSEC_PER_SEC);
+
+	val = FIELD_PREP(IPQ_PWM_REG0_HI_DURATION, hi_dur) |
+		FIELD_PREP(IPQ_PWM_REG0_PWM_DIV, pwm_div);
+	ipq_pwm_reg_write(pwm, IPQ_PWM_REG0, val);
+
+	val = FIELD_PREP(IPQ_PWM_REG1_PRE_DIV, pre_div);
+	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
+
+	/* PWM enable toggle needs a separate write to REG1 */
+	val |= IPQ_PWM_REG1_UPDATE;
+	if (enable)
+		val |= IPQ_PWM_REG1_ENABLE;
+	ipq_pwm_reg_write(pwm, IPQ_PWM_REG1, val);
+}
+
+static int ipq_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+			 const struct pwm_state *state)
+{
+	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
+	unsigned int pre_div, pwm_div, best_pre_div, best_pwm_div;
+	unsigned long rate = clk_get_rate(ipq_chip->clk);
+	u64 period_ns, duty_ns, period_rate;
+	u64 min_diff;
+
+	if (state->polarity != PWM_POLARITY_NORMAL)
+		return -EINVAL;
+
+	if (state->period < DIV64_U64_ROUND_UP(NSEC_PER_SEC, rate))
+		return -ERANGE;
+
+	period_ns = min(state->period, IPQ_PWM_MAX_PERIOD_NS);
+	duty_ns = min(state->duty_cycle, period_ns);
+
+	/*
+	 * period_ns is 1G or less. As long as rate is less than 16 GHz,
+	 * period_rate does not overflow. Make that explicit.
+	 */
+	if (rate > 16ULL * GIGA)
+		return -EINVAL;
+	period_rate = period_ns * rate;
+	best_pre_div = IPQ_PWM_MAX_DIV;
+	best_pwm_div = IPQ_PWM_MAX_DIV;
+	/*
+	 * We don't need to consider pre_div values smaller than
+	 *
+	 *                              period_rate
+	 *  pre_div_min := ------------------------------------
+	 *                 NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1)
+	 *
+	 * because pre_div = pre_div_min results in a better
+	 * approximation.
+	 */
+	pre_div = div64_u64(period_rate,
+			(u64)NSEC_PER_SEC * (IPQ_PWM_MAX_DIV + 1));
+	min_diff = period_rate;
+
+	for (; pre_div <= IPQ_PWM_MAX_DIV; pre_div++) {
+		u64 remainder;
+
+		pwm_div = div64_u64_rem(period_rate,
+				(u64)NSEC_PER_SEC * (pre_div + 1), &remainder);
+		/* pwm_div is unsigned; the check below catches underflow */
+		pwm_div--;
+
+		/*
+		 * Swapping values for pre_div and pwm_div produces the same
+		 * period length. So we can skip all settings with pre_div >
+		 * pwm_div which results in bigger constraints for selecting
+		 * the duty_cycle than with the two values swapped.
+		 */
+		if (pre_div > pwm_div)
+			break;
+
+		/*
+		 * Make sure we can do 100% duty cycle where
+		 * hi_dur == pwm_div + 1
+		 */
+		if (pwm_div > IPQ_PWM_MAX_DIV - 1)
+			continue;
+
+		if (remainder < min_diff) {
+			best_pre_div = pre_div;
+			best_pwm_div = pwm_div;
+			min_diff = remainder;
+
+			if (min_diff == 0) /* bingo */
+				break;
+		}
+	}
+
+	/* config divider values for the closest possible frequency */
+	config_div_and_duty(pwm, best_pre_div, best_pwm_div,
+			    rate, duty_ns, state->enabled);
+
+	return 0;
+}
+
+static void ipq_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+			      struct pwm_state *state)
+{
+	struct ipq_pwm_chip *ipq_chip = ipq_pwm_from_chip(chip);
+	unsigned long rate = clk_get_rate(ipq_chip->clk);
+	unsigned int pre_div, pwm_div, hi_dur;
+	u64 effective_div, hi_div;
+	u32 reg0, reg1;
+
+	reg0 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG0);
+	reg1 = ipq_pwm_reg_read(pwm, IPQ_PWM_REG1);
+
+	state->polarity = PWM_POLARITY_NORMAL;
+	state->enabled = reg1 & IPQ_PWM_REG1_ENABLE;
+
+	pwm_div = FIELD_GET(IPQ_PWM_REG0_PWM_DIV, reg0);
+	hi_dur = FIELD_GET(IPQ_PWM_REG0_HI_DURATION, reg0);
+	pre_div = FIELD_GET(IPQ_PWM_REG1_PRE_DIV, reg1);
+
+	/* No overflow here, both pre_div and pwm_div <= 0xffff */
+	effective_div = (u64)(pre_div + 1) * (pwm_div + 1);
+	state->period = DIV64_U64_ROUND_UP(effective_div * NSEC_PER_SEC, rate);
+
+	hi_div = hi_dur * (pre_div + 1);
+	state->duty_cycle = DIV64_U64_ROUND_UP(hi_div * NSEC_PER_SEC, rate);
+}
+
+static const struct pwm_ops ipq_pwm_ops = {
+	.apply = ipq_pwm_apply,
+	.get_state = ipq_pwm_get_state,
+	.owner = THIS_MODULE,
+};
+
+static int ipq_pwm_probe(struct platform_device *pdev)
+{
+	struct ipq_pwm_chip *pwm;
+	struct device *dev = &pdev->dev;
+	int ret;
+
+	pwm = devm_kzalloc(dev, sizeof(*pwm), GFP_KERNEL);
+	if (!pwm)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, pwm);
+
+	pwm->mem = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(pwm->mem))
+		return dev_err_probe(dev, PTR_ERR(pwm->mem),
+				"regs map failed");
+
+	pwm->clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(pwm->clk))
+		return dev_err_probe(dev, PTR_ERR(pwm->clk),
+				"failed to get clock");
+
+	ret = clk_prepare_enable(pwm->clk);
+	if (ret)
+		return dev_err_probe(dev, ret, "clock enable failed");
+
+	pwm->chip.dev = dev;
+	pwm->chip.ops = &ipq_pwm_ops;
+	pwm->chip.npwm = 4;
+
+	ret = pwmchip_add(&pwm->chip);
+	if (ret < 0) {
+		dev_err_probe(dev, ret, "pwmchip_add() failed\n");
+		clk_disable_unprepare(pwm->clk);
+	}
+
+	return ret;
+}
+
+static int ipq_pwm_remove(struct platform_device *pdev)
+{
+	struct ipq_pwm_chip *pwm = platform_get_drvdata(pdev);
+
+	pwmchip_remove(&pwm->chip);
+	clk_disable_unprepare(pwm->clk);
+
+	return 0;
+}
+
+static const struct of_device_id pwm_ipq_dt_match[] = {
+	{ .compatible = "qcom,ipq6018-pwm", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, pwm_ipq_dt_match);
+
+static struct platform_driver ipq_pwm_driver = {
+	.driver = {
+		.name = "ipq-pwm",
+		.of_match_table = pwm_ipq_dt_match,
+	},
+	.probe = ipq_pwm_probe,
+	.remove = ipq_pwm_remove,
+};
+
+module_platform_driver(ipq_pwm_driver);
+
+MODULE_LICENSE("Dual BSD/GPL");
-- 
2.34.1


^ permalink raw reply related	[flat|nested] 34+ messages in thread

end of thread, other threads:[~2023-09-22 10:57 UTC | newest]

Thread overview: 34+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-05-19  7:48 [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block Baruch Siach
2021-05-19  7:48 ` Baruch Siach
2021-05-19  7:48 ` [PATCH 2/3] dt-bindings: pwm: add IPQ6018 binding Baruch Siach
2021-05-19  7:48   ` Baruch Siach
2021-05-21  1:34   ` Rob Herring
2021-05-21  1:34     ` Rob Herring
2021-05-19  7:48 ` [PATCH 3/3] arm64: dts: ipq6018: add pwm node Baruch Siach
2021-05-19  7:48   ` Baruch Siach
2021-05-22 21:35 ` [PATCH 1/3] pwm: driver for qualcomm ipq6018 pwm block Uwe Kleine-König
2021-05-22 21:35   ` Uwe Kleine-König
2021-05-23 15:54   ` Baruch Siach
2021-05-23 15:54     ` Baruch Siach
2021-05-22 21:37 ` Uwe Kleine-König
2021-05-22 21:37   ` Uwe Kleine-König
2022-02-07  9:30 Baruch Siach
2022-02-07  9:30 ` Baruch Siach
2022-02-07 20:22 ` kernel test robot
2022-02-07 20:22   ` kernel test robot
2022-02-08  6:51   ` Baruch Siach
2022-02-08  6:51     ` Baruch Siach
2022-02-08 18:47     ` Nathan Chancellor
2022-02-08 18:47       ` Nathan Chancellor
2023-09-15  6:25 ` Devi Priya
2023-09-15  6:25   ` Devi Priya
2023-09-15  6:36   ` Baruch Siach
2023-09-15  6:36     ` Baruch Siach
2023-09-20  4:58     ` Devi Priya
2023-09-20  4:58       ` Devi Priya
2023-09-22  6:00     ` Devi Priya
2023-09-22  6:00       ` Devi Priya
2023-09-22  8:35       ` Baruch Siach
2023-09-22  8:35         ` Baruch Siach
2023-09-22 10:56         ` Devi Priya
2023-09-22 10:56           ` Devi Priya

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.