All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH V3 0/5] Add i.MX7ULP EVK PWM backlight support
@ 2019-03-14  6:27 ` Anson Huang
  0 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14  6:27 UTC (permalink / raw)
  To: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree, linux-arm-kernel,
	linux-kernel, u.kleine-koenig
  Cc: dl-linux-imx

i.MX7ULP EVK board has MIPI-DSI display, its backlight is supplied
by TPM PWM module, this patch set enables i.MX7ULP TPM PWM driver
support and also add backlight support for MIPI-DSI display.

Anson Huang (5):
  dt-bindings: pwm: Add i.MX TPM PWM binding
  pwm: Add i.MX TPM PWM driver support
  ARM: imx_v6_v7_defconfig: Add TPM PWM support by default
  ARM: dts: imx7ulp: Add pwm0 support
  ARM: dts: imx7ulp-evk: Add backlight support

 .../devicetree/bindings/pwm/imx-tpm-pwm.txt        |  23 ++
 arch/arm/boot/dts/imx7ulp-evk.dts                  |  20 ++
 arch/arm/boot/dts/imx7ulp.dtsi                     |  11 +
 arch/arm/configs/imx_v6_v7_defconfig               |   1 +
 drivers/pwm/Kconfig                                |   9 +
 drivers/pwm/Makefile                               |   1 +
 drivers/pwm/pwm-imx-tpm.c                          | 277 +++++++++++++++++++++
 7 files changed, 342 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt
 create mode 100644 drivers/pwm/pwm-imx-tpm.c

-- 
2.7.4


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

* [PATCH V3 0/5] Add i.MX7ULP EVK PWM backlight support
@ 2019-03-14  6:27 ` Anson Huang
  0 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14  6:27 UTC (permalink / raw)
  To: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree
  Cc: dl-linux-imx

i.MX7ULP EVK board has MIPI-DSI display, its backlight is supplied
by TPM PWM module, this patch set enables i.MX7ULP TPM PWM driver
support and also add backlight support for MIPI-DSI display.

Anson Huang (5):
  dt-bindings: pwm: Add i.MX TPM PWM binding
  pwm: Add i.MX TPM PWM driver support
  ARM: imx_v6_v7_defconfig: Add TPM PWM support by default
  ARM: dts: imx7ulp: Add pwm0 support
  ARM: dts: imx7ulp-evk: Add backlight support

 .../devicetree/bindings/pwm/imx-tpm-pwm.txt        |  23 ++
 arch/arm/boot/dts/imx7ulp-evk.dts                  |  20 ++
 arch/arm/boot/dts/imx7ulp.dtsi                     |  11 +
 arch/arm/configs/imx_v6_v7_defconfig               |   1 +
 drivers/pwm/Kconfig                                |   9 +
 drivers/pwm/Makefile                               |   1 +
 drivers/pwm/pwm-imx-tpm.c                          | 277 +++++++++++++++++++++
 7 files changed, 342 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt
 create mode 100644 drivers/pwm/pwm-imx-tpm.c

-- 
2.7.4

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

* [PATCH V3 0/5] Add i.MX7ULP EVK PWM backlight support
@ 2019-03-14  6:27 ` Anson Huang
  0 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14  6:27 UTC (permalink / raw)
  To: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree, linux-arm-kernel,
	linux-kernel, u.kleine-koenig
  Cc: dl-linux-imx

i.MX7ULP EVK board has MIPI-DSI display, its backlight is supplied
by TPM PWM module, this patch set enables i.MX7ULP TPM PWM driver
support and also add backlight support for MIPI-DSI display.

Anson Huang (5):
  dt-bindings: pwm: Add i.MX TPM PWM binding
  pwm: Add i.MX TPM PWM driver support
  ARM: imx_v6_v7_defconfig: Add TPM PWM support by default
  ARM: dts: imx7ulp: Add pwm0 support
  ARM: dts: imx7ulp-evk: Add backlight support

 .../devicetree/bindings/pwm/imx-tpm-pwm.txt        |  23 ++
 arch/arm/boot/dts/imx7ulp-evk.dts                  |  20 ++
 arch/arm/boot/dts/imx7ulp.dtsi                     |  11 +
 arch/arm/configs/imx_v6_v7_defconfig               |   1 +
 drivers/pwm/Kconfig                                |   9 +
 drivers/pwm/Makefile                               |   1 +
 drivers/pwm/pwm-imx-tpm.c                          | 277 +++++++++++++++++++++
 7 files changed, 342 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt
 create mode 100644 drivers/pwm/pwm-imx-tpm.c

-- 
2.7.4


_______________________________________________
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] 30+ messages in thread

* [PATCH V3 1/5] dt-bindings: pwm: Add i.MX TPM PWM binding
  2019-03-14  6:27 ` Anson Huang
  (?)
@ 2019-03-14  6:27   ` Anson Huang
  -1 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14  6:27 UTC (permalink / raw)
  To: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree, linux-arm-kernel,
	linux-kernel, u.kleine-koenig
  Cc: dl-linux-imx

Add i.MX TPM(Low Power Timer/Pulse Width Modulation Module) PWM binding.

Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
---
No changes since V2.
---
 Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt

diff --git a/Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt b/Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt
new file mode 100644
index 0000000..d47b8fb
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt
@@ -0,0 +1,19 @@
+Freescale i.MX TPM PWM controller
+
+Required properties:
+- compatible : Should be "fsl,imx-tpm-pwm".
+- reg: Physical base address and length of the controller's registers.
+- #pwm-cells: Should be 2. See pwm.txt in this directory for a description of the cells format.
+- clocks : The clock provided by the SoC to drive the PWM.
+- interrupts: The interrupt for the pwm controller.
+
+Example:
+
+pwm0: tpm@40250000 {
+	compatible = "fsl,imx-tpm-pwm";
+	reg = <0x40250000 0x1000>;
+	assigned-clocks = <&clks IMX7ULP_CLK_LPTPM4>;
+	assigned-clock-parents = <&clks IMX7ULP_CLK_SOSC_BUS_CLK>;
+	clocks = <&clks IMX7ULP_CLK_LPTPM4>;
+	#pwm-cells = <2>;
+};
-- 
2.7.4


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

* [PATCH V3 1/5] dt-bindings: pwm: Add i.MX TPM PWM binding
@ 2019-03-14  6:27   ` Anson Huang
  0 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14  6:27 UTC (permalink / raw)
  To: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree, linux-arm-kernel
  Cc: dl-linux-imx

Add i.MX TPM(Low Power Timer/Pulse Width Modulation Module) PWM binding.

Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
---
No changes since V2.
---
 Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt

diff --git a/Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt b/Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt
new file mode 100644
index 0000000..d47b8fb
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt
@@ -0,0 +1,19 @@
+Freescale i.MX TPM PWM controller
+
+Required properties:
+- compatible : Should be "fsl,imx-tpm-pwm".
+- reg: Physical base address and length of the controller's registers.
+- #pwm-cells: Should be 2. See pwm.txt in this directory for a description of the cells format.
+- clocks : The clock provided by the SoC to drive the PWM.
+- interrupts: The interrupt for the pwm controller.
+
+Example:
+
+pwm0: tpm@40250000 {
+	compatible = "fsl,imx-tpm-pwm";
+	reg = <0x40250000 0x1000>;
+	assigned-clocks = <&clks IMX7ULP_CLK_LPTPM4>;
+	assigned-clock-parents = <&clks IMX7ULP_CLK_SOSC_BUS_CLK>;
+	clocks = <&clks IMX7ULP_CLK_LPTPM4>;
+	#pwm-cells = <2>;
+};
-- 
2.7.4

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

* [PATCH V3 1/5] dt-bindings: pwm: Add i.MX TPM PWM binding
@ 2019-03-14  6:27   ` Anson Huang
  0 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14  6:27 UTC (permalink / raw)
  To: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree, linux-arm-kernel,
	linux-kernel, u.kleine-koenig
  Cc: dl-linux-imx

Add i.MX TPM(Low Power Timer/Pulse Width Modulation Module) PWM binding.

Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
---
No changes since V2.
---
 Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt

diff --git a/Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt b/Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt
new file mode 100644
index 0000000..d47b8fb
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/imx-tpm-pwm.txt
@@ -0,0 +1,19 @@
+Freescale i.MX TPM PWM controller
+
+Required properties:
+- compatible : Should be "fsl,imx-tpm-pwm".
+- reg: Physical base address and length of the controller's registers.
+- #pwm-cells: Should be 2. See pwm.txt in this directory for a description of the cells format.
+- clocks : The clock provided by the SoC to drive the PWM.
+- interrupts: The interrupt for the pwm controller.
+
+Example:
+
+pwm0: tpm@40250000 {
+	compatible = "fsl,imx-tpm-pwm";
+	reg = <0x40250000 0x1000>;
+	assigned-clocks = <&clks IMX7ULP_CLK_LPTPM4>;
+	assigned-clock-parents = <&clks IMX7ULP_CLK_SOSC_BUS_CLK>;
+	clocks = <&clks IMX7ULP_CLK_LPTPM4>;
+	#pwm-cells = <2>;
+};
-- 
2.7.4


_______________________________________________
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] 30+ messages in thread

* [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support
  2019-03-14  6:27 ` Anson Huang
  (?)
@ 2019-03-14  6:27   ` Anson Huang
  -1 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14  6:27 UTC (permalink / raw)
  To: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree, linux-arm-kernel,
	linux-kernel, u.kleine-koenig
  Cc: dl-linux-imx

i.MX7ULP has TPM(Low Power Timer/Pulse Width Modulation Module)
inside, add TPM PWM driver support.

Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
---
Changes since V2:
	- Add "IMX_" as prefix to macro define as TPM is already used;
	- Use macro define for channel registers address instead of calculation in everywhere.
	- Add limitations statement in copyright.
---
 drivers/pwm/Kconfig       |  10 ++
 drivers/pwm/Makefile      |   1 +
 drivers/pwm/pwm-imx-tpm.c | 343 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 354 insertions(+)
 create mode 100644 drivers/pwm/pwm-imx-tpm.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index a8f47df..c1cbb43 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -201,6 +201,16 @@ config PWM_IMX
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-imx.
 
+config PWM_IMX_TPM
+	tristate "i.MX TPM PWM support"
+	depends on ARCH_MXC
+	help
+	  Generic PWM framework driver for i.MX7ULP TPM module, TPM's full
+	  name is Low Power Timer/Pulse Width Modulation Module.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called pwm-imx-tpm.
+
 config PWM_JZ4740
 	tristate "Ingenic JZ47xx PWM support"
 	depends on MACH_INGENIC
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 9c676a0..64e036c 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_PWM_FSL_FTM)	+= pwm-fsl-ftm.o
 obj-$(CONFIG_PWM_HIBVT)		+= pwm-hibvt.o
 obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
 obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o
+obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
 obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
 obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
 obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c
new file mode 100644
index 0000000..cdb29f6
--- /dev/null
+++ b/drivers/pwm/pwm-imx-tpm.c
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2018-2019 NXP.
+ *
+ * Limitations:
+ * - The TPM counter and period counter are shared between
+ *   multiple channels, so all channels should use same period
+ *   settings.
+ * - When a channel is disabled, its polarity settings will be
+ *   saved and its output will be disabled by clearing polarity
+ *   setting, when channel is enabled, polarity settings will be
+ *   restored and output will be enabled again.
+ * - ONLY when all channels are disabled, then TPM counter will
+ *   be disabled.
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/log2.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#define IMX_TPM_GLOBAL		0x8
+#define IMX_TPM_SC		0x10
+#define IMX_TPM_CNT		0x14
+#define IMX_TPM_MOD		0x18
+#define IMX_TPM_C0SC		0x20
+#define IMX_TPM_C0V		0x24
+
+#define IMX_TPM_SC_CMOD_SHIFT	3
+#define IMX_TPM_SC_CMOD_MASK	GENMASK(4, 3)
+#define IMX_TPM_SC_CPWMS	BIT(5)
+
+#define IMX_TPM_CnSC_CHF	BIT(7)
+#define IMX_TPM_CnSC_MSnB	BIT(5)
+#define IMX_TPM_CnSC_MSnA	BIT(4)
+#define IMX_TPM_CnSC_ELSnB	BIT(3)
+#define IMX_TPM_CnSC_ELSnA	BIT(2)
+
+#define IMX_TPM_SC_PS_MASK	0x7
+#define IMX_TPM_MOD_MOD_MASK	0xffff
+
+#define IMX_TPM_COUNT_MAX	0xffff
+
+#define IMX_TPM_DEFAULT_PWM_CHANNEL_NUM	2
+
+#define IMX_TPM_CnSC(n)	(IMX_TPM_C0SC + n * 0x8)
+#define IMX_TPM_CnV(n)	(IMX_TPM_C0V + n * 0x8)
+
+struct imx_tpm_pwm_chip {
+	struct pwm_chip chip;
+	struct clk *clk;
+	void __iomem *base;
+	spinlock_t lock;
+	u32 chn_config[IMX_TPM_DEFAULT_PWM_CHANNEL_NUM];
+	bool chn_status[IMX_TPM_DEFAULT_PWM_CHANNEL_NUM];
+};
+
+#define to_imx_tpm_pwm_chip(_chip)	container_of(_chip, struct imx_tpm_pwm_chip, chip)
+
+static void imx_tpm_pwm_config_counter(struct pwm_chip *chip, u32 period)
+{
+	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+	unsigned int period_cnt;
+	u32 val, div;
+	u64 tmp;
+
+	tmp = clk_get_rate(tpm->clk);
+	tmp *= period;
+	val = DIV_ROUND_CLOSEST_ULL(tmp, NSEC_PER_SEC);
+	if (val < IMX_TPM_COUNT_MAX)
+		div = 0;
+	else
+		div = ilog2(roundup_pow_of_two(val / IMX_TPM_COUNT_MAX));
+	if (div > IMX_TPM_SC_PS_MASK) {
+		dev_err(chip->dev,
+			"failed to find valid prescale value!\n");
+		return;
+	}
+	/* set TPM counter prescale */
+	val = readl(tpm->base + IMX_TPM_SC);
+	val &= ~IMX_TPM_SC_PS_MASK;
+	val |= div;
+	writel(val, tpm->base + IMX_TPM_SC);
+
+	/* set period counter */
+	do_div(tmp, NSEC_PER_SEC);
+	period_cnt = DIV_ROUND_CLOSEST_ULL(tmp, 1 << div);
+	writel(period_cnt & IMX_TPM_MOD_MOD_MASK, tpm->base + IMX_TPM_MOD);
+}
+
+static void imx_tpm_pwm_config(struct pwm_chip *chip,
+			       struct pwm_device *pwm,
+			       struct pwm_state *state)
+{
+	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+	static bool tpm_cnt_initialized;
+	unsigned int duty_cnt;
+	u32 val;
+	u64 tmp;
+
+	/*
+	 * TPM counter is shared by multi channels, let's make it to be
+	 * ONLY first channel can config TPM counter's precale and period
+	 * count.
+	 */
+	if (!tpm_cnt_initialized) {
+		imx_tpm_pwm_config_counter(chip, state->period);
+		tpm_cnt_initialized = true;
+	}
+
+	/* set duty counter */
+	tmp = readl(tpm->base + IMX_TPM_MOD) & IMX_TPM_MOD_MOD_MASK;
+	tmp *= state->duty_cycle;
+	duty_cnt = DIV_ROUND_CLOSEST_ULL(tmp, state->period);
+	writel(duty_cnt & IMX_TPM_MOD_MOD_MASK,
+		 tpm->base + IMX_TPM_CnV(pwm->hwpwm));
+
+	/* set polarity */
+	val = readl(tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
+	val &= ~(IMX_TPM_CnSC_ELSnB | IMX_TPM_CnSC_ELSnA | IMX_TPM_CnSC_MSnA);
+	val |= IMX_TPM_CnSC_MSnB;
+	val |= state->polarity ? IMX_TPM_CnSC_ELSnA : IMX_TPM_CnSC_ELSnB;
+	/*
+	 * polarity settings will enabled/disable output statue
+	 * immediately, so here ONLY save the config and will be
+	 * written into register when channel is enabled/disabled.
+	 */
+	tpm->chn_config[pwm->hwpwm] = val;
+}
+
+static void imx_tpm_pwm_enable(struct pwm_chip *chip,
+			       struct pwm_device *pwm,
+			       bool enable)
+{
+	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+	u32 val, i;
+
+	val = readl(tpm->base + IMX_TPM_SC);
+	if (enable) {
+		/* restore channel config */
+		writel(tpm->chn_config[pwm->hwpwm],
+			tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
+
+		/* start TPM counter anyway */
+		val |= 0x1 << IMX_TPM_SC_CMOD_SHIFT;
+		writel(val, tpm->base + IMX_TPM_SC);
+	} else {
+		/* save channel config */
+		tpm->chn_config[pwm->hwpwm] = readl(tpm->base +
+			IMX_TPM_CnSC(pwm->hwpwm));
+		/* disable channel */
+		writel(IMX_TPM_CnSC_CHF, tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
+
+		for (i = 0; i < chip->npwm; i++)
+			if (i != pwm->hwpwm && tpm->chn_status[i])
+				break;
+		if (i == chip->npwm) {
+			/* stop TPM counter since all channels are disabled */
+			val &= ~IMX_TPM_SC_CMOD_MASK;
+			writel(val, tpm->base + IMX_TPM_SC);
+		}
+	}
+
+	/* update channel statue */
+	tpm->chn_status[pwm->hwpwm] = enable;
+}
+
+static void imx_tpm_pwm_get_state(struct pwm_chip *chip,
+				  struct pwm_device *pwm,
+				  struct pwm_state *state)
+{
+	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+	unsigned long rate, flags;
+	u64 tmp;
+	u32 val;
+
+	spin_lock_irqsave(&tpm->lock, flags);
+
+	/* get period */
+	rate = clk_get_rate(tpm->clk);
+	tmp = readl(tpm->base + IMX_TPM_MOD);
+	val = readl(tpm->base + IMX_TPM_SC);
+	val &= IMX_TPM_SC_PS_MASK;
+	tmp *= (1 << val) * NSEC_PER_SEC;
+	state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate);
+
+	/* get duty cycle */
+	tmp = readl(tpm->base + IMX_TPM_CnV(pwm->hwpwm));
+	tmp *= (1 << val) * NSEC_PER_SEC;
+	state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate);
+
+	/* get polarity */
+	val = readl(tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
+	if (val & IMX_TPM_CnSC_ELSnA)
+		state->polarity = PWM_POLARITY_INVERSED;
+	else
+		state->polarity = PWM_POLARITY_NORMAL;
+
+	/* get channel status */
+	state->enabled = tpm->chn_status[pwm->hwpwm] ? true : false;
+
+	spin_unlock_irqrestore(&tpm->lock, flags);
+}
+
+static int imx_tpm_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+			  struct pwm_state *state)
+{
+	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+	struct pwm_state curstate;
+	unsigned long flags;
+
+	imx_tpm_pwm_get_state(chip, pwm, &curstate);
+
+	spin_lock_irqsave(&tpm->lock, flags);
+
+	if (state->period != curstate.period ||
+	    state->duty_cycle != curstate.duty_cycle ||
+	    state->polarity != curstate.polarity)
+		imx_tpm_pwm_config(chip, pwm, state);
+
+	if (state->enabled != curstate.enabled)
+		imx_tpm_pwm_enable(chip, pwm, state->enabled);
+
+	spin_unlock_irqrestore(&tpm->lock, flags);
+
+	return 0;
+}
+
+static const struct pwm_ops imx_tpm_pwm_ops = {
+	.get_state = imx_tpm_pwm_get_state,
+	.apply = imx_tpm_pwm_apply,
+	.owner = THIS_MODULE,
+};
+
+static int imx_tpm_pwm_probe(struct platform_device *pdev)
+{
+	struct imx_tpm_pwm_chip *tpm;
+	struct resource *res;
+	int ret;
+
+	tpm = devm_kzalloc(&pdev->dev, sizeof(*tpm), GFP_KERNEL);
+	if (!tpm)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, tpm);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	tpm->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(tpm->base)) {
+		ret = PTR_ERR(tpm->base);
+		if (ret != -EPROBE_DEFER)
+			dev_err(&pdev->dev, "pwm ioremap failed %d\n", ret);
+		return ret;
+	}
+
+	tpm->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(tpm->clk)) {
+		ret = PTR_ERR(tpm->clk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(&pdev->dev, "failed to get pwm clk %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(tpm->clk);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"failed to prepare or enable clk %d\n", ret);
+		return ret;
+	}
+
+	tpm->chip.dev = &pdev->dev;
+	tpm->chip.ops = &imx_tpm_pwm_ops;
+	tpm->chip.base = -1;
+	tpm->chip.npwm = IMX_TPM_DEFAULT_PWM_CHANNEL_NUM;
+
+	spin_lock_init(&tpm->lock);
+
+	ret = pwmchip_add(&tpm->chip);
+	if (ret)
+		dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret);
+
+	return ret;
+}
+
+static int imx_tpm_pwm_remove(struct platform_device *pdev)
+{
+	struct imx_tpm_pwm_chip *tpm = platform_get_drvdata(pdev);
+
+	return pwmchip_remove(&tpm->chip);
+}
+
+static int __maybe_unused imx_tpm_pwm_suspend(struct device *dev)
+{
+	struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(tpm->clk);
+
+	return 0;
+}
+
+static int __maybe_unused imx_tpm_pwm_resume(struct device *dev)
+{
+	struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
+	int ret = clk_prepare_enable(tpm->clk);
+
+	if (ret)
+		dev_err(dev,
+			"failed to prepare or enable clk %d\n", ret);
+
+	return ret;
+};
+
+static SIMPLE_DEV_PM_OPS(imx_tpm_pwm_pm,
+			 imx_tpm_pwm_suspend, imx_tpm_pwm_resume);
+
+static const struct of_device_id imx_tpm_pwm_dt_ids[] = {
+	{ .compatible = "fsl,imx-tpm-pwm", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_tpm_pwm_dt_ids);
+
+static struct platform_driver imx_tpm_pwm_driver = {
+	.driver = {
+		.name = "imx-tpm-pwm",
+		.of_match_table = imx_tpm_pwm_dt_ids,
+		.pm = &imx_tpm_pwm_pm,
+	},
+	.probe	= imx_tpm_pwm_probe,
+	.remove = imx_tpm_pwm_remove,
+};
+module_platform_driver(imx_tpm_pwm_driver);
+
+MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>");
+MODULE_DESCRIPTION("i.MX TPM PWM Driver");
+MODULE_LICENSE("GPL v2");
-- 
2.7.4


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

* [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support
@ 2019-03-14  6:27   ` Anson Huang
  0 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14  6:27 UTC (permalink / raw)
  To: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree, linux-arm-kernel
  Cc: dl-linux-imx

i.MX7ULP has TPM(Low Power Timer/Pulse Width Modulation Module)
inside, add TPM PWM driver support.

Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
---
Changes since V2:
	- Add "IMX_" as prefix to macro define as TPM is already used;
	- Use macro define for channel registers address instead of calculation in everywhere.
	- Add limitations statement in copyright.
---
 drivers/pwm/Kconfig       |  10 ++
 drivers/pwm/Makefile      |   1 +
 drivers/pwm/pwm-imx-tpm.c | 343 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 354 insertions(+)
 create mode 100644 drivers/pwm/pwm-imx-tpm.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index a8f47df..c1cbb43 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -201,6 +201,16 @@ config PWM_IMX
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-imx.
 
+config PWM_IMX_TPM
+	tristate "i.MX TPM PWM support"
+	depends on ARCH_MXC
+	help
+	  Generic PWM framework driver for i.MX7ULP TPM module, TPM's full
+	  name is Low Power Timer/Pulse Width Modulation Module.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called pwm-imx-tpm.
+
 config PWM_JZ4740
 	tristate "Ingenic JZ47xx PWM support"
 	depends on MACH_INGENIC
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 9c676a0..64e036c 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_PWM_FSL_FTM)	+= pwm-fsl-ftm.o
 obj-$(CONFIG_PWM_HIBVT)		+= pwm-hibvt.o
 obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
 obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o
+obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
 obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
 obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
 obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c
new file mode 100644
index 0000000..cdb29f6
--- /dev/null
+++ b/drivers/pwm/pwm-imx-tpm.c
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2018-2019 NXP.
+ *
+ * Limitations:
+ * - The TPM counter and period counter are shared between
+ *   multiple channels, so all channels should use same period
+ *   settings.
+ * - When a channel is disabled, its polarity settings will be
+ *   saved and its output will be disabled by clearing polarity
+ *   setting, when channel is enabled, polarity settings will be
+ *   restored and output will be enabled again.
+ * - ONLY when all channels are disabled, then TPM counter will
+ *   be disabled.
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/log2.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#define IMX_TPM_GLOBAL		0x8
+#define IMX_TPM_SC		0x10
+#define IMX_TPM_CNT		0x14
+#define IMX_TPM_MOD		0x18
+#define IMX_TPM_C0SC		0x20
+#define IMX_TPM_C0V		0x24
+
+#define IMX_TPM_SC_CMOD_SHIFT	3
+#define IMX_TPM_SC_CMOD_MASK	GENMASK(4, 3)
+#define IMX_TPM_SC_CPWMS	BIT(5)
+
+#define IMX_TPM_CnSC_CHF	BIT(7)
+#define IMX_TPM_CnSC_MSnB	BIT(5)
+#define IMX_TPM_CnSC_MSnA	BIT(4)
+#define IMX_TPM_CnSC_ELSnB	BIT(3)
+#define IMX_TPM_CnSC_ELSnA	BIT(2)
+
+#define IMX_TPM_SC_PS_MASK	0x7
+#define IMX_TPM_MOD_MOD_MASK	0xffff
+
+#define IMX_TPM_COUNT_MAX	0xffff
+
+#define IMX_TPM_DEFAULT_PWM_CHANNEL_NUM	2
+
+#define IMX_TPM_CnSC(n)	(IMX_TPM_C0SC + n * 0x8)
+#define IMX_TPM_CnV(n)	(IMX_TPM_C0V + n * 0x8)
+
+struct imx_tpm_pwm_chip {
+	struct pwm_chip chip;
+	struct clk *clk;
+	void __iomem *base;
+	spinlock_t lock;
+	u32 chn_config[IMX_TPM_DEFAULT_PWM_CHANNEL_NUM];
+	bool chn_status[IMX_TPM_DEFAULT_PWM_CHANNEL_NUM];
+};
+
+#define to_imx_tpm_pwm_chip(_chip)	container_of(_chip, struct imx_tpm_pwm_chip, chip)
+
+static void imx_tpm_pwm_config_counter(struct pwm_chip *chip, u32 period)
+{
+	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+	unsigned int period_cnt;
+	u32 val, div;
+	u64 tmp;
+
+	tmp = clk_get_rate(tpm->clk);
+	tmp *= period;
+	val = DIV_ROUND_CLOSEST_ULL(tmp, NSEC_PER_SEC);
+	if (val < IMX_TPM_COUNT_MAX)
+		div = 0;
+	else
+		div = ilog2(roundup_pow_of_two(val / IMX_TPM_COUNT_MAX));
+	if (div > IMX_TPM_SC_PS_MASK) {
+		dev_err(chip->dev,
+			"failed to find valid prescale value!\n");
+		return;
+	}
+	/* set TPM counter prescale */
+	val = readl(tpm->base + IMX_TPM_SC);
+	val &= ~IMX_TPM_SC_PS_MASK;
+	val |= div;
+	writel(val, tpm->base + IMX_TPM_SC);
+
+	/* set period counter */
+	do_div(tmp, NSEC_PER_SEC);
+	period_cnt = DIV_ROUND_CLOSEST_ULL(tmp, 1 << div);
+	writel(period_cnt & IMX_TPM_MOD_MOD_MASK, tpm->base + IMX_TPM_MOD);
+}
+
+static void imx_tpm_pwm_config(struct pwm_chip *chip,
+			       struct pwm_device *pwm,
+			       struct pwm_state *state)
+{
+	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+	static bool tpm_cnt_initialized;
+	unsigned int duty_cnt;
+	u32 val;
+	u64 tmp;
+
+	/*
+	 * TPM counter is shared by multi channels, let's make it to be
+	 * ONLY first channel can config TPM counter's precale and period
+	 * count.
+	 */
+	if (!tpm_cnt_initialized) {
+		imx_tpm_pwm_config_counter(chip, state->period);
+		tpm_cnt_initialized = true;
+	}
+
+	/* set duty counter */
+	tmp = readl(tpm->base + IMX_TPM_MOD) & IMX_TPM_MOD_MOD_MASK;
+	tmp *= state->duty_cycle;
+	duty_cnt = DIV_ROUND_CLOSEST_ULL(tmp, state->period);
+	writel(duty_cnt & IMX_TPM_MOD_MOD_MASK,
+		 tpm->base + IMX_TPM_CnV(pwm->hwpwm));
+
+	/* set polarity */
+	val = readl(tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
+	val &= ~(IMX_TPM_CnSC_ELSnB | IMX_TPM_CnSC_ELSnA | IMX_TPM_CnSC_MSnA);
+	val |= IMX_TPM_CnSC_MSnB;
+	val |= state->polarity ? IMX_TPM_CnSC_ELSnA : IMX_TPM_CnSC_ELSnB;
+	/*
+	 * polarity settings will enabled/disable output statue
+	 * immediately, so here ONLY save the config and will be
+	 * written into register when channel is enabled/disabled.
+	 */
+	tpm->chn_config[pwm->hwpwm] = val;
+}
+
+static void imx_tpm_pwm_enable(struct pwm_chip *chip,
+			       struct pwm_device *pwm,
+			       bool enable)
+{
+	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+	u32 val, i;
+
+	val = readl(tpm->base + IMX_TPM_SC);
+	if (enable) {
+		/* restore channel config */
+		writel(tpm->chn_config[pwm->hwpwm],
+			tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
+
+		/* start TPM counter anyway */
+		val |= 0x1 << IMX_TPM_SC_CMOD_SHIFT;
+		writel(val, tpm->base + IMX_TPM_SC);
+	} else {
+		/* save channel config */
+		tpm->chn_config[pwm->hwpwm] = readl(tpm->base +
+			IMX_TPM_CnSC(pwm->hwpwm));
+		/* disable channel */
+		writel(IMX_TPM_CnSC_CHF, tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
+
+		for (i = 0; i < chip->npwm; i++)
+			if (i != pwm->hwpwm && tpm->chn_status[i])
+				break;
+		if (i == chip->npwm) {
+			/* stop TPM counter since all channels are disabled */
+			val &= ~IMX_TPM_SC_CMOD_MASK;
+			writel(val, tpm->base + IMX_TPM_SC);
+		}
+	}
+
+	/* update channel statue */
+	tpm->chn_status[pwm->hwpwm] = enable;
+}
+
+static void imx_tpm_pwm_get_state(struct pwm_chip *chip,
+				  struct pwm_device *pwm,
+				  struct pwm_state *state)
+{
+	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+	unsigned long rate, flags;
+	u64 tmp;
+	u32 val;
+
+	spin_lock_irqsave(&tpm->lock, flags);
+
+	/* get period */
+	rate = clk_get_rate(tpm->clk);
+	tmp = readl(tpm->base + IMX_TPM_MOD);
+	val = readl(tpm->base + IMX_TPM_SC);
+	val &= IMX_TPM_SC_PS_MASK;
+	tmp *= (1 << val) * NSEC_PER_SEC;
+	state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate);
+
+	/* get duty cycle */
+	tmp = readl(tpm->base + IMX_TPM_CnV(pwm->hwpwm));
+	tmp *= (1 << val) * NSEC_PER_SEC;
+	state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate);
+
+	/* get polarity */
+	val = readl(tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
+	if (val & IMX_TPM_CnSC_ELSnA)
+		state->polarity = PWM_POLARITY_INVERSED;
+	else
+		state->polarity = PWM_POLARITY_NORMAL;
+
+	/* get channel status */
+	state->enabled = tpm->chn_status[pwm->hwpwm] ? true : false;
+
+	spin_unlock_irqrestore(&tpm->lock, flags);
+}
+
+static int imx_tpm_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+			  struct pwm_state *state)
+{
+	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+	struct pwm_state curstate;
+	unsigned long flags;
+
+	imx_tpm_pwm_get_state(chip, pwm, &curstate);
+
+	spin_lock_irqsave(&tpm->lock, flags);
+
+	if (state->period != curstate.period ||
+	    state->duty_cycle != curstate.duty_cycle ||
+	    state->polarity != curstate.polarity)
+		imx_tpm_pwm_config(chip, pwm, state);
+
+	if (state->enabled != curstate.enabled)
+		imx_tpm_pwm_enable(chip, pwm, state->enabled);
+
+	spin_unlock_irqrestore(&tpm->lock, flags);
+
+	return 0;
+}
+
+static const struct pwm_ops imx_tpm_pwm_ops = {
+	.get_state = imx_tpm_pwm_get_state,
+	.apply = imx_tpm_pwm_apply,
+	.owner = THIS_MODULE,
+};
+
+static int imx_tpm_pwm_probe(struct platform_device *pdev)
+{
+	struct imx_tpm_pwm_chip *tpm;
+	struct resource *res;
+	int ret;
+
+	tpm = devm_kzalloc(&pdev->dev, sizeof(*tpm), GFP_KERNEL);
+	if (!tpm)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, tpm);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	tpm->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(tpm->base)) {
+		ret = PTR_ERR(tpm->base);
+		if (ret != -EPROBE_DEFER)
+			dev_err(&pdev->dev, "pwm ioremap failed %d\n", ret);
+		return ret;
+	}
+
+	tpm->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(tpm->clk)) {
+		ret = PTR_ERR(tpm->clk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(&pdev->dev, "failed to get pwm clk %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(tpm->clk);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"failed to prepare or enable clk %d\n", ret);
+		return ret;
+	}
+
+	tpm->chip.dev = &pdev->dev;
+	tpm->chip.ops = &imx_tpm_pwm_ops;
+	tpm->chip.base = -1;
+	tpm->chip.npwm = IMX_TPM_DEFAULT_PWM_CHANNEL_NUM;
+
+	spin_lock_init(&tpm->lock);
+
+	ret = pwmchip_add(&tpm->chip);
+	if (ret)
+		dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret);
+
+	return ret;
+}
+
+static int imx_tpm_pwm_remove(struct platform_device *pdev)
+{
+	struct imx_tpm_pwm_chip *tpm = platform_get_drvdata(pdev);
+
+	return pwmchip_remove(&tpm->chip);
+}
+
+static int __maybe_unused imx_tpm_pwm_suspend(struct device *dev)
+{
+	struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(tpm->clk);
+
+	return 0;
+}
+
+static int __maybe_unused imx_tpm_pwm_resume(struct device *dev)
+{
+	struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
+	int ret = clk_prepare_enable(tpm->clk);
+
+	if (ret)
+		dev_err(dev,
+			"failed to prepare or enable clk %d\n", ret);
+
+	return ret;
+};
+
+static SIMPLE_DEV_PM_OPS(imx_tpm_pwm_pm,
+			 imx_tpm_pwm_suspend, imx_tpm_pwm_resume);
+
+static const struct of_device_id imx_tpm_pwm_dt_ids[] = {
+	{ .compatible = "fsl,imx-tpm-pwm", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_tpm_pwm_dt_ids);
+
+static struct platform_driver imx_tpm_pwm_driver = {
+	.driver = {
+		.name = "imx-tpm-pwm",
+		.of_match_table = imx_tpm_pwm_dt_ids,
+		.pm = &imx_tpm_pwm_pm,
+	},
+	.probe	= imx_tpm_pwm_probe,
+	.remove = imx_tpm_pwm_remove,
+};
+module_platform_driver(imx_tpm_pwm_driver);
+
+MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>");
+MODULE_DESCRIPTION("i.MX TPM PWM Driver");
+MODULE_LICENSE("GPL v2");
-- 
2.7.4

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

* [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support
@ 2019-03-14  6:27   ` Anson Huang
  0 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14  6:27 UTC (permalink / raw)
  To: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree, linux-arm-kernel,
	linux-kernel, u.kleine-koenig
  Cc: dl-linux-imx

i.MX7ULP has TPM(Low Power Timer/Pulse Width Modulation Module)
inside, add TPM PWM driver support.

Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
---
Changes since V2:
	- Add "IMX_" as prefix to macro define as TPM is already used;
	- Use macro define for channel registers address instead of calculation in everywhere.
	- Add limitations statement in copyright.
---
 drivers/pwm/Kconfig       |  10 ++
 drivers/pwm/Makefile      |   1 +
 drivers/pwm/pwm-imx-tpm.c | 343 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 354 insertions(+)
 create mode 100644 drivers/pwm/pwm-imx-tpm.c

diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index a8f47df..c1cbb43 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -201,6 +201,16 @@ config PWM_IMX
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-imx.
 
+config PWM_IMX_TPM
+	tristate "i.MX TPM PWM support"
+	depends on ARCH_MXC
+	help
+	  Generic PWM framework driver for i.MX7ULP TPM module, TPM's full
+	  name is Low Power Timer/Pulse Width Modulation Module.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called pwm-imx-tpm.
+
 config PWM_JZ4740
 	tristate "Ingenic JZ47xx PWM support"
 	depends on MACH_INGENIC
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 9c676a0..64e036c 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_PWM_FSL_FTM)	+= pwm-fsl-ftm.o
 obj-$(CONFIG_PWM_HIBVT)		+= pwm-hibvt.o
 obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
 obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o
+obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
 obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
 obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
 obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c
new file mode 100644
index 0000000..cdb29f6
--- /dev/null
+++ b/drivers/pwm/pwm-imx-tpm.c
@@ -0,0 +1,343 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2018-2019 NXP.
+ *
+ * Limitations:
+ * - The TPM counter and period counter are shared between
+ *   multiple channels, so all channels should use same period
+ *   settings.
+ * - When a channel is disabled, its polarity settings will be
+ *   saved and its output will be disabled by clearing polarity
+ *   setting, when channel is enabled, polarity settings will be
+ *   restored and output will be enabled again.
+ * - ONLY when all channels are disabled, then TPM counter will
+ *   be disabled.
+ */
+
+#include <linux/bitops.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/log2.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+
+#define IMX_TPM_GLOBAL		0x8
+#define IMX_TPM_SC		0x10
+#define IMX_TPM_CNT		0x14
+#define IMX_TPM_MOD		0x18
+#define IMX_TPM_C0SC		0x20
+#define IMX_TPM_C0V		0x24
+
+#define IMX_TPM_SC_CMOD_SHIFT	3
+#define IMX_TPM_SC_CMOD_MASK	GENMASK(4, 3)
+#define IMX_TPM_SC_CPWMS	BIT(5)
+
+#define IMX_TPM_CnSC_CHF	BIT(7)
+#define IMX_TPM_CnSC_MSnB	BIT(5)
+#define IMX_TPM_CnSC_MSnA	BIT(4)
+#define IMX_TPM_CnSC_ELSnB	BIT(3)
+#define IMX_TPM_CnSC_ELSnA	BIT(2)
+
+#define IMX_TPM_SC_PS_MASK	0x7
+#define IMX_TPM_MOD_MOD_MASK	0xffff
+
+#define IMX_TPM_COUNT_MAX	0xffff
+
+#define IMX_TPM_DEFAULT_PWM_CHANNEL_NUM	2
+
+#define IMX_TPM_CnSC(n)	(IMX_TPM_C0SC + n * 0x8)
+#define IMX_TPM_CnV(n)	(IMX_TPM_C0V + n * 0x8)
+
+struct imx_tpm_pwm_chip {
+	struct pwm_chip chip;
+	struct clk *clk;
+	void __iomem *base;
+	spinlock_t lock;
+	u32 chn_config[IMX_TPM_DEFAULT_PWM_CHANNEL_NUM];
+	bool chn_status[IMX_TPM_DEFAULT_PWM_CHANNEL_NUM];
+};
+
+#define to_imx_tpm_pwm_chip(_chip)	container_of(_chip, struct imx_tpm_pwm_chip, chip)
+
+static void imx_tpm_pwm_config_counter(struct pwm_chip *chip, u32 period)
+{
+	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+	unsigned int period_cnt;
+	u32 val, div;
+	u64 tmp;
+
+	tmp = clk_get_rate(tpm->clk);
+	tmp *= period;
+	val = DIV_ROUND_CLOSEST_ULL(tmp, NSEC_PER_SEC);
+	if (val < IMX_TPM_COUNT_MAX)
+		div = 0;
+	else
+		div = ilog2(roundup_pow_of_two(val / IMX_TPM_COUNT_MAX));
+	if (div > IMX_TPM_SC_PS_MASK) {
+		dev_err(chip->dev,
+			"failed to find valid prescale value!\n");
+		return;
+	}
+	/* set TPM counter prescale */
+	val = readl(tpm->base + IMX_TPM_SC);
+	val &= ~IMX_TPM_SC_PS_MASK;
+	val |= div;
+	writel(val, tpm->base + IMX_TPM_SC);
+
+	/* set period counter */
+	do_div(tmp, NSEC_PER_SEC);
+	period_cnt = DIV_ROUND_CLOSEST_ULL(tmp, 1 << div);
+	writel(period_cnt & IMX_TPM_MOD_MOD_MASK, tpm->base + IMX_TPM_MOD);
+}
+
+static void imx_tpm_pwm_config(struct pwm_chip *chip,
+			       struct pwm_device *pwm,
+			       struct pwm_state *state)
+{
+	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+	static bool tpm_cnt_initialized;
+	unsigned int duty_cnt;
+	u32 val;
+	u64 tmp;
+
+	/*
+	 * TPM counter is shared by multi channels, let's make it to be
+	 * ONLY first channel can config TPM counter's precale and period
+	 * count.
+	 */
+	if (!tpm_cnt_initialized) {
+		imx_tpm_pwm_config_counter(chip, state->period);
+		tpm_cnt_initialized = true;
+	}
+
+	/* set duty counter */
+	tmp = readl(tpm->base + IMX_TPM_MOD) & IMX_TPM_MOD_MOD_MASK;
+	tmp *= state->duty_cycle;
+	duty_cnt = DIV_ROUND_CLOSEST_ULL(tmp, state->period);
+	writel(duty_cnt & IMX_TPM_MOD_MOD_MASK,
+		 tpm->base + IMX_TPM_CnV(pwm->hwpwm));
+
+	/* set polarity */
+	val = readl(tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
+	val &= ~(IMX_TPM_CnSC_ELSnB | IMX_TPM_CnSC_ELSnA | IMX_TPM_CnSC_MSnA);
+	val |= IMX_TPM_CnSC_MSnB;
+	val |= state->polarity ? IMX_TPM_CnSC_ELSnA : IMX_TPM_CnSC_ELSnB;
+	/*
+	 * polarity settings will enabled/disable output statue
+	 * immediately, so here ONLY save the config and will be
+	 * written into register when channel is enabled/disabled.
+	 */
+	tpm->chn_config[pwm->hwpwm] = val;
+}
+
+static void imx_tpm_pwm_enable(struct pwm_chip *chip,
+			       struct pwm_device *pwm,
+			       bool enable)
+{
+	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+	u32 val, i;
+
+	val = readl(tpm->base + IMX_TPM_SC);
+	if (enable) {
+		/* restore channel config */
+		writel(tpm->chn_config[pwm->hwpwm],
+			tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
+
+		/* start TPM counter anyway */
+		val |= 0x1 << IMX_TPM_SC_CMOD_SHIFT;
+		writel(val, tpm->base + IMX_TPM_SC);
+	} else {
+		/* save channel config */
+		tpm->chn_config[pwm->hwpwm] = readl(tpm->base +
+			IMX_TPM_CnSC(pwm->hwpwm));
+		/* disable channel */
+		writel(IMX_TPM_CnSC_CHF, tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
+
+		for (i = 0; i < chip->npwm; i++)
+			if (i != pwm->hwpwm && tpm->chn_status[i])
+				break;
+		if (i == chip->npwm) {
+			/* stop TPM counter since all channels are disabled */
+			val &= ~IMX_TPM_SC_CMOD_MASK;
+			writel(val, tpm->base + IMX_TPM_SC);
+		}
+	}
+
+	/* update channel statue */
+	tpm->chn_status[pwm->hwpwm] = enable;
+}
+
+static void imx_tpm_pwm_get_state(struct pwm_chip *chip,
+				  struct pwm_device *pwm,
+				  struct pwm_state *state)
+{
+	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+	unsigned long rate, flags;
+	u64 tmp;
+	u32 val;
+
+	spin_lock_irqsave(&tpm->lock, flags);
+
+	/* get period */
+	rate = clk_get_rate(tpm->clk);
+	tmp = readl(tpm->base + IMX_TPM_MOD);
+	val = readl(tpm->base + IMX_TPM_SC);
+	val &= IMX_TPM_SC_PS_MASK;
+	tmp *= (1 << val) * NSEC_PER_SEC;
+	state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate);
+
+	/* get duty cycle */
+	tmp = readl(tpm->base + IMX_TPM_CnV(pwm->hwpwm));
+	tmp *= (1 << val) * NSEC_PER_SEC;
+	state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate);
+
+	/* get polarity */
+	val = readl(tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
+	if (val & IMX_TPM_CnSC_ELSnA)
+		state->polarity = PWM_POLARITY_INVERSED;
+	else
+		state->polarity = PWM_POLARITY_NORMAL;
+
+	/* get channel status */
+	state->enabled = tpm->chn_status[pwm->hwpwm] ? true : false;
+
+	spin_unlock_irqrestore(&tpm->lock, flags);
+}
+
+static int imx_tpm_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+			  struct pwm_state *state)
+{
+	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
+	struct pwm_state curstate;
+	unsigned long flags;
+
+	imx_tpm_pwm_get_state(chip, pwm, &curstate);
+
+	spin_lock_irqsave(&tpm->lock, flags);
+
+	if (state->period != curstate.period ||
+	    state->duty_cycle != curstate.duty_cycle ||
+	    state->polarity != curstate.polarity)
+		imx_tpm_pwm_config(chip, pwm, state);
+
+	if (state->enabled != curstate.enabled)
+		imx_tpm_pwm_enable(chip, pwm, state->enabled);
+
+	spin_unlock_irqrestore(&tpm->lock, flags);
+
+	return 0;
+}
+
+static const struct pwm_ops imx_tpm_pwm_ops = {
+	.get_state = imx_tpm_pwm_get_state,
+	.apply = imx_tpm_pwm_apply,
+	.owner = THIS_MODULE,
+};
+
+static int imx_tpm_pwm_probe(struct platform_device *pdev)
+{
+	struct imx_tpm_pwm_chip *tpm;
+	struct resource *res;
+	int ret;
+
+	tpm = devm_kzalloc(&pdev->dev, sizeof(*tpm), GFP_KERNEL);
+	if (!tpm)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, tpm);
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	tpm->base = devm_ioremap_resource(&pdev->dev, res);
+	if (IS_ERR(tpm->base)) {
+		ret = PTR_ERR(tpm->base);
+		if (ret != -EPROBE_DEFER)
+			dev_err(&pdev->dev, "pwm ioremap failed %d\n", ret);
+		return ret;
+	}
+
+	tpm->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(tpm->clk)) {
+		ret = PTR_ERR(tpm->clk);
+		if (ret != -EPROBE_DEFER)
+			dev_err(&pdev->dev, "failed to get pwm clk %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(tpm->clk);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"failed to prepare or enable clk %d\n", ret);
+		return ret;
+	}
+
+	tpm->chip.dev = &pdev->dev;
+	tpm->chip.ops = &imx_tpm_pwm_ops;
+	tpm->chip.base = -1;
+	tpm->chip.npwm = IMX_TPM_DEFAULT_PWM_CHANNEL_NUM;
+
+	spin_lock_init(&tpm->lock);
+
+	ret = pwmchip_add(&tpm->chip);
+	if (ret)
+		dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret);
+
+	return ret;
+}
+
+static int imx_tpm_pwm_remove(struct platform_device *pdev)
+{
+	struct imx_tpm_pwm_chip *tpm = platform_get_drvdata(pdev);
+
+	return pwmchip_remove(&tpm->chip);
+}
+
+static int __maybe_unused imx_tpm_pwm_suspend(struct device *dev)
+{
+	struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
+
+	clk_disable_unprepare(tpm->clk);
+
+	return 0;
+}
+
+static int __maybe_unused imx_tpm_pwm_resume(struct device *dev)
+{
+	struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
+	int ret = clk_prepare_enable(tpm->clk);
+
+	if (ret)
+		dev_err(dev,
+			"failed to prepare or enable clk %d\n", ret);
+
+	return ret;
+};
+
+static SIMPLE_DEV_PM_OPS(imx_tpm_pwm_pm,
+			 imx_tpm_pwm_suspend, imx_tpm_pwm_resume);
+
+static const struct of_device_id imx_tpm_pwm_dt_ids[] = {
+	{ .compatible = "fsl,imx-tpm-pwm", },
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx_tpm_pwm_dt_ids);
+
+static struct platform_driver imx_tpm_pwm_driver = {
+	.driver = {
+		.name = "imx-tpm-pwm",
+		.of_match_table = imx_tpm_pwm_dt_ids,
+		.pm = &imx_tpm_pwm_pm,
+	},
+	.probe	= imx_tpm_pwm_probe,
+	.remove = imx_tpm_pwm_remove,
+};
+module_platform_driver(imx_tpm_pwm_driver);
+
+MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>");
+MODULE_DESCRIPTION("i.MX TPM PWM Driver");
+MODULE_LICENSE("GPL v2");
-- 
2.7.4


_______________________________________________
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] 30+ messages in thread

* [PATCH V3 3/5] ARM: imx_v6_v7_defconfig: Add TPM PWM support by default
  2019-03-14  6:27 ` Anson Huang
  (?)
@ 2019-03-14  6:27   ` Anson Huang
  -1 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14  6:27 UTC (permalink / raw)
  To: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree, linux-arm-kernel,
	linux-kernel, u.kleine-koenig
  Cc: dl-linux-imx

Select CONFIG_PWM_IMX_TPM by default to support i.MX7ULP
TPM PWM.

Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
---
No changes since V2.
---
 arch/arm/configs/imx_v6_v7_defconfig | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm/configs/imx_v6_v7_defconfig b/arch/arm/configs/imx_v6_v7_defconfig
index 5586a50..57862c6 100644
--- a/arch/arm/configs/imx_v6_v7_defconfig
+++ b/arch/arm/configs/imx_v6_v7_defconfig
@@ -399,6 +399,7 @@ CONFIG_MPL3115=y
 CONFIG_PWM=y
 CONFIG_PWM_FSL_FTM=y
 CONFIG_PWM_IMX=y
+CONFIG_PWM_IMX_TPM=y
 CONFIG_NVMEM_IMX_OCOTP=y
 CONFIG_NVMEM_VF610_OCOTP=y
 CONFIG_TEE=y
-- 
2.7.4


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

* [PATCH V3 3/5] ARM: imx_v6_v7_defconfig: Add TPM PWM support by default
@ 2019-03-14  6:27   ` Anson Huang
  0 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14  6:27 UTC (permalink / raw)
  To: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree, linux-arm-kernel
  Cc: dl-linux-imx

Select CONFIG_PWM_IMX_TPM by default to support i.MX7ULP
TPM PWM.

Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
---
No changes since V2.
---
 arch/arm/configs/imx_v6_v7_defconfig | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm/configs/imx_v6_v7_defconfig b/arch/arm/configs/imx_v6_v7_defconfig
index 5586a50..57862c6 100644
--- a/arch/arm/configs/imx_v6_v7_defconfig
+++ b/arch/arm/configs/imx_v6_v7_defconfig
@@ -399,6 +399,7 @@ CONFIG_MPL3115=y
 CONFIG_PWM=y
 CONFIG_PWM_FSL_FTM=y
 CONFIG_PWM_IMX=y
+CONFIG_PWM_IMX_TPM=y
 CONFIG_NVMEM_IMX_OCOTP=y
 CONFIG_NVMEM_VF610_OCOTP=y
 CONFIG_TEE=y
-- 
2.7.4

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

* [PATCH V3 3/5] ARM: imx_v6_v7_defconfig: Add TPM PWM support by default
@ 2019-03-14  6:27   ` Anson Huang
  0 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14  6:27 UTC (permalink / raw)
  To: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree, linux-arm-kernel,
	linux-kernel, u.kleine-koenig
  Cc: dl-linux-imx

Select CONFIG_PWM_IMX_TPM by default to support i.MX7ULP
TPM PWM.

Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
---
No changes since V2.
---
 arch/arm/configs/imx_v6_v7_defconfig | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm/configs/imx_v6_v7_defconfig b/arch/arm/configs/imx_v6_v7_defconfig
index 5586a50..57862c6 100644
--- a/arch/arm/configs/imx_v6_v7_defconfig
+++ b/arch/arm/configs/imx_v6_v7_defconfig
@@ -399,6 +399,7 @@ CONFIG_MPL3115=y
 CONFIG_PWM=y
 CONFIG_PWM_FSL_FTM=y
 CONFIG_PWM_IMX=y
+CONFIG_PWM_IMX_TPM=y
 CONFIG_NVMEM_IMX_OCOTP=y
 CONFIG_NVMEM_VF610_OCOTP=y
 CONFIG_TEE=y
-- 
2.7.4


_______________________________________________
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] 30+ messages in thread

* [PATCH V3 4/5] ARM: dts: imx7ulp: Add pwm0 support
  2019-03-14  6:27 ` Anson Huang
  (?)
@ 2019-03-14  6:27   ` Anson Huang
  -1 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14  6:27 UTC (permalink / raw)
  To: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree, linux-arm-kernel,
	linux-kernel, u.kleine-koenig
  Cc: dl-linux-imx

Add i.MX7ULP EVK board PWM0 support.

Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
---
No changes since V2.
---
 arch/arm/boot/dts/imx7ulp-evk.dts | 12 ++++++++++++
 arch/arm/boot/dts/imx7ulp.dtsi    | 10 ++++++++++
 2 files changed, 22 insertions(+)

diff --git a/arch/arm/boot/dts/imx7ulp-evk.dts b/arch/arm/boot/dts/imx7ulp-evk.dts
index a09026a..3f5ea18 100644
--- a/arch/arm/boot/dts/imx7ulp-evk.dts
+++ b/arch/arm/boot/dts/imx7ulp-evk.dts
@@ -40,6 +40,12 @@
 	status = "okay";
 };
 
+&pwm0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&pinctrl_pwm0>;
+	status = "okay";
+};
+
 &usdhc0 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&pinctrl_usdhc0>;
@@ -57,6 +63,12 @@
 		bias-pull-up;
 	};
 
+	pinctrl_pwm0: pwm0grp {
+		fsl,pins = <
+			IMX7ULP_PAD_PTF2__TPM4_CH1	0x2
+		>;
+	};
+
 	pinctrl_usdhc0: usdhc0grp {
 		fsl,pins = <
 			IMX7ULP_PAD_PTD1__SDHC0_CMD	0x43
diff --git a/arch/arm/boot/dts/imx7ulp.dtsi b/arch/arm/boot/dts/imx7ulp.dtsi
index fca6e50..6361c4e 100644
--- a/arch/arm/boot/dts/imx7ulp.dtsi
+++ b/arch/arm/boot/dts/imx7ulp.dtsi
@@ -124,6 +124,16 @@
 			status = "disabled";
 		};
 
+		pwm0: pwm@40250000 {
+			compatible = "fsl,imx-tpm-pwm";
+			reg = <0x40250000 0x1000>;
+			assigned-clocks = <&pcc2 IMX7ULP_CLK_LPTPM4>;
+			assigned-clock-parents = <&scg1 IMX7ULP_CLK_SOSC_BUS_CLK>;
+			clocks = <&pcc2 IMX7ULP_CLK_LPTPM4>;
+			#pwm-cells = <2>;
+			status = "disabled";
+		};
+
 		tpm5: tpm@40260000 {
 			compatible = "fsl,imx7ulp-tpm";
 			reg = <0x40260000 0x1000>;
-- 
2.7.4


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

* [PATCH V3 4/5] ARM: dts: imx7ulp: Add pwm0 support
@ 2019-03-14  6:27   ` Anson Huang
  0 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14  6:27 UTC (permalink / raw)
  To: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree
  Cc: dl-linux-imx

Add i.MX7ULP EVK board PWM0 support.

Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
---
No changes since V2.
---
 arch/arm/boot/dts/imx7ulp-evk.dts | 12 ++++++++++++
 arch/arm/boot/dts/imx7ulp.dtsi    | 10 ++++++++++
 2 files changed, 22 insertions(+)

diff --git a/arch/arm/boot/dts/imx7ulp-evk.dts b/arch/arm/boot/dts/imx7ulp-evk.dts
index a09026a..3f5ea18 100644
--- a/arch/arm/boot/dts/imx7ulp-evk.dts
+++ b/arch/arm/boot/dts/imx7ulp-evk.dts
@@ -40,6 +40,12 @@
 	status = "okay";
 };
 
+&pwm0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&pinctrl_pwm0>;
+	status = "okay";
+};
+
 &usdhc0 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&pinctrl_usdhc0>;
@@ -57,6 +63,12 @@
 		bias-pull-up;
 	};
 
+	pinctrl_pwm0: pwm0grp {
+		fsl,pins = <
+			IMX7ULP_PAD_PTF2__TPM4_CH1	0x2
+		>;
+	};
+
 	pinctrl_usdhc0: usdhc0grp {
 		fsl,pins = <
 			IMX7ULP_PAD_PTD1__SDHC0_CMD	0x43
diff --git a/arch/arm/boot/dts/imx7ulp.dtsi b/arch/arm/boot/dts/imx7ulp.dtsi
index fca6e50..6361c4e 100644
--- a/arch/arm/boot/dts/imx7ulp.dtsi
+++ b/arch/arm/boot/dts/imx7ulp.dtsi
@@ -124,6 +124,16 @@
 			status = "disabled";
 		};
 
+		pwm0: pwm@40250000 {
+			compatible = "fsl,imx-tpm-pwm";
+			reg = <0x40250000 0x1000>;
+			assigned-clocks = <&pcc2 IMX7ULP_CLK_LPTPM4>;
+			assigned-clock-parents = <&scg1 IMX7ULP_CLK_SOSC_BUS_CLK>;
+			clocks = <&pcc2 IMX7ULP_CLK_LPTPM4>;
+			#pwm-cells = <2>;
+			status = "disabled";
+		};
+
 		tpm5: tpm@40260000 {
 			compatible = "fsl,imx7ulp-tpm";
 			reg = <0x40260000 0x1000>;
-- 
2.7.4

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

* [PATCH V3 4/5] ARM: dts: imx7ulp: Add pwm0 support
@ 2019-03-14  6:27   ` Anson Huang
  0 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14  6:27 UTC (permalink / raw)
  To: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree, linux-arm-kernel,
	linux-kernel, u.kleine-koenig
  Cc: dl-linux-imx

Add i.MX7ULP EVK board PWM0 support.

Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
---
No changes since V2.
---
 arch/arm/boot/dts/imx7ulp-evk.dts | 12 ++++++++++++
 arch/arm/boot/dts/imx7ulp.dtsi    | 10 ++++++++++
 2 files changed, 22 insertions(+)

diff --git a/arch/arm/boot/dts/imx7ulp-evk.dts b/arch/arm/boot/dts/imx7ulp-evk.dts
index a09026a..3f5ea18 100644
--- a/arch/arm/boot/dts/imx7ulp-evk.dts
+++ b/arch/arm/boot/dts/imx7ulp-evk.dts
@@ -40,6 +40,12 @@
 	status = "okay";
 };
 
+&pwm0 {
+	pinctrl-names = "default";
+	pinctrl-0 = <&pinctrl_pwm0>;
+	status = "okay";
+};
+
 &usdhc0 {
 	pinctrl-names = "default";
 	pinctrl-0 = <&pinctrl_usdhc0>;
@@ -57,6 +63,12 @@
 		bias-pull-up;
 	};
 
+	pinctrl_pwm0: pwm0grp {
+		fsl,pins = <
+			IMX7ULP_PAD_PTF2__TPM4_CH1	0x2
+		>;
+	};
+
 	pinctrl_usdhc0: usdhc0grp {
 		fsl,pins = <
 			IMX7ULP_PAD_PTD1__SDHC0_CMD	0x43
diff --git a/arch/arm/boot/dts/imx7ulp.dtsi b/arch/arm/boot/dts/imx7ulp.dtsi
index fca6e50..6361c4e 100644
--- a/arch/arm/boot/dts/imx7ulp.dtsi
+++ b/arch/arm/boot/dts/imx7ulp.dtsi
@@ -124,6 +124,16 @@
 			status = "disabled";
 		};
 
+		pwm0: pwm@40250000 {
+			compatible = "fsl,imx-tpm-pwm";
+			reg = <0x40250000 0x1000>;
+			assigned-clocks = <&pcc2 IMX7ULP_CLK_LPTPM4>;
+			assigned-clock-parents = <&scg1 IMX7ULP_CLK_SOSC_BUS_CLK>;
+			clocks = <&pcc2 IMX7ULP_CLK_LPTPM4>;
+			#pwm-cells = <2>;
+			status = "disabled";
+		};
+
 		tpm5: tpm@40260000 {
 			compatible = "fsl,imx7ulp-tpm";
 			reg = <0x40260000 0x1000>;
-- 
2.7.4


_______________________________________________
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] 30+ messages in thread

* [PATCH V3 5/5] ARM: dts: imx7ulp-evk: Add backlight support
  2019-03-14  6:27 ` Anson Huang
  (?)
@ 2019-03-14  6:27   ` Anson Huang
  -1 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14  6:27 UTC (permalink / raw)
  To: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree, linux-arm-kernel,
	linux-kernel, u.kleine-koenig
  Cc: dl-linux-imx

This patch adds i.MX7ULP EVK board MIPI-DSI backlight
support.

Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
---
No changes since V2.
---
 arch/arm/boot/dts/imx7ulp-evk.dts | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/arch/arm/boot/dts/imx7ulp-evk.dts b/arch/arm/boot/dts/imx7ulp-evk.dts
index 3f5ea18..f90f2f3 100644
--- a/arch/arm/boot/dts/imx7ulp-evk.dts
+++ b/arch/arm/boot/dts/imx7ulp-evk.dts
@@ -22,6 +22,14 @@
 		reg = <0x60000000 0x40000000>;
 	};
 
+	backlight {
+		compatible = "pwm-backlight";
+		pwms = <&pwm0 1 50000>;
+		brightness-levels = <0 20 25 30 35 40 100>;
+		default-brightness-level = <6>;
+		status = "okay";
+	};
+
 	reg_vsd_3v3: regulator-vsd-3v3 {
 		compatible = "regulator-fixed";
 		regulator-name = "VSD_3V3";
-- 
2.7.4


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

* [PATCH V3 5/5] ARM: dts: imx7ulp-evk: Add backlight support
@ 2019-03-14  6:27   ` Anson Huang
  0 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14  6:27 UTC (permalink / raw)
  To: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree
  Cc: dl-linux-imx

This patch adds i.MX7ULP EVK board MIPI-DSI backlight
support.

Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
---
No changes since V2.
---
 arch/arm/boot/dts/imx7ulp-evk.dts | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/arch/arm/boot/dts/imx7ulp-evk.dts b/arch/arm/boot/dts/imx7ulp-evk.dts
index 3f5ea18..f90f2f3 100644
--- a/arch/arm/boot/dts/imx7ulp-evk.dts
+++ b/arch/arm/boot/dts/imx7ulp-evk.dts
@@ -22,6 +22,14 @@
 		reg = <0x60000000 0x40000000>;
 	};
 
+	backlight {
+		compatible = "pwm-backlight";
+		pwms = <&pwm0 1 50000>;
+		brightness-levels = <0 20 25 30 35 40 100>;
+		default-brightness-level = <6>;
+		status = "okay";
+	};
+
 	reg_vsd_3v3: regulator-vsd-3v3 {
 		compatible = "regulator-fixed";
 		regulator-name = "VSD_3V3";
-- 
2.7.4

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

* [PATCH V3 5/5] ARM: dts: imx7ulp-evk: Add backlight support
@ 2019-03-14  6:27   ` Anson Huang
  0 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14  6:27 UTC (permalink / raw)
  To: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree, linux-arm-kernel,
	linux-kernel, u.kleine-koenig
  Cc: dl-linux-imx

This patch adds i.MX7ULP EVK board MIPI-DSI backlight
support.

Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
---
No changes since V2.
---
 arch/arm/boot/dts/imx7ulp-evk.dts | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/arch/arm/boot/dts/imx7ulp-evk.dts b/arch/arm/boot/dts/imx7ulp-evk.dts
index 3f5ea18..f90f2f3 100644
--- a/arch/arm/boot/dts/imx7ulp-evk.dts
+++ b/arch/arm/boot/dts/imx7ulp-evk.dts
@@ -22,6 +22,14 @@
 		reg = <0x60000000 0x40000000>;
 	};
 
+	backlight {
+		compatible = "pwm-backlight";
+		pwms = <&pwm0 1 50000>;
+		brightness-levels = <0 20 25 30 35 40 100>;
+		default-brightness-level = <6>;
+		status = "okay";
+	};
+
 	reg_vsd_3v3: regulator-vsd-3v3 {
 		compatible = "regulator-fixed";
 		regulator-name = "VSD_3V3";
-- 
2.7.4


_______________________________________________
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] 30+ messages in thread

* Re: [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support
  2019-03-14  6:27   ` Anson Huang
  (?)
@ 2019-03-14  9:22     ` Uwe Kleine-König
  -1 siblings, 0 replies; 30+ messages in thread
From: Uwe Kleine-König @ 2019-03-14  9:22 UTC (permalink / raw)
  To: Anson Huang
  Cc: thierry.reding, robh+dt, mark.rutland, shawnguo, s.hauer, kernel,
	festevam, linux, stefan, otavio, Leonard Crestez, schnitzeltony,
	jan.tuerk, Robin Gong, linux-pwm, devicetree, linux-arm-kernel,
	linux-kernel, dl-linux-imx

On Thu, Mar 14, 2019 at 06:27:20AM +0000, Anson Huang wrote:
> i.MX7ULP has TPM(Low Power Timer/Pulse Width Modulation Module)
> inside, add TPM PWM driver support.
> 
> Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
> ---
> Changes since V2:
> 	- Add "IMX_" as prefix to macro define as TPM is already used;
> 	- Use macro define for channel registers address instead of calculation in everywhere.
> 	- Add limitations statement in copyright.
> ---
>  drivers/pwm/Kconfig       |  10 ++
>  drivers/pwm/Makefile      |   1 +
>  drivers/pwm/pwm-imx-tpm.c | 343 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 354 insertions(+)
>  create mode 100644 drivers/pwm/pwm-imx-tpm.c
> 
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index a8f47df..c1cbb43 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -201,6 +201,16 @@ config PWM_IMX
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called pwm-imx.
>  
> +config PWM_IMX_TPM
> +	tristate "i.MX TPM PWM support"
> +	depends on ARCH_MXC
> +	help
> +	  Generic PWM framework driver for i.MX7ULP TPM module, TPM's full
> +	  name is Low Power Timer/Pulse Width Modulation Module.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called pwm-imx-tpm.
> +
>  config PWM_JZ4740
>  	tristate "Ingenic JZ47xx PWM support"
>  	depends on MACH_INGENIC
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 9c676a0..64e036c 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -18,6 +18,7 @@ obj-$(CONFIG_PWM_FSL_FTM)	+= pwm-fsl-ftm.o
>  obj-$(CONFIG_PWM_HIBVT)		+= pwm-hibvt.o
>  obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
>  obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o
> +obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
>  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>  obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
>  obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
> diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c
> new file mode 100644
> index 0000000..cdb29f6
> --- /dev/null
> +++ b/drivers/pwm/pwm-imx-tpm.c
> @@ -0,0 +1,343 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2018-2019 NXP.
> + *
> + * Limitations:
> + * - The TPM counter and period counter are shared between
> + *   multiple channels, so all channels should use same period
> + *   settings.
> + * - When a channel is disabled, its polarity settings will be
> + *   saved and its output will be disabled by clearing polarity
> + *   setting, when channel is enabled, polarity settings will be
> + *   restored and output will be enabled again.
> + * - ONLY when all channels are disabled, then TPM counter will
> + *   be disabled.

I wouldn't list the last two items in this section. These are
implementation details that a PWM consumer doesn't need to bother with.
These are fine to be noted in a code comment at the appropriate place.

Other than that please look at my feedback for the previous series that
I just sent out. I think most things still apply to this version.

Best regards
Uwe

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

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

* Re: [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support
@ 2019-03-14  9:22     ` Uwe Kleine-König
  0 siblings, 0 replies; 30+ messages in thread
From: Uwe Kleine-König @ 2019-03-14  9:22 UTC (permalink / raw)
  To: Anson Huang
  Cc: mark.rutland, linux-pwm, Robin Gong, schnitzeltony, otavio,
	devicetree, festevam, s.hauer, jan.tuerk, linux, robh+dt,
	linux-kernel, thierry.reding, stefan, kernel, Leonard Crestez,
	shawnguo, linux-arm-kernel@lists.infradead.org

On Thu, Mar 14, 2019 at 06:27:20AM +0000, Anson Huang wrote:
> i.MX7ULP has TPM(Low Power Timer/Pulse Width Modulation Module)
> inside, add TPM PWM driver support.
> 
> Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
> ---
> Changes since V2:
> 	- Add "IMX_" as prefix to macro define as TPM is already used;
> 	- Use macro define for channel registers address instead of calculation in everywhere.
> 	- Add limitations statement in copyright.
> ---
>  drivers/pwm/Kconfig       |  10 ++
>  drivers/pwm/Makefile      |   1 +
>  drivers/pwm/pwm-imx-tpm.c | 343 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 354 insertions(+)
>  create mode 100644 drivers/pwm/pwm-imx-tpm.c
> 
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index a8f47df..c1cbb43 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -201,6 +201,16 @@ config PWM_IMX
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called pwm-imx.
>  
> +config PWM_IMX_TPM
> +	tristate "i.MX TPM PWM support"
> +	depends on ARCH_MXC
> +	help
> +	  Generic PWM framework driver for i.MX7ULP TPM module, TPM's full
> +	  name is Low Power Timer/Pulse Width Modulation Module.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called pwm-imx-tpm.
> +
>  config PWM_JZ4740
>  	tristate "Ingenic JZ47xx PWM support"
>  	depends on MACH_INGENIC
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 9c676a0..64e036c 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -18,6 +18,7 @@ obj-$(CONFIG_PWM_FSL_FTM)	+= pwm-fsl-ftm.o
>  obj-$(CONFIG_PWM_HIBVT)		+= pwm-hibvt.o
>  obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
>  obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o
> +obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
>  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>  obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
>  obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
> diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c
> new file mode 100644
> index 0000000..cdb29f6
> --- /dev/null
> +++ b/drivers/pwm/pwm-imx-tpm.c
> @@ -0,0 +1,343 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2018-2019 NXP.
> + *
> + * Limitations:
> + * - The TPM counter and period counter are shared between
> + *   multiple channels, so all channels should use same period
> + *   settings.
> + * - When a channel is disabled, its polarity settings will be
> + *   saved and its output will be disabled by clearing polarity
> + *   setting, when channel is enabled, polarity settings will be
> + *   restored and output will be enabled again.
> + * - ONLY when all channels are disabled, then TPM counter will
> + *   be disabled.

I wouldn't list the last two items in this section. These are
implementation details that a PWM consumer doesn't need to bother with.
These are fine to be noted in a code comment at the appropriate place.

Other than that please look at my feedback for the previous series that
I just sent out. I think most things still apply to this version.

Best regards
Uwe

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

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

* Re: [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support
@ 2019-03-14  9:22     ` Uwe Kleine-König
  0 siblings, 0 replies; 30+ messages in thread
From: Uwe Kleine-König @ 2019-03-14  9:22 UTC (permalink / raw)
  To: Anson Huang
  Cc: mark.rutland, linux-pwm, Robin Gong, schnitzeltony, otavio,
	devicetree, festevam, s.hauer, jan.tuerk, linux, robh+dt,
	linux-kernel, thierry.reding, stefan, kernel, Leonard Crestez,
	shawnguo, linux-arm-kernel, dl-linux-imx

On Thu, Mar 14, 2019 at 06:27:20AM +0000, Anson Huang wrote:
> i.MX7ULP has TPM(Low Power Timer/Pulse Width Modulation Module)
> inside, add TPM PWM driver support.
> 
> Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
> ---
> Changes since V2:
> 	- Add "IMX_" as prefix to macro define as TPM is already used;
> 	- Use macro define for channel registers address instead of calculation in everywhere.
> 	- Add limitations statement in copyright.
> ---
>  drivers/pwm/Kconfig       |  10 ++
>  drivers/pwm/Makefile      |   1 +
>  drivers/pwm/pwm-imx-tpm.c | 343 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 354 insertions(+)
>  create mode 100644 drivers/pwm/pwm-imx-tpm.c
> 
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index a8f47df..c1cbb43 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -201,6 +201,16 @@ config PWM_IMX
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called pwm-imx.
>  
> +config PWM_IMX_TPM
> +	tristate "i.MX TPM PWM support"
> +	depends on ARCH_MXC
> +	help
> +	  Generic PWM framework driver for i.MX7ULP TPM module, TPM's full
> +	  name is Low Power Timer/Pulse Width Modulation Module.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called pwm-imx-tpm.
> +
>  config PWM_JZ4740
>  	tristate "Ingenic JZ47xx PWM support"
>  	depends on MACH_INGENIC
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 9c676a0..64e036c 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -18,6 +18,7 @@ obj-$(CONFIG_PWM_FSL_FTM)	+= pwm-fsl-ftm.o
>  obj-$(CONFIG_PWM_HIBVT)		+= pwm-hibvt.o
>  obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
>  obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o
> +obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
>  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>  obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
>  obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
> diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c
> new file mode 100644
> index 0000000..cdb29f6
> --- /dev/null
> +++ b/drivers/pwm/pwm-imx-tpm.c
> @@ -0,0 +1,343 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2018-2019 NXP.
> + *
> + * Limitations:
> + * - The TPM counter and period counter are shared between
> + *   multiple channels, so all channels should use same period
> + *   settings.
> + * - When a channel is disabled, its polarity settings will be
> + *   saved and its output will be disabled by clearing polarity
> + *   setting, when channel is enabled, polarity settings will be
> + *   restored and output will be enabled again.
> + * - ONLY when all channels are disabled, then TPM counter will
> + *   be disabled.

I wouldn't list the last two items in this section. These are
implementation details that a PWM consumer doesn't need to bother with.
These are fine to be noted in a code comment at the appropriate place.

Other than that please look at my feedback for the previous series that
I just sent out. I think most things still apply to this version.

Best regards
Uwe

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

_______________________________________________
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] 30+ messages in thread

* Re: [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support
  2019-03-14  6:27   ` Anson Huang
  (?)
@ 2019-03-14  9:39     ` Claudiu.Beznea
  -1 siblings, 0 replies; 30+ messages in thread
From: Claudiu.Beznea @ 2019-03-14  9:39 UTC (permalink / raw)
  To: anson.huang, thierry.reding, robh+dt, mark.rutland, shawnguo,
	s.hauer, kernel, festevam, linux, stefan, otavio,
	leonard.crestez, schnitzeltony, jan.tuerk, yibin.gong, linux-pwm,
	devicetree, linux-arm-kernel, linux-kernel, u.kleine-koenig
  Cc: linux-imx



On 14.03.2019 08:27, Anson Huang wrote:
> i.MX7ULP has TPM(Low Power Timer/Pulse Width Modulation Module)
> inside, add TPM PWM driver support.
> 
> Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
> ---
> Changes since V2:
> 	- Add "IMX_" as prefix to macro define as TPM is already used;
> 	- Use macro define for channel registers address instead of calculation in everywhere.
> 	- Add limitations statement in copyright.
> ---
>  drivers/pwm/Kconfig       |  10 ++
>  drivers/pwm/Makefile      |   1 +
>  drivers/pwm/pwm-imx-tpm.c | 343 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 354 insertions(+)
>  create mode 100644 drivers/pwm/pwm-imx-tpm.c
> 
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index a8f47df..c1cbb43 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -201,6 +201,16 @@ config PWM_IMX
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called pwm-imx.
>  
> +config PWM_IMX_TPM
> +	tristate "i.MX TPM PWM support"
> +	depends on ARCH_MXC
> +	help
> +	  Generic PWM framework driver for i.MX7ULP TPM module, TPM's full
> +	  name is Low Power Timer/Pulse Width Modulation Module.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called pwm-imx-tpm.
> +
>  config PWM_JZ4740
>  	tristate "Ingenic JZ47xx PWM support"
>  	depends on MACH_INGENIC
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 9c676a0..64e036c 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -18,6 +18,7 @@ obj-$(CONFIG_PWM_FSL_FTM)	+= pwm-fsl-ftm.o
>  obj-$(CONFIG_PWM_HIBVT)		+= pwm-hibvt.o
>  obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
>  obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o
> +obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
>  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>  obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
>  obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
> diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c
> new file mode 100644
> index 0000000..cdb29f6
> --- /dev/null
> +++ b/drivers/pwm/pwm-imx-tpm.c
> @@ -0,0 +1,343 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2018-2019 NXP.
> + *
> + * Limitations:
> + * - The TPM counter and period counter are shared between
> + *   multiple channels, so all channels should use same period
> + *   settings.
> + * - When a channel is disabled, its polarity settings will be
> + *   saved and its output will be disabled by clearing polarity
> + *   setting, when channel is enabled, polarity settings will be
> + *   restored and output will be enabled again.
> + * - ONLY when all channels are disabled, then TPM counter will
> + *   be disabled.
> + */
> +
> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/log2.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +
> +#define IMX_TPM_GLOBAL		0x8
> +#define IMX_TPM_SC		0x10
> +#define IMX_TPM_CNT		0x14
> +#define IMX_TPM_MOD		0x18
> +#define IMX_TPM_C0SC		0x20
> +#define IMX_TPM_C0V		0x24
> +
> +#define IMX_TPM_SC_CMOD_SHIFT	3
> +#define IMX_TPM_SC_CMOD_MASK	GENMASK(4, 3)
> +#define IMX_TPM_SC_CPWMS	BIT(5)
> +
> +#define IMX_TPM_CnSC_CHF	BIT(7)
> +#define IMX_TPM_CnSC_MSnB	BIT(5)
> +#define IMX_TPM_CnSC_MSnA	BIT(4)
> +#define IMX_TPM_CnSC_ELSnB	BIT(3)
> +#define IMX_TPM_CnSC_ELSnA	BIT(2)
> +
> +#define IMX_TPM_SC_PS_MASK	0x7
> +#define IMX_TPM_MOD_MOD_MASK	0xffff
> +
> +#define IMX_TPM_COUNT_MAX	0xffff
> +
> +#define IMX_TPM_DEFAULT_PWM_CHANNEL_NUM	2
> +
> +#define IMX_TPM_CnSC(n)	(IMX_TPM_C0SC + n * 0x8)
> +#define IMX_TPM_CnV(n)	(IMX_TPM_C0V + n * 0x8)
> +
> +struct imx_tpm_pwm_chip {
> +	struct pwm_chip chip;
> +	struct clk *clk;
> +	void __iomem *base;
> +	spinlock_t lock;
> +	u32 chn_config[IMX_TPM_DEFAULT_PWM_CHANNEL_NUM];
> +	bool chn_status[IMX_TPM_DEFAULT_PWM_CHANNEL_NUM];
> +};
> +
> +#define to_imx_tpm_pwm_chip(_chip)	container_of(_chip, struct imx_tpm_pwm_chip, chip)
> +
> +static void imx_tpm_pwm_config_counter(struct pwm_chip *chip, u32 period)
> +{
> +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> +	unsigned int period_cnt;
> +	u32 val, div;
> +	u64 tmp;
> +
> +	tmp = clk_get_rate(tpm->clk);
> +	tmp *= period;
> +	val = DIV_ROUND_CLOSEST_ULL(tmp, NSEC_PER_SEC);
> +	if (val < IMX_TPM_COUNT_MAX)
> +		div = 0;
> +	else
> +		div = ilog2(roundup_pow_of_two(val / IMX_TPM_COUNT_MAX));
> +	if (div > IMX_TPM_SC_PS_MASK) {
> +		dev_err(chip->dev,
> +			"failed to find valid prescale value!\n");
> +		return;
> +	}
> +	/* set TPM counter prescale */
> +	val = readl(tpm->base + IMX_TPM_SC);
> +	val &= ~IMX_TPM_SC_PS_MASK;
> +	val |= div;
> +	writel(val, tpm->base + IMX_TPM_SC);
> +
> +	/* set period counter */
> +	do_div(tmp, NSEC_PER_SEC);
> +	period_cnt = DIV_ROUND_CLOSEST_ULL(tmp, 1 << div);
> +	writel(period_cnt & IMX_TPM_MOD_MOD_MASK, tpm->base + IMX_TPM_MOD);
> +}
> +
> +static void imx_tpm_pwm_config(struct pwm_chip *chip,
> +			       struct pwm_device *pwm,
> +			       struct pwm_state *state)
> +{
> +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> +	static bool tpm_cnt_initialized;
> +	unsigned int duty_cnt;
> +	u32 val;
> +	u64 tmp;
> +
> +	/*
> +	 * TPM counter is shared by multi channels, let's make it to be
> +	 * ONLY first channel can config TPM counter's precale and period
> +	 * count.
> +	 */
> +	if (!tpm_cnt_initialized) {
> +		imx_tpm_pwm_config_counter(chip, state->period);
> +		tpm_cnt_initialized = true;
> +	}
> +
> +	/* set duty counter */
> +	tmp = readl(tpm->base + IMX_TPM_MOD) & IMX_TPM_MOD_MOD_MASK;
> +	tmp *= state->duty_cycle;
> +	duty_cnt = DIV_ROUND_CLOSEST_ULL(tmp, state->period);
> +	writel(duty_cnt & IMX_TPM_MOD_MOD_MASK,
> +		 tpm->base + IMX_TPM_CnV(pwm->hwpwm));
> +
> +	/* set polarity */
> +	val = readl(tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> +	val &= ~(IMX_TPM_CnSC_ELSnB | IMX_TPM_CnSC_ELSnA | IMX_TPM_CnSC_MSnA);
> +	val |= IMX_TPM_CnSC_MSnB;
> +	val |= state->polarity ? IMX_TPM_CnSC_ELSnA : IMX_TPM_CnSC_ELSnB;
> +	/*
> +	 * polarity settings will enabled/disable output statue
> +	 * immediately, so here ONLY save the config and will be
> +	 * written into register when channel is enabled/disabled.
> +	 */
> +	tpm->chn_config[pwm->hwpwm] = val;
> +}
> +
> +static void imx_tpm_pwm_enable(struct pwm_chip *chip,
> +			       struct pwm_device *pwm,
> +			       bool enable)
> +{
> +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> +	u32 val, i;
> +
> +	val = readl(tpm->base + IMX_TPM_SC);
> +	if (enable) {
> +		/* restore channel config */
> +		writel(tpm->chn_config[pwm->hwpwm],
> +			tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> +
> +		/* start TPM counter anyway */
> +		val |= 0x1 << IMX_TPM_SC_CMOD_SHIFT;
> +		writel(val, tpm->base + IMX_TPM_SC);
> +	} else {
> +		/* save channel config */
> +		tpm->chn_config[pwm->hwpwm] = readl(tpm->base +
> +			IMX_TPM_CnSC(pwm->hwpwm));
> +		/* disable channel */
> +		writel(IMX_TPM_CnSC_CHF, tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> +
> +		for (i = 0; i < chip->npwm; i++)
> +			if (i != pwm->hwpwm && tpm->chn_status[i])
> +				break;
> +		if (i == chip->npwm) {
> +			/* stop TPM counter since all channels are disabled */
> +			val &= ~IMX_TPM_SC_CMOD_MASK;
> +			writel(val, tpm->base + IMX_TPM_SC);
> +		}
> +	}
> +
> +	/* update channel statue */
> +	tpm->chn_status[pwm->hwpwm] = enable;
> +}
> +
> +static void imx_tpm_pwm_get_state(struct pwm_chip *chip,
> +				  struct pwm_device *pwm,
> +				  struct pwm_state *state)
> +{
> +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> +	unsigned long rate, flags;
> +	u64 tmp;
> +	u32 val;
> +
> +	spin_lock_irqsave(&tpm->lock, flags);
> +
> +	/* get period */
> +	rate = clk_get_rate(tpm->clk);
> +	tmp = readl(tpm->base + IMX_TPM_MOD);
> +	val = readl(tpm->base + IMX_TPM_SC);
> +	val &= IMX_TPM_SC_PS_MASK;
> +	tmp *= (1 << val) * NSEC_PER_SEC;
> +	state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate);
> +
> +	/* get duty cycle */
> +	tmp = readl(tpm->base + IMX_TPM_CnV(pwm->hwpwm));
> +	tmp *= (1 << val) * NSEC_PER_SEC;
> +	state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate);
> +
> +	/* get polarity */
> +	val = readl(tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> +	if (val & IMX_TPM_CnSC_ELSnA)
> +		state->polarity = PWM_POLARITY_INVERSED;
> +	else
> +		state->polarity = PWM_POLARITY_NORMAL;
> +
> +	/* get channel status */
> +	state->enabled = tpm->chn_status[pwm->hwpwm] ? true : false;
> +
> +	spin_unlock_irqrestore(&tpm->lock, flags);
> +}
> +
> +static int imx_tpm_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> +			  struct pwm_state *state)
> +{
> +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> +	struct pwm_state curstate;
> +	unsigned long flags;
> +
> +	imx_tpm_pwm_get_state(chip, pwm, &curstate);
> +
> +	spin_lock_irqsave(&tpm->lock, flags);
> +
> +	if (state->period != curstate.period ||
> +	    state->duty_cycle != curstate.duty_cycle ||
> +	    state->polarity != curstate.polarity)
> +		imx_tpm_pwm_config(chip, pwm, state);
> +
> +	if (state->enabled != curstate.enabled)
> +		imx_tpm_pwm_enable(chip, pwm, state->enabled);
> +
> +	spin_unlock_irqrestore(&tpm->lock, flags);
> +
> +	return 0;
> +}
> +
> +static const struct pwm_ops imx_tpm_pwm_ops = {
> +	.get_state = imx_tpm_pwm_get_state,
> +	.apply = imx_tpm_pwm_apply,
> +	.owner = THIS_MODULE,
> +};
> +
> +static int imx_tpm_pwm_probe(struct platform_device *pdev)
> +{
> +	struct imx_tpm_pwm_chip *tpm;
> +	struct resource *res;
> +	int ret;
> +
> +	tpm = devm_kzalloc(&pdev->dev, sizeof(*tpm), GFP_KERNEL);
> +	if (!tpm)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, tpm);
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	tpm->base = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(tpm->base)) {
> +		ret = PTR_ERR(tpm->base);
> +		if (ret != -EPROBE_DEFER)
> +			dev_err(&pdev->dev, "pwm ioremap failed %d\n", ret);
> +		return ret;
> +	}
> +
> +	tpm->clk = devm_clk_get(&pdev->dev, NULL);
> +	if (IS_ERR(tpm->clk)) {
> +		ret = PTR_ERR(tpm->clk);
> +		if (ret != -EPROBE_DEFER)
> +			dev_err(&pdev->dev, "failed to get pwm clk %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = clk_prepare_enable(tpm->clk);
> +	if (ret) {
> +		dev_err(&pdev->dev,
> +			"failed to prepare or enable clk %d\n", ret);
> +		return ret;
> +	}
> +
> +	tpm->chip.dev = &pdev->dev;
> +	tpm->chip.ops = &imx_tpm_pwm_ops;
> +	tpm->chip.base = -1;
> +	tpm->chip.npwm = IMX_TPM_DEFAULT_PWM_CHANNEL_NUM;
> +
> +	spin_lock_init(&tpm->lock);
> +
> +	ret = pwmchip_add(&tpm->chip);
> +	if (ret)
> +		dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret);

clk_disable_unprepare() in case of failure? Although I would use
clk_prepare()/clk_unprepare() in probe and clk_prepare_enable() only when
PWM is active.

> +
> +	return ret;
> +}
> +
> +static int imx_tpm_pwm_remove(struct platform_device *pdev)
> +{
> +	struct imx_tpm_pwm_chip *tpm = platform_get_drvdata(pdev);
> +

Ditto

> +	return pwmchip_remove(&tpm->chip);
> +}
> +
> +static int __maybe_unused imx_tpm_pwm_suspend(struct device *dev)
> +{
> +	struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
> +
> +	clk_disable_unprepare(tpm->clk);
> +
> +	return 0;
> +}
> +
> +static int __maybe_unused imx_tpm_pwm_resume(struct device *dev)
> +{
> +	struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
> +	int ret = clk_prepare_enable(tpm->clk);
> +
> +	if (ret)
> +		dev_err(dev,
> +			"failed to prepare or enable clk %d\n", ret);
> +
> +	return ret;
> +};
> +
> +static SIMPLE_DEV_PM_OPS(imx_tpm_pwm_pm,
> +			 imx_tpm_pwm_suspend, imx_tpm_pwm_resume);
> +
> +static const struct of_device_id imx_tpm_pwm_dt_ids[] = {
> +	{ .compatible = "fsl,imx-tpm-pwm", },
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, imx_tpm_pwm_dt_ids);
> +
> +static struct platform_driver imx_tpm_pwm_driver = {
> +	.driver = {
> +		.name = "imx-tpm-pwm",
> +		.of_match_table = imx_tpm_pwm_dt_ids,
> +		.pm = &imx_tpm_pwm_pm,
> +	},
> +	.probe	= imx_tpm_pwm_probe,
> +	.remove = imx_tpm_pwm_remove,
> +};
> +module_platform_driver(imx_tpm_pwm_driver);
> +
> +MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>");
> +MODULE_DESCRIPTION("i.MX TPM PWM Driver");
> +MODULE_LICENSE("GPL v2");
> 

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

* Re: [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support
@ 2019-03-14  9:39     ` Claudiu.Beznea
  0 siblings, 0 replies; 30+ messages in thread
From: Claudiu.Beznea @ 2019-03-14  9:39 UTC (permalink / raw)
  To: anson.huang, thierry.reding, robh+dt, mark.rutland, shawnguo,
	s.hauer, kernel, festevam, linux, stefan, otavio,
	leonard.crestez, schnitzeltony, jan.tuerk, yibin.gong, linux-pwm,
	devicetree, linux-arm-kernel, linux-kernel, u.kleine-koenig
  Cc: linux-imx



On 14.03.2019 08:27, Anson Huang wrote:
> i.MX7ULP has TPM(Low Power Timer/Pulse Width Modulation Module)
> inside, add TPM PWM driver support.
> 
> Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
> ---
> Changes since V2:
> 	- Add "IMX_" as prefix to macro define as TPM is already used;
> 	- Use macro define for channel registers address instead of calculation in everywhere.
> 	- Add limitations statement in copyright.
> ---
>  drivers/pwm/Kconfig       |  10 ++
>  drivers/pwm/Makefile      |   1 +
>  drivers/pwm/pwm-imx-tpm.c | 343 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 354 insertions(+)
>  create mode 100644 drivers/pwm/pwm-imx-tpm.c
> 
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index a8f47df..c1cbb43 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -201,6 +201,16 @@ config PWM_IMX
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called pwm-imx.
>  
> +config PWM_IMX_TPM
> +	tristate "i.MX TPM PWM support"
> +	depends on ARCH_MXC
> +	help
> +	  Generic PWM framework driver for i.MX7ULP TPM module, TPM's full
> +	  name is Low Power Timer/Pulse Width Modulation Module.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called pwm-imx-tpm.
> +
>  config PWM_JZ4740
>  	tristate "Ingenic JZ47xx PWM support"
>  	depends on MACH_INGENIC
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 9c676a0..64e036c 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -18,6 +18,7 @@ obj-$(CONFIG_PWM_FSL_FTM)	+= pwm-fsl-ftm.o
>  obj-$(CONFIG_PWM_HIBVT)		+= pwm-hibvt.o
>  obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
>  obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o
> +obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
>  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>  obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
>  obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
> diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c
> new file mode 100644
> index 0000000..cdb29f6
> --- /dev/null
> +++ b/drivers/pwm/pwm-imx-tpm.c
> @@ -0,0 +1,343 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2018-2019 NXP.
> + *
> + * Limitations:
> + * - The TPM counter and period counter are shared between
> + *   multiple channels, so all channels should use same period
> + *   settings.
> + * - When a channel is disabled, its polarity settings will be
> + *   saved and its output will be disabled by clearing polarity
> + *   setting, when channel is enabled, polarity settings will be
> + *   restored and output will be enabled again.
> + * - ONLY when all channels are disabled, then TPM counter will
> + *   be disabled.
> + */
> +
> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/log2.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +
> +#define IMX_TPM_GLOBAL		0x8
> +#define IMX_TPM_SC		0x10
> +#define IMX_TPM_CNT		0x14
> +#define IMX_TPM_MOD		0x18
> +#define IMX_TPM_C0SC		0x20
> +#define IMX_TPM_C0V		0x24
> +
> +#define IMX_TPM_SC_CMOD_SHIFT	3
> +#define IMX_TPM_SC_CMOD_MASK	GENMASK(4, 3)
> +#define IMX_TPM_SC_CPWMS	BIT(5)
> +
> +#define IMX_TPM_CnSC_CHF	BIT(7)
> +#define IMX_TPM_CnSC_MSnB	BIT(5)
> +#define IMX_TPM_CnSC_MSnA	BIT(4)
> +#define IMX_TPM_CnSC_ELSnB	BIT(3)
> +#define IMX_TPM_CnSC_ELSnA	BIT(2)
> +
> +#define IMX_TPM_SC_PS_MASK	0x7
> +#define IMX_TPM_MOD_MOD_MASK	0xffff
> +
> +#define IMX_TPM_COUNT_MAX	0xffff
> +
> +#define IMX_TPM_DEFAULT_PWM_CHANNEL_NUM	2
> +
> +#define IMX_TPM_CnSC(n)	(IMX_TPM_C0SC + n * 0x8)
> +#define IMX_TPM_CnV(n)	(IMX_TPM_C0V + n * 0x8)
> +
> +struct imx_tpm_pwm_chip {
> +	struct pwm_chip chip;
> +	struct clk *clk;
> +	void __iomem *base;
> +	spinlock_t lock;
> +	u32 chn_config[IMX_TPM_DEFAULT_PWM_CHANNEL_NUM];
> +	bool chn_status[IMX_TPM_DEFAULT_PWM_CHANNEL_NUM];
> +};
> +
> +#define to_imx_tpm_pwm_chip(_chip)	container_of(_chip, struct imx_tpm_pwm_chip, chip)
> +
> +static void imx_tpm_pwm_config_counter(struct pwm_chip *chip, u32 period)
> +{
> +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> +	unsigned int period_cnt;
> +	u32 val, div;
> +	u64 tmp;
> +
> +	tmp = clk_get_rate(tpm->clk);
> +	tmp *= period;
> +	val = DIV_ROUND_CLOSEST_ULL(tmp, NSEC_PER_SEC);
> +	if (val < IMX_TPM_COUNT_MAX)
> +		div = 0;
> +	else
> +		div = ilog2(roundup_pow_of_two(val / IMX_TPM_COUNT_MAX));
> +	if (div > IMX_TPM_SC_PS_MASK) {
> +		dev_err(chip->dev,
> +			"failed to find valid prescale value!\n");
> +		return;
> +	}
> +	/* set TPM counter prescale */
> +	val = readl(tpm->base + IMX_TPM_SC);
> +	val &= ~IMX_TPM_SC_PS_MASK;
> +	val |= div;
> +	writel(val, tpm->base + IMX_TPM_SC);
> +
> +	/* set period counter */
> +	do_div(tmp, NSEC_PER_SEC);
> +	period_cnt = DIV_ROUND_CLOSEST_ULL(tmp, 1 << div);
> +	writel(period_cnt & IMX_TPM_MOD_MOD_MASK, tpm->base + IMX_TPM_MOD);
> +}
> +
> +static void imx_tpm_pwm_config(struct pwm_chip *chip,
> +			       struct pwm_device *pwm,
> +			       struct pwm_state *state)
> +{
> +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> +	static bool tpm_cnt_initialized;
> +	unsigned int duty_cnt;
> +	u32 val;
> +	u64 tmp;
> +
> +	/*
> +	 * TPM counter is shared by multi channels, let's make it to be
> +	 * ONLY first channel can config TPM counter's precale and period
> +	 * count.
> +	 */
> +	if (!tpm_cnt_initialized) {
> +		imx_tpm_pwm_config_counter(chip, state->period);
> +		tpm_cnt_initialized = true;
> +	}
> +
> +	/* set duty counter */
> +	tmp = readl(tpm->base + IMX_TPM_MOD) & IMX_TPM_MOD_MOD_MASK;
> +	tmp *= state->duty_cycle;
> +	duty_cnt = DIV_ROUND_CLOSEST_ULL(tmp, state->period);
> +	writel(duty_cnt & IMX_TPM_MOD_MOD_MASK,
> +		 tpm->base + IMX_TPM_CnV(pwm->hwpwm));
> +
> +	/* set polarity */
> +	val = readl(tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> +	val &= ~(IMX_TPM_CnSC_ELSnB | IMX_TPM_CnSC_ELSnA | IMX_TPM_CnSC_MSnA);
> +	val |= IMX_TPM_CnSC_MSnB;
> +	val |= state->polarity ? IMX_TPM_CnSC_ELSnA : IMX_TPM_CnSC_ELSnB;
> +	/*
> +	 * polarity settings will enabled/disable output statue
> +	 * immediately, so here ONLY save the config and will be
> +	 * written into register when channel is enabled/disabled.
> +	 */
> +	tpm->chn_config[pwm->hwpwm] = val;
> +}
> +
> +static void imx_tpm_pwm_enable(struct pwm_chip *chip,
> +			       struct pwm_device *pwm,
> +			       bool enable)
> +{
> +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> +	u32 val, i;
> +
> +	val = readl(tpm->base + IMX_TPM_SC);
> +	if (enable) {
> +		/* restore channel config */
> +		writel(tpm->chn_config[pwm->hwpwm],
> +			tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> +
> +		/* start TPM counter anyway */
> +		val |= 0x1 << IMX_TPM_SC_CMOD_SHIFT;
> +		writel(val, tpm->base + IMX_TPM_SC);
> +	} else {
> +		/* save channel config */
> +		tpm->chn_config[pwm->hwpwm] = readl(tpm->base +
> +			IMX_TPM_CnSC(pwm->hwpwm));
> +		/* disable channel */
> +		writel(IMX_TPM_CnSC_CHF, tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> +
> +		for (i = 0; i < chip->npwm; i++)
> +			if (i != pwm->hwpwm && tpm->chn_status[i])
> +				break;
> +		if (i == chip->npwm) {
> +			/* stop TPM counter since all channels are disabled */
> +			val &= ~IMX_TPM_SC_CMOD_MASK;
> +			writel(val, tpm->base + IMX_TPM_SC);
> +		}
> +	}
> +
> +	/* update channel statue */
> +	tpm->chn_status[pwm->hwpwm] = enable;
> +}
> +
> +static void imx_tpm_pwm_get_state(struct pwm_chip *chip,
> +				  struct pwm_device *pwm,
> +				  struct pwm_state *state)
> +{
> +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> +	unsigned long rate, flags;
> +	u64 tmp;
> +	u32 val;
> +
> +	spin_lock_irqsave(&tpm->lock, flags);
> +
> +	/* get period */
> +	rate = clk_get_rate(tpm->clk);
> +	tmp = readl(tpm->base + IMX_TPM_MOD);
> +	val = readl(tpm->base + IMX_TPM_SC);
> +	val &= IMX_TPM_SC_PS_MASK;
> +	tmp *= (1 << val) * NSEC_PER_SEC;
> +	state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate);
> +
> +	/* get duty cycle */
> +	tmp = readl(tpm->base + IMX_TPM_CnV(pwm->hwpwm));
> +	tmp *= (1 << val) * NSEC_PER_SEC;
> +	state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate);
> +
> +	/* get polarity */
> +	val = readl(tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> +	if (val & IMX_TPM_CnSC_ELSnA)
> +		state->polarity = PWM_POLARITY_INVERSED;
> +	else
> +		state->polarity = PWM_POLARITY_NORMAL;
> +
> +	/* get channel status */
> +	state->enabled = tpm->chn_status[pwm->hwpwm] ? true : false;
> +
> +	spin_unlock_irqrestore(&tpm->lock, flags);
> +}
> +
> +static int imx_tpm_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> +			  struct pwm_state *state)
> +{
> +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> +	struct pwm_state curstate;
> +	unsigned long flags;
> +
> +	imx_tpm_pwm_get_state(chip, pwm, &curstate);
> +
> +	spin_lock_irqsave(&tpm->lock, flags);
> +
> +	if (state->period != curstate.period ||
> +	    state->duty_cycle != curstate.duty_cycle ||
> +	    state->polarity != curstate.polarity)
> +		imx_tpm_pwm_config(chip, pwm, state);
> +
> +	if (state->enabled != curstate.enabled)
> +		imx_tpm_pwm_enable(chip, pwm, state->enabled);
> +
> +	spin_unlock_irqrestore(&tpm->lock, flags);
> +
> +	return 0;
> +}
> +
> +static const struct pwm_ops imx_tpm_pwm_ops = {
> +	.get_state = imx_tpm_pwm_get_state,
> +	.apply = imx_tpm_pwm_apply,
> +	.owner = THIS_MODULE,
> +};
> +
> +static int imx_tpm_pwm_probe(struct platform_device *pdev)
> +{
> +	struct imx_tpm_pwm_chip *tpm;
> +	struct resource *res;
> +	int ret;
> +
> +	tpm = devm_kzalloc(&pdev->dev, sizeof(*tpm), GFP_KERNEL);
> +	if (!tpm)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, tpm);
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	tpm->base = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(tpm->base)) {
> +		ret = PTR_ERR(tpm->base);
> +		if (ret != -EPROBE_DEFER)
> +			dev_err(&pdev->dev, "pwm ioremap failed %d\n", ret);
> +		return ret;
> +	}
> +
> +	tpm->clk = devm_clk_get(&pdev->dev, NULL);
> +	if (IS_ERR(tpm->clk)) {
> +		ret = PTR_ERR(tpm->clk);
> +		if (ret != -EPROBE_DEFER)
> +			dev_err(&pdev->dev, "failed to get pwm clk %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = clk_prepare_enable(tpm->clk);
> +	if (ret) {
> +		dev_err(&pdev->dev,
> +			"failed to prepare or enable clk %d\n", ret);
> +		return ret;
> +	}
> +
> +	tpm->chip.dev = &pdev->dev;
> +	tpm->chip.ops = &imx_tpm_pwm_ops;
> +	tpm->chip.base = -1;
> +	tpm->chip.npwm = IMX_TPM_DEFAULT_PWM_CHANNEL_NUM;
> +
> +	spin_lock_init(&tpm->lock);
> +
> +	ret = pwmchip_add(&tpm->chip);
> +	if (ret)
> +		dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret);

clk_disable_unprepare() in case of failure? Although I would use
clk_prepare()/clk_unprepare() in probe and clk_prepare_enable() only when
PWM is active.

> +
> +	return ret;
> +}
> +
> +static int imx_tpm_pwm_remove(struct platform_device *pdev)
> +{
> +	struct imx_tpm_pwm_chip *tpm = platform_get_drvdata(pdev);
> +

Ditto

> +	return pwmchip_remove(&tpm->chip);
> +}
> +
> +static int __maybe_unused imx_tpm_pwm_suspend(struct device *dev)
> +{
> +	struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
> +
> +	clk_disable_unprepare(tpm->clk);
> +
> +	return 0;
> +}
> +
> +static int __maybe_unused imx_tpm_pwm_resume(struct device *dev)
> +{
> +	struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
> +	int ret = clk_prepare_enable(tpm->clk);
> +
> +	if (ret)
> +		dev_err(dev,
> +			"failed to prepare or enable clk %d\n", ret);
> +
> +	return ret;
> +};
> +
> +static SIMPLE_DEV_PM_OPS(imx_tpm_pwm_pm,
> +			 imx_tpm_pwm_suspend, imx_tpm_pwm_resume);
> +
> +static const struct of_device_id imx_tpm_pwm_dt_ids[] = {
> +	{ .compatible = "fsl,imx-tpm-pwm", },
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, imx_tpm_pwm_dt_ids);
> +
> +static struct platform_driver imx_tpm_pwm_driver = {
> +	.driver = {
> +		.name = "imx-tpm-pwm",
> +		.of_match_table = imx_tpm_pwm_dt_ids,
> +		.pm = &imx_tpm_pwm_pm,
> +	},
> +	.probe	= imx_tpm_pwm_probe,
> +	.remove = imx_tpm_pwm_remove,
> +};
> +module_platform_driver(imx_tpm_pwm_driver);
> +
> +MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>");
> +MODULE_DESCRIPTION("i.MX TPM PWM Driver");
> +MODULE_LICENSE("GPL v2");
> 

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

* Re: [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support
@ 2019-03-14  9:39     ` Claudiu.Beznea
  0 siblings, 0 replies; 30+ messages in thread
From: Claudiu.Beznea @ 2019-03-14  9:39 UTC (permalink / raw)
  To: anson.huang, thierry.reding, robh+dt, mark.rutland, shawnguo,
	s.hauer, kernel, festevam, linux, stefan, otavio,
	leonard.crestez, schnitzeltony, jan.tuerk, yibin.gong, linux-pwm,
	devicetree, linux-arm-kernel, linux-kernel, u.kleine-koenig
  Cc: linux-imx



On 14.03.2019 08:27, Anson Huang wrote:
> i.MX7ULP has TPM(Low Power Timer/Pulse Width Modulation Module)
> inside, add TPM PWM driver support.
> 
> Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
> ---
> Changes since V2:
> 	- Add "IMX_" as prefix to macro define as TPM is already used;
> 	- Use macro define for channel registers address instead of calculation in everywhere.
> 	- Add limitations statement in copyright.
> ---
>  drivers/pwm/Kconfig       |  10 ++
>  drivers/pwm/Makefile      |   1 +
>  drivers/pwm/pwm-imx-tpm.c | 343 ++++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 354 insertions(+)
>  create mode 100644 drivers/pwm/pwm-imx-tpm.c
> 
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> index a8f47df..c1cbb43 100644
> --- a/drivers/pwm/Kconfig
> +++ b/drivers/pwm/Kconfig
> @@ -201,6 +201,16 @@ config PWM_IMX
>  	  To compile this driver as a module, choose M here: the module
>  	  will be called pwm-imx.
>  
> +config PWM_IMX_TPM
> +	tristate "i.MX TPM PWM support"
> +	depends on ARCH_MXC
> +	help
> +	  Generic PWM framework driver for i.MX7ULP TPM module, TPM's full
> +	  name is Low Power Timer/Pulse Width Modulation Module.
> +
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called pwm-imx-tpm.
> +
>  config PWM_JZ4740
>  	tristate "Ingenic JZ47xx PWM support"
>  	depends on MACH_INGENIC
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> index 9c676a0..64e036c 100644
> --- a/drivers/pwm/Makefile
> +++ b/drivers/pwm/Makefile
> @@ -18,6 +18,7 @@ obj-$(CONFIG_PWM_FSL_FTM)	+= pwm-fsl-ftm.o
>  obj-$(CONFIG_PWM_HIBVT)		+= pwm-hibvt.o
>  obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
>  obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o
> +obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
>  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
>  obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
>  obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
> diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c
> new file mode 100644
> index 0000000..cdb29f6
> --- /dev/null
> +++ b/drivers/pwm/pwm-imx-tpm.c
> @@ -0,0 +1,343 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright 2018-2019 NXP.
> + *
> + * Limitations:
> + * - The TPM counter and period counter are shared between
> + *   multiple channels, so all channels should use same period
> + *   settings.
> + * - When a channel is disabled, its polarity settings will be
> + *   saved and its output will be disabled by clearing polarity
> + *   setting, when channel is enabled, polarity settings will be
> + *   restored and output will be enabled again.
> + * - ONLY when all channels are disabled, then TPM counter will
> + *   be disabled.
> + */
> +
> +#include <linux/bitops.h>
> +#include <linux/clk.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/log2.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/of_address.h>
> +#include <linux/platform_device.h>
> +#include <linux/pwm.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +
> +#define IMX_TPM_GLOBAL		0x8
> +#define IMX_TPM_SC		0x10
> +#define IMX_TPM_CNT		0x14
> +#define IMX_TPM_MOD		0x18
> +#define IMX_TPM_C0SC		0x20
> +#define IMX_TPM_C0V		0x24
> +
> +#define IMX_TPM_SC_CMOD_SHIFT	3
> +#define IMX_TPM_SC_CMOD_MASK	GENMASK(4, 3)
> +#define IMX_TPM_SC_CPWMS	BIT(5)
> +
> +#define IMX_TPM_CnSC_CHF	BIT(7)
> +#define IMX_TPM_CnSC_MSnB	BIT(5)
> +#define IMX_TPM_CnSC_MSnA	BIT(4)
> +#define IMX_TPM_CnSC_ELSnB	BIT(3)
> +#define IMX_TPM_CnSC_ELSnA	BIT(2)
> +
> +#define IMX_TPM_SC_PS_MASK	0x7
> +#define IMX_TPM_MOD_MOD_MASK	0xffff
> +
> +#define IMX_TPM_COUNT_MAX	0xffff
> +
> +#define IMX_TPM_DEFAULT_PWM_CHANNEL_NUM	2
> +
> +#define IMX_TPM_CnSC(n)	(IMX_TPM_C0SC + n * 0x8)
> +#define IMX_TPM_CnV(n)	(IMX_TPM_C0V + n * 0x8)
> +
> +struct imx_tpm_pwm_chip {
> +	struct pwm_chip chip;
> +	struct clk *clk;
> +	void __iomem *base;
> +	spinlock_t lock;
> +	u32 chn_config[IMX_TPM_DEFAULT_PWM_CHANNEL_NUM];
> +	bool chn_status[IMX_TPM_DEFAULT_PWM_CHANNEL_NUM];
> +};
> +
> +#define to_imx_tpm_pwm_chip(_chip)	container_of(_chip, struct imx_tpm_pwm_chip, chip)
> +
> +static void imx_tpm_pwm_config_counter(struct pwm_chip *chip, u32 period)
> +{
> +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> +	unsigned int period_cnt;
> +	u32 val, div;
> +	u64 tmp;
> +
> +	tmp = clk_get_rate(tpm->clk);
> +	tmp *= period;
> +	val = DIV_ROUND_CLOSEST_ULL(tmp, NSEC_PER_SEC);
> +	if (val < IMX_TPM_COUNT_MAX)
> +		div = 0;
> +	else
> +		div = ilog2(roundup_pow_of_two(val / IMX_TPM_COUNT_MAX));
> +	if (div > IMX_TPM_SC_PS_MASK) {
> +		dev_err(chip->dev,
> +			"failed to find valid prescale value!\n");
> +		return;
> +	}
> +	/* set TPM counter prescale */
> +	val = readl(tpm->base + IMX_TPM_SC);
> +	val &= ~IMX_TPM_SC_PS_MASK;
> +	val |= div;
> +	writel(val, tpm->base + IMX_TPM_SC);
> +
> +	/* set period counter */
> +	do_div(tmp, NSEC_PER_SEC);
> +	period_cnt = DIV_ROUND_CLOSEST_ULL(tmp, 1 << div);
> +	writel(period_cnt & IMX_TPM_MOD_MOD_MASK, tpm->base + IMX_TPM_MOD);
> +}
> +
> +static void imx_tpm_pwm_config(struct pwm_chip *chip,
> +			       struct pwm_device *pwm,
> +			       struct pwm_state *state)
> +{
> +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> +	static bool tpm_cnt_initialized;
> +	unsigned int duty_cnt;
> +	u32 val;
> +	u64 tmp;
> +
> +	/*
> +	 * TPM counter is shared by multi channels, let's make it to be
> +	 * ONLY first channel can config TPM counter's precale and period
> +	 * count.
> +	 */
> +	if (!tpm_cnt_initialized) {
> +		imx_tpm_pwm_config_counter(chip, state->period);
> +		tpm_cnt_initialized = true;
> +	}
> +
> +	/* set duty counter */
> +	tmp = readl(tpm->base + IMX_TPM_MOD) & IMX_TPM_MOD_MOD_MASK;
> +	tmp *= state->duty_cycle;
> +	duty_cnt = DIV_ROUND_CLOSEST_ULL(tmp, state->period);
> +	writel(duty_cnt & IMX_TPM_MOD_MOD_MASK,
> +		 tpm->base + IMX_TPM_CnV(pwm->hwpwm));
> +
> +	/* set polarity */
> +	val = readl(tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> +	val &= ~(IMX_TPM_CnSC_ELSnB | IMX_TPM_CnSC_ELSnA | IMX_TPM_CnSC_MSnA);
> +	val |= IMX_TPM_CnSC_MSnB;
> +	val |= state->polarity ? IMX_TPM_CnSC_ELSnA : IMX_TPM_CnSC_ELSnB;
> +	/*
> +	 * polarity settings will enabled/disable output statue
> +	 * immediately, so here ONLY save the config and will be
> +	 * written into register when channel is enabled/disabled.
> +	 */
> +	tpm->chn_config[pwm->hwpwm] = val;
> +}
> +
> +static void imx_tpm_pwm_enable(struct pwm_chip *chip,
> +			       struct pwm_device *pwm,
> +			       bool enable)
> +{
> +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> +	u32 val, i;
> +
> +	val = readl(tpm->base + IMX_TPM_SC);
> +	if (enable) {
> +		/* restore channel config */
> +		writel(tpm->chn_config[pwm->hwpwm],
> +			tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> +
> +		/* start TPM counter anyway */
> +		val |= 0x1 << IMX_TPM_SC_CMOD_SHIFT;
> +		writel(val, tpm->base + IMX_TPM_SC);
> +	} else {
> +		/* save channel config */
> +		tpm->chn_config[pwm->hwpwm] = readl(tpm->base +
> +			IMX_TPM_CnSC(pwm->hwpwm));
> +		/* disable channel */
> +		writel(IMX_TPM_CnSC_CHF, tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> +
> +		for (i = 0; i < chip->npwm; i++)
> +			if (i != pwm->hwpwm && tpm->chn_status[i])
> +				break;
> +		if (i == chip->npwm) {
> +			/* stop TPM counter since all channels are disabled */
> +			val &= ~IMX_TPM_SC_CMOD_MASK;
> +			writel(val, tpm->base + IMX_TPM_SC);
> +		}
> +	}
> +
> +	/* update channel statue */
> +	tpm->chn_status[pwm->hwpwm] = enable;
> +}
> +
> +static void imx_tpm_pwm_get_state(struct pwm_chip *chip,
> +				  struct pwm_device *pwm,
> +				  struct pwm_state *state)
> +{
> +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> +	unsigned long rate, flags;
> +	u64 tmp;
> +	u32 val;
> +
> +	spin_lock_irqsave(&tpm->lock, flags);
> +
> +	/* get period */
> +	rate = clk_get_rate(tpm->clk);
> +	tmp = readl(tpm->base + IMX_TPM_MOD);
> +	val = readl(tpm->base + IMX_TPM_SC);
> +	val &= IMX_TPM_SC_PS_MASK;
> +	tmp *= (1 << val) * NSEC_PER_SEC;
> +	state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate);
> +
> +	/* get duty cycle */
> +	tmp = readl(tpm->base + IMX_TPM_CnV(pwm->hwpwm));
> +	tmp *= (1 << val) * NSEC_PER_SEC;
> +	state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate);
> +
> +	/* get polarity */
> +	val = readl(tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> +	if (val & IMX_TPM_CnSC_ELSnA)
> +		state->polarity = PWM_POLARITY_INVERSED;
> +	else
> +		state->polarity = PWM_POLARITY_NORMAL;
> +
> +	/* get channel status */
> +	state->enabled = tpm->chn_status[pwm->hwpwm] ? true : false;
> +
> +	spin_unlock_irqrestore(&tpm->lock, flags);
> +}
> +
> +static int imx_tpm_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
> +			  struct pwm_state *state)
> +{
> +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> +	struct pwm_state curstate;
> +	unsigned long flags;
> +
> +	imx_tpm_pwm_get_state(chip, pwm, &curstate);
> +
> +	spin_lock_irqsave(&tpm->lock, flags);
> +
> +	if (state->period != curstate.period ||
> +	    state->duty_cycle != curstate.duty_cycle ||
> +	    state->polarity != curstate.polarity)
> +		imx_tpm_pwm_config(chip, pwm, state);
> +
> +	if (state->enabled != curstate.enabled)
> +		imx_tpm_pwm_enable(chip, pwm, state->enabled);
> +
> +	spin_unlock_irqrestore(&tpm->lock, flags);
> +
> +	return 0;
> +}
> +
> +static const struct pwm_ops imx_tpm_pwm_ops = {
> +	.get_state = imx_tpm_pwm_get_state,
> +	.apply = imx_tpm_pwm_apply,
> +	.owner = THIS_MODULE,
> +};
> +
> +static int imx_tpm_pwm_probe(struct platform_device *pdev)
> +{
> +	struct imx_tpm_pwm_chip *tpm;
> +	struct resource *res;
> +	int ret;
> +
> +	tpm = devm_kzalloc(&pdev->dev, sizeof(*tpm), GFP_KERNEL);
> +	if (!tpm)
> +		return -ENOMEM;
> +
> +	platform_set_drvdata(pdev, tpm);
> +
> +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +	tpm->base = devm_ioremap_resource(&pdev->dev, res);
> +	if (IS_ERR(tpm->base)) {
> +		ret = PTR_ERR(tpm->base);
> +		if (ret != -EPROBE_DEFER)
> +			dev_err(&pdev->dev, "pwm ioremap failed %d\n", ret);
> +		return ret;
> +	}
> +
> +	tpm->clk = devm_clk_get(&pdev->dev, NULL);
> +	if (IS_ERR(tpm->clk)) {
> +		ret = PTR_ERR(tpm->clk);
> +		if (ret != -EPROBE_DEFER)
> +			dev_err(&pdev->dev, "failed to get pwm clk %d\n", ret);
> +		return ret;
> +	}
> +
> +	ret = clk_prepare_enable(tpm->clk);
> +	if (ret) {
> +		dev_err(&pdev->dev,
> +			"failed to prepare or enable clk %d\n", ret);
> +		return ret;
> +	}
> +
> +	tpm->chip.dev = &pdev->dev;
> +	tpm->chip.ops = &imx_tpm_pwm_ops;
> +	tpm->chip.base = -1;
> +	tpm->chip.npwm = IMX_TPM_DEFAULT_PWM_CHANNEL_NUM;
> +
> +	spin_lock_init(&tpm->lock);
> +
> +	ret = pwmchip_add(&tpm->chip);
> +	if (ret)
> +		dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret);

clk_disable_unprepare() in case of failure? Although I would use
clk_prepare()/clk_unprepare() in probe and clk_prepare_enable() only when
PWM is active.

> +
> +	return ret;
> +}
> +
> +static int imx_tpm_pwm_remove(struct platform_device *pdev)
> +{
> +	struct imx_tpm_pwm_chip *tpm = platform_get_drvdata(pdev);
> +

Ditto

> +	return pwmchip_remove(&tpm->chip);
> +}
> +
> +static int __maybe_unused imx_tpm_pwm_suspend(struct device *dev)
> +{
> +	struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
> +
> +	clk_disable_unprepare(tpm->clk);
> +
> +	return 0;
> +}
> +
> +static int __maybe_unused imx_tpm_pwm_resume(struct device *dev)
> +{
> +	struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
> +	int ret = clk_prepare_enable(tpm->clk);
> +
> +	if (ret)
> +		dev_err(dev,
> +			"failed to prepare or enable clk %d\n", ret);
> +
> +	return ret;
> +};
> +
> +static SIMPLE_DEV_PM_OPS(imx_tpm_pwm_pm,
> +			 imx_tpm_pwm_suspend, imx_tpm_pwm_resume);
> +
> +static const struct of_device_id imx_tpm_pwm_dt_ids[] = {
> +	{ .compatible = "fsl,imx-tpm-pwm", },
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, imx_tpm_pwm_dt_ids);
> +
> +static struct platform_driver imx_tpm_pwm_driver = {
> +	.driver = {
> +		.name = "imx-tpm-pwm",
> +		.of_match_table = imx_tpm_pwm_dt_ids,
> +		.pm = &imx_tpm_pwm_pm,
> +	},
> +	.probe	= imx_tpm_pwm_probe,
> +	.remove = imx_tpm_pwm_remove,
> +};
> +module_platform_driver(imx_tpm_pwm_driver);
> +
> +MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>");
> +MODULE_DESCRIPTION("i.MX TPM PWM Driver");
> +MODULE_LICENSE("GPL v2");
> 
_______________________________________________
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] 30+ messages in thread

* RE: [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support
  2019-03-14  9:39     ` Claudiu.Beznea
  (?)
@ 2019-03-14 13:07       ` Anson Huang
  -1 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14 13:07 UTC (permalink / raw)
  To: Claudiu.Beznea, thierry.reding, robh+dt, mark.rutland, shawnguo,
	s.hauer, kernel, festevam, linux, stefan, otavio,
	Leonard Crestez, schnitzeltony, jan.tuerk, Robin Gong, linux-pwm,
	devicetree, linux-arm-kernel, linux-kernel, u.kleine-koenig
  Cc: dl-linux-imx

Hi, Claudiu

Best Regards!
Anson Huang

> -----Original Message-----
> From: Claudiu.Beznea@microchip.com
> [mailto:Claudiu.Beznea@microchip.com]
> Sent: 2019年3月14日 17:40
> To: Anson Huang <anson.huang@nxp.com>; thierry.reding@gmail.com;
> robh+dt@kernel.org; mark.rutland@arm.com; shawnguo@kernel.org;
> s.hauer@pengutronix.de; kernel@pengutronix.de; festevam@gmail.com;
> linux@armlinux.org.uk; stefan@agner.ch; otavio@ossystems.com.br;
> Leonard Crestez <leonard.crestez@nxp.com>; schnitzeltony@gmail.com;
> jan.tuerk@emtrion.com; Robin Gong <yibin.gong@nxp.com>; linux-
> pwm@vger.kernel.org; devicetree@vger.kernel.org; linux-arm-
> kernel@lists.infradead.org; linux-kernel@vger.kernel.org; u.kleine-
> koenig@pengutronix.de
> Cc: dl-linux-imx <linux-imx@nxp.com>
> Subject: Re: [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support
> 
> 
> 
> On 14.03.2019 08:27, Anson Huang wrote:
> > i.MX7ULP has TPM(Low Power Timer/Pulse Width Modulation Module)
> > inside, add TPM PWM driver support.
> >
> > Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
> > ---
> > Changes since V2:
> > 	- Add "IMX_" as prefix to macro define as TPM is already used;
> > 	- Use macro define for channel registers address instead of
> calculation in everywhere.
> > 	- Add limitations statement in copyright.
> > ---
> >  drivers/pwm/Kconfig       |  10 ++
> >  drivers/pwm/Makefile      |   1 +
> >  drivers/pwm/pwm-imx-tpm.c | 343
> > ++++++++++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 354 insertions(+)
> >  create mode 100644 drivers/pwm/pwm-imx-tpm.c
> >
> > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index
> > a8f47df..c1cbb43 100644
> > --- a/drivers/pwm/Kconfig
> > +++ b/drivers/pwm/Kconfig
> > @@ -201,6 +201,16 @@ config PWM_IMX
> >  	  To compile this driver as a module, choose M here: the module
> >  	  will be called pwm-imx.
> >
> > +config PWM_IMX_TPM
> > +	tristate "i.MX TPM PWM support"
> > +	depends on ARCH_MXC
> > +	help
> > +	  Generic PWM framework driver for i.MX7ULP TPM module, TPM's
> full
> > +	  name is Low Power Timer/Pulse Width Modulation Module.
> > +
> > +	  To compile this driver as a module, choose M here: the module
> > +	  will be called pwm-imx-tpm.
> > +
> >  config PWM_JZ4740
> >  	tristate "Ingenic JZ47xx PWM support"
> >  	depends on MACH_INGENIC
> > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index
> > 9c676a0..64e036c 100644
> > --- a/drivers/pwm/Makefile
> > +++ b/drivers/pwm/Makefile
> > @@ -18,6 +18,7 @@ obj-$(CONFIG_PWM_FSL_FTM)	+= pwm-fsl-ftm.o
> >  obj-$(CONFIG_PWM_HIBVT)		+= pwm-hibvt.o
> >  obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
> >  obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o
> > +obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
> >  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
> >  obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
> >  obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
> > diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c
> new
> > file mode 100644 index 0000000..cdb29f6
> > --- /dev/null
> > +++ b/drivers/pwm/pwm-imx-tpm.c
> > @@ -0,0 +1,343 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright 2018-2019 NXP.
> > + *
> > + * Limitations:
> > + * - The TPM counter and period counter are shared between
> > + *   multiple channels, so all channels should use same period
> > + *   settings.
> > + * - When a channel is disabled, its polarity settings will be
> > + *   saved and its output will be disabled by clearing polarity
> > + *   setting, when channel is enabled, polarity settings will be
> > + *   restored and output will be enabled again.
> > + * - ONLY when all channels are disabled, then TPM counter will
> > + *   be disabled.
> > + */
> > +
> > +#include <linux/bitops.h>
> > +#include <linux/clk.h>
> > +#include <linux/err.h>
> > +#include <linux/io.h>
> > +#include <linux/log2.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/of_address.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pwm.h>
> > +#include <linux/slab.h>
> > +#include <linux/spinlock.h>
> > +
> > +#define IMX_TPM_GLOBAL		0x8
> > +#define IMX_TPM_SC		0x10
> > +#define IMX_TPM_CNT		0x14
> > +#define IMX_TPM_MOD		0x18
> > +#define IMX_TPM_C0SC		0x20
> > +#define IMX_TPM_C0V		0x24
> > +
> > +#define IMX_TPM_SC_CMOD_SHIFT	3
> > +#define IMX_TPM_SC_CMOD_MASK	GENMASK(4, 3)
> > +#define IMX_TPM_SC_CPWMS	BIT(5)
> > +
> > +#define IMX_TPM_CnSC_CHF	BIT(7)
> > +#define IMX_TPM_CnSC_MSnB	BIT(5)
> > +#define IMX_TPM_CnSC_MSnA	BIT(4)
> > +#define IMX_TPM_CnSC_ELSnB	BIT(3)
> > +#define IMX_TPM_CnSC_ELSnA	BIT(2)
> > +
> > +#define IMX_TPM_SC_PS_MASK	0x7
> > +#define IMX_TPM_MOD_MOD_MASK	0xffff
> > +
> > +#define IMX_TPM_COUNT_MAX	0xffff
> > +
> > +#define IMX_TPM_DEFAULT_PWM_CHANNEL_NUM	2
> > +
> > +#define IMX_TPM_CnSC(n)	(IMX_TPM_C0SC + n * 0x8)
> > +#define IMX_TPM_CnV(n)	(IMX_TPM_C0V + n * 0x8)
> > +
> > +struct imx_tpm_pwm_chip {
> > +	struct pwm_chip chip;
> > +	struct clk *clk;
> > +	void __iomem *base;
> > +	spinlock_t lock;
> > +	u32 chn_config[IMX_TPM_DEFAULT_PWM_CHANNEL_NUM];
> > +	bool chn_status[IMX_TPM_DEFAULT_PWM_CHANNEL_NUM];
> > +};
> > +
> > +#define to_imx_tpm_pwm_chip(_chip)	container_of(_chip, struct
> imx_tpm_pwm_chip, chip)
> > +
> > +static void imx_tpm_pwm_config_counter(struct pwm_chip *chip, u32
> > +period) {
> > +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> > +	unsigned int period_cnt;
> > +	u32 val, div;
> > +	u64 tmp;
> > +
> > +	tmp = clk_get_rate(tpm->clk);
> > +	tmp *= period;
> > +	val = DIV_ROUND_CLOSEST_ULL(tmp, NSEC_PER_SEC);
> > +	if (val < IMX_TPM_COUNT_MAX)
> > +		div = 0;
> > +	else
> > +		div = ilog2(roundup_pow_of_two(val /
> IMX_TPM_COUNT_MAX));
> > +	if (div > IMX_TPM_SC_PS_MASK) {
> > +		dev_err(chip->dev,
> > +			"failed to find valid prescale value!\n");
> > +		return;
> > +	}
> > +	/* set TPM counter prescale */
> > +	val = readl(tpm->base + IMX_TPM_SC);
> > +	val &= ~IMX_TPM_SC_PS_MASK;
> > +	val |= div;
> > +	writel(val, tpm->base + IMX_TPM_SC);
> > +
> > +	/* set period counter */
> > +	do_div(tmp, NSEC_PER_SEC);
> > +	period_cnt = DIV_ROUND_CLOSEST_ULL(tmp, 1 << div);
> > +	writel(period_cnt & IMX_TPM_MOD_MOD_MASK, tpm->base +
> IMX_TPM_MOD);
> > +}
> > +
> > +static void imx_tpm_pwm_config(struct pwm_chip *chip,
> > +			       struct pwm_device *pwm,
> > +			       struct pwm_state *state)
> > +{
> > +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> > +	static bool tpm_cnt_initialized;
> > +	unsigned int duty_cnt;
> > +	u32 val;
> > +	u64 tmp;
> > +
> > +	/*
> > +	 * TPM counter is shared by multi channels, let's make it to be
> > +	 * ONLY first channel can config TPM counter's precale and period
> > +	 * count.
> > +	 */
> > +	if (!tpm_cnt_initialized) {
> > +		imx_tpm_pwm_config_counter(chip, state->period);
> > +		tpm_cnt_initialized = true;
> > +	}
> > +
> > +	/* set duty counter */
> > +	tmp = readl(tpm->base + IMX_TPM_MOD) &
> IMX_TPM_MOD_MOD_MASK;
> > +	tmp *= state->duty_cycle;
> > +	duty_cnt = DIV_ROUND_CLOSEST_ULL(tmp, state->period);
> > +	writel(duty_cnt & IMX_TPM_MOD_MOD_MASK,
> > +		 tpm->base + IMX_TPM_CnV(pwm->hwpwm));
> > +
> > +	/* set polarity */
> > +	val = readl(tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> > +	val &= ~(IMX_TPM_CnSC_ELSnB | IMX_TPM_CnSC_ELSnA |
> IMX_TPM_CnSC_MSnA);
> > +	val |= IMX_TPM_CnSC_MSnB;
> > +	val |= state->polarity ? IMX_TPM_CnSC_ELSnA :
> IMX_TPM_CnSC_ELSnB;
> > +	/*
> > +	 * polarity settings will enabled/disable output statue
> > +	 * immediately, so here ONLY save the config and will be
> > +	 * written into register when channel is enabled/disabled.
> > +	 */
> > +	tpm->chn_config[pwm->hwpwm] = val;
> > +}
> > +
> > +static void imx_tpm_pwm_enable(struct pwm_chip *chip,
> > +			       struct pwm_device *pwm,
> > +			       bool enable)
> > +{
> > +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> > +	u32 val, i;
> > +
> > +	val = readl(tpm->base + IMX_TPM_SC);
> > +	if (enable) {
> > +		/* restore channel config */
> > +		writel(tpm->chn_config[pwm->hwpwm],
> > +			tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> > +
> > +		/* start TPM counter anyway */
> > +		val |= 0x1 << IMX_TPM_SC_CMOD_SHIFT;
> > +		writel(val, tpm->base + IMX_TPM_SC);
> > +	} else {
> > +		/* save channel config */
> > +		tpm->chn_config[pwm->hwpwm] = readl(tpm->base +
> > +			IMX_TPM_CnSC(pwm->hwpwm));
> > +		/* disable channel */
> > +		writel(IMX_TPM_CnSC_CHF, tpm->base +
> IMX_TPM_CnSC(pwm->hwpwm));
> > +
> > +		for (i = 0; i < chip->npwm; i++)
> > +			if (i != pwm->hwpwm && tpm->chn_status[i])
> > +				break;
> > +		if (i == chip->npwm) {
> > +			/* stop TPM counter since all channels are disabled
> */
> > +			val &= ~IMX_TPM_SC_CMOD_MASK;
> > +			writel(val, tpm->base + IMX_TPM_SC);
> > +		}
> > +	}
> > +
> > +	/* update channel statue */
> > +	tpm->chn_status[pwm->hwpwm] = enable; }
> > +
> > +static void imx_tpm_pwm_get_state(struct pwm_chip *chip,
> > +				  struct pwm_device *pwm,
> > +				  struct pwm_state *state)
> > +{
> > +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> > +	unsigned long rate, flags;
> > +	u64 tmp;
> > +	u32 val;
> > +
> > +	spin_lock_irqsave(&tpm->lock, flags);
> > +
> > +	/* get period */
> > +	rate = clk_get_rate(tpm->clk);
> > +	tmp = readl(tpm->base + IMX_TPM_MOD);
> > +	val = readl(tpm->base + IMX_TPM_SC);
> > +	val &= IMX_TPM_SC_PS_MASK;
> > +	tmp *= (1 << val) * NSEC_PER_SEC;
> > +	state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate);
> > +
> > +	/* get duty cycle */
> > +	tmp = readl(tpm->base + IMX_TPM_CnV(pwm->hwpwm));
> > +	tmp *= (1 << val) * NSEC_PER_SEC;
> > +	state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate);
> > +
> > +	/* get polarity */
> > +	val = readl(tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> > +	if (val & IMX_TPM_CnSC_ELSnA)
> > +		state->polarity = PWM_POLARITY_INVERSED;
> > +	else
> > +		state->polarity = PWM_POLARITY_NORMAL;
> > +
> > +	/* get channel status */
> > +	state->enabled = tpm->chn_status[pwm->hwpwm] ? true : false;
> > +
> > +	spin_unlock_irqrestore(&tpm->lock, flags); }
> > +
> > +static int imx_tpm_pwm_apply(struct pwm_chip *chip, struct pwm_device
> *pwm,
> > +			  struct pwm_state *state)
> > +{
> > +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> > +	struct pwm_state curstate;
> > +	unsigned long flags;
> > +
> > +	imx_tpm_pwm_get_state(chip, pwm, &curstate);
> > +
> > +	spin_lock_irqsave(&tpm->lock, flags);
> > +
> > +	if (state->period != curstate.period ||
> > +	    state->duty_cycle != curstate.duty_cycle ||
> > +	    state->polarity != curstate.polarity)
> > +		imx_tpm_pwm_config(chip, pwm, state);
> > +
> > +	if (state->enabled != curstate.enabled)
> > +		imx_tpm_pwm_enable(chip, pwm, state->enabled);
> > +
> > +	spin_unlock_irqrestore(&tpm->lock, flags);
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct pwm_ops imx_tpm_pwm_ops = {
> > +	.get_state = imx_tpm_pwm_get_state,
> > +	.apply = imx_tpm_pwm_apply,
> > +	.owner = THIS_MODULE,
> > +};
> > +
> > +static int imx_tpm_pwm_probe(struct platform_device *pdev) {
> > +	struct imx_tpm_pwm_chip *tpm;
> > +	struct resource *res;
> > +	int ret;
> > +
> > +	tpm = devm_kzalloc(&pdev->dev, sizeof(*tpm), GFP_KERNEL);
> > +	if (!tpm)
> > +		return -ENOMEM;
> > +
> > +	platform_set_drvdata(pdev, tpm);
> > +
> > +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > +	tpm->base = devm_ioremap_resource(&pdev->dev, res);
> > +	if (IS_ERR(tpm->base)) {
> > +		ret = PTR_ERR(tpm->base);
> > +		if (ret != -EPROBE_DEFER)
> > +			dev_err(&pdev->dev, "pwm ioremap failed %d\n",
> ret);
> > +		return ret;
> > +	}
> > +
> > +	tpm->clk = devm_clk_get(&pdev->dev, NULL);
> > +	if (IS_ERR(tpm->clk)) {
> > +		ret = PTR_ERR(tpm->clk);
> > +		if (ret != -EPROBE_DEFER)
> > +			dev_err(&pdev->dev, "failed to get pwm clk %d\n",
> ret);
> > +		return ret;
> > +	}
> > +
> > +	ret = clk_prepare_enable(tpm->clk);
> > +	if (ret) {
> > +		dev_err(&pdev->dev,
> > +			"failed to prepare or enable clk %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	tpm->chip.dev = &pdev->dev;
> > +	tpm->chip.ops = &imx_tpm_pwm_ops;
> > +	tpm->chip.base = -1;
> > +	tpm->chip.npwm = IMX_TPM_DEFAULT_PWM_CHANNEL_NUM;
> > +
> > +	spin_lock_init(&tpm->lock);
> > +
> > +	ret = pwmchip_add(&tpm->chip);
> > +	if (ret)
> > +		dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret);
> 
> clk_disable_unprepare() in case of failure? Although I would use
> clk_prepare()/clk_unprepare() in probe and clk_prepare_enable() only when
> PWM is active.

I missed clk disable here for error handling, for the prepare/enable operation, actually
ONLY enable is useful for i.MX7ULP PWM clock, and since the PWM clock is used for
both function and register access, some of the registers writing need to use PWM clock
to sync into register, so just enable clock before register write and disable it immediately
after writing is NOT working, unless we add the register read back check after writing, then
disable the clock, that makes the register writing much more complicated, given that the PWM clock
is normally from OSC and it does NOT consume much power, so here I just make it enabled
after probed, and ONLY disable it after driver is removed or suspended.

> 
> > +
> > +	return ret;
> > +}
> > +
> > +static int imx_tpm_pwm_remove(struct platform_device *pdev) {
> > +	struct imx_tpm_pwm_chip *tpm = platform_get_drvdata(pdev);
> > +
> 
> Ditto

Fix it in V4.


Thanks,
Anson.

> 
> > +	return pwmchip_remove(&tpm->chip);
> > +}
> > +
> > +static int __maybe_unused imx_tpm_pwm_suspend(struct device *dev) {
> > +	struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
> > +
> > +	clk_disable_unprepare(tpm->clk);
> > +
> > +	return 0;
> > +}
> > +
> > +static int __maybe_unused imx_tpm_pwm_resume(struct device *dev) {
> > +	struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
> > +	int ret = clk_prepare_enable(tpm->clk);
> > +
> > +	if (ret)
> > +		dev_err(dev,
> > +			"failed to prepare or enable clk %d\n", ret);
> > +
> > +	return ret;
> > +};
> > +
> > +static SIMPLE_DEV_PM_OPS(imx_tpm_pwm_pm,
> > +			 imx_tpm_pwm_suspend, imx_tpm_pwm_resume);
> > +
> > +static const struct of_device_id imx_tpm_pwm_dt_ids[] = {
> > +	{ .compatible = "fsl,imx-tpm-pwm", },
> > +	{ /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, imx_tpm_pwm_dt_ids);
> > +
> > +static struct platform_driver imx_tpm_pwm_driver = {
> > +	.driver = {
> > +		.name = "imx-tpm-pwm",
> > +		.of_match_table = imx_tpm_pwm_dt_ids,
> > +		.pm = &imx_tpm_pwm_pm,
> > +	},
> > +	.probe	= imx_tpm_pwm_probe,
> > +	.remove = imx_tpm_pwm_remove,
> > +};
> > +module_platform_driver(imx_tpm_pwm_driver);
> > +
> > +MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>");
> > +MODULE_DESCRIPTION("i.MX TPM PWM Driver"); MODULE_LICENSE("GPL
> v2");
> >

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

* RE: [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support
@ 2019-03-14 13:07       ` Anson Huang
  0 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14 13:07 UTC (permalink / raw)
  To: Claudiu.Beznea, thierry.reding, robh+dt, mark.rutland, shawnguo,
	s.hauer, kernel, festevam, linux, stefan, otavio,
	Leonard Crestez, schnitzeltony, jan.tuerk, Robin Gong, linux-pwm
  Cc: dl-linux-imx

Hi, Claudiu

Best Regards!
Anson Huang

> -----Original Message-----
> From: Claudiu.Beznea@microchip.com
> [mailto:Claudiu.Beznea@microchip.com]
> Sent: 2019年3月14日 17:40
> To: Anson Huang <anson.huang@nxp.com>; thierry.reding@gmail.com;
> robh+dt@kernel.org; mark.rutland@arm.com; shawnguo@kernel.org;
> s.hauer@pengutronix.de; kernel@pengutronix.de; festevam@gmail.com;
> linux@armlinux.org.uk; stefan@agner.ch; otavio@ossystems.com.br;
> Leonard Crestez <leonard.crestez@nxp.com>; schnitzeltony@gmail.com;
> jan.tuerk@emtrion.com; Robin Gong <yibin.gong@nxp.com>; linux-
> pwm@vger.kernel.org; devicetree@vger.kernel.org; linux-arm-
> kernel@lists.infradead.org; linux-kernel@vger.kernel.org; u.kleine-
> koenig@pengutronix.de
> Cc: dl-linux-imx <linux-imx@nxp.com>
> Subject: Re: [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support
> 
> 
> 
> On 14.03.2019 08:27, Anson Huang wrote:
> > i.MX7ULP has TPM(Low Power Timer/Pulse Width Modulation Module)
> > inside, add TPM PWM driver support.
> >
> > Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
> > ---
> > Changes since V2:
> > 	- Add "IMX_" as prefix to macro define as TPM is already used;
> > 	- Use macro define for channel registers address instead of
> calculation in everywhere.
> > 	- Add limitations statement in copyright.
> > ---
> >  drivers/pwm/Kconfig       |  10 ++
> >  drivers/pwm/Makefile      |   1 +
> >  drivers/pwm/pwm-imx-tpm.c | 343
> > ++++++++++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 354 insertions(+)
> >  create mode 100644 drivers/pwm/pwm-imx-tpm.c
> >
> > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index
> > a8f47df..c1cbb43 100644
> > --- a/drivers/pwm/Kconfig
> > +++ b/drivers/pwm/Kconfig
> > @@ -201,6 +201,16 @@ config PWM_IMX
> >  	  To compile this driver as a module, choose M here: the module
> >  	  will be called pwm-imx.
> >
> > +config PWM_IMX_TPM
> > +	tristate "i.MX TPM PWM support"
> > +	depends on ARCH_MXC
> > +	help
> > +	  Generic PWM framework driver for i.MX7ULP TPM module, TPM's
> full
> > +	  name is Low Power Timer/Pulse Width Modulation Module.
> > +
> > +	  To compile this driver as a module, choose M here: the module
> > +	  will be called pwm-imx-tpm.
> > +
> >  config PWM_JZ4740
> >  	tristate "Ingenic JZ47xx PWM support"
> >  	depends on MACH_INGENIC
> > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index
> > 9c676a0..64e036c 100644
> > --- a/drivers/pwm/Makefile
> > +++ b/drivers/pwm/Makefile
> > @@ -18,6 +18,7 @@ obj-$(CONFIG_PWM_FSL_FTM)	+= pwm-fsl-ftm.o
> >  obj-$(CONFIG_PWM_HIBVT)		+= pwm-hibvt.o
> >  obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
> >  obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o
> > +obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
> >  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
> >  obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
> >  obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
> > diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c
> new
> > file mode 100644 index 0000000..cdb29f6
> > --- /dev/null
> > +++ b/drivers/pwm/pwm-imx-tpm.c
> > @@ -0,0 +1,343 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright 2018-2019 NXP.
> > + *
> > + * Limitations:
> > + * - The TPM counter and period counter are shared between
> > + *   multiple channels, so all channels should use same period
> > + *   settings.
> > + * - When a channel is disabled, its polarity settings will be
> > + *   saved and its output will be disabled by clearing polarity
> > + *   setting, when channel is enabled, polarity settings will be
> > + *   restored and output will be enabled again.
> > + * - ONLY when all channels are disabled, then TPM counter will
> > + *   be disabled.
> > + */
> > +
> > +#include <linux/bitops.h>
> > +#include <linux/clk.h>
> > +#include <linux/err.h>
> > +#include <linux/io.h>
> > +#include <linux/log2.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/of_address.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pwm.h>
> > +#include <linux/slab.h>
> > +#include <linux/spinlock.h>
> > +
> > +#define IMX_TPM_GLOBAL		0x8
> > +#define IMX_TPM_SC		0x10
> > +#define IMX_TPM_CNT		0x14
> > +#define IMX_TPM_MOD		0x18
> > +#define IMX_TPM_C0SC		0x20
> > +#define IMX_TPM_C0V		0x24
> > +
> > +#define IMX_TPM_SC_CMOD_SHIFT	3
> > +#define IMX_TPM_SC_CMOD_MASK	GENMASK(4, 3)
> > +#define IMX_TPM_SC_CPWMS	BIT(5)
> > +
> > +#define IMX_TPM_CnSC_CHF	BIT(7)
> > +#define IMX_TPM_CnSC_MSnB	BIT(5)
> > +#define IMX_TPM_CnSC_MSnA	BIT(4)
> > +#define IMX_TPM_CnSC_ELSnB	BIT(3)
> > +#define IMX_TPM_CnSC_ELSnA	BIT(2)
> > +
> > +#define IMX_TPM_SC_PS_MASK	0x7
> > +#define IMX_TPM_MOD_MOD_MASK	0xffff
> > +
> > +#define IMX_TPM_COUNT_MAX	0xffff
> > +
> > +#define IMX_TPM_DEFAULT_PWM_CHANNEL_NUM	2
> > +
> > +#define IMX_TPM_CnSC(n)	(IMX_TPM_C0SC + n * 0x8)
> > +#define IMX_TPM_CnV(n)	(IMX_TPM_C0V + n * 0x8)
> > +
> > +struct imx_tpm_pwm_chip {
> > +	struct pwm_chip chip;
> > +	struct clk *clk;
> > +	void __iomem *base;
> > +	spinlock_t lock;
> > +	u32 chn_config[IMX_TPM_DEFAULT_PWM_CHANNEL_NUM];
> > +	bool chn_status[IMX_TPM_DEFAULT_PWM_CHANNEL_NUM];
> > +};
> > +
> > +#define to_imx_tpm_pwm_chip(_chip)	container_of(_chip, struct
> imx_tpm_pwm_chip, chip)
> > +
> > +static void imx_tpm_pwm_config_counter(struct pwm_chip *chip, u32
> > +period) {
> > +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> > +	unsigned int period_cnt;
> > +	u32 val, div;
> > +	u64 tmp;
> > +
> > +	tmp = clk_get_rate(tpm->clk);
> > +	tmp *= period;
> > +	val = DIV_ROUND_CLOSEST_ULL(tmp, NSEC_PER_SEC);
> > +	if (val < IMX_TPM_COUNT_MAX)
> > +		div = 0;
> > +	else
> > +		div = ilog2(roundup_pow_of_two(val /
> IMX_TPM_COUNT_MAX));
> > +	if (div > IMX_TPM_SC_PS_MASK) {
> > +		dev_err(chip->dev,
> > +			"failed to find valid prescale value!\n");
> > +		return;
> > +	}
> > +	/* set TPM counter prescale */
> > +	val = readl(tpm->base + IMX_TPM_SC);
> > +	val &= ~IMX_TPM_SC_PS_MASK;
> > +	val |= div;
> > +	writel(val, tpm->base + IMX_TPM_SC);
> > +
> > +	/* set period counter */
> > +	do_div(tmp, NSEC_PER_SEC);
> > +	period_cnt = DIV_ROUND_CLOSEST_ULL(tmp, 1 << div);
> > +	writel(period_cnt & IMX_TPM_MOD_MOD_MASK, tpm->base +
> IMX_TPM_MOD);
> > +}
> > +
> > +static void imx_tpm_pwm_config(struct pwm_chip *chip,
> > +			       struct pwm_device *pwm,
> > +			       struct pwm_state *state)
> > +{
> > +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> > +	static bool tpm_cnt_initialized;
> > +	unsigned int duty_cnt;
> > +	u32 val;
> > +	u64 tmp;
> > +
> > +	/*
> > +	 * TPM counter is shared by multi channels, let's make it to be
> > +	 * ONLY first channel can config TPM counter's precale and period
> > +	 * count.
> > +	 */
> > +	if (!tpm_cnt_initialized) {
> > +		imx_tpm_pwm_config_counter(chip, state->period);
> > +		tpm_cnt_initialized = true;
> > +	}
> > +
> > +	/* set duty counter */
> > +	tmp = readl(tpm->base + IMX_TPM_MOD) &
> IMX_TPM_MOD_MOD_MASK;
> > +	tmp *= state->duty_cycle;
> > +	duty_cnt = DIV_ROUND_CLOSEST_ULL(tmp, state->period);
> > +	writel(duty_cnt & IMX_TPM_MOD_MOD_MASK,
> > +		 tpm->base + IMX_TPM_CnV(pwm->hwpwm));
> > +
> > +	/* set polarity */
> > +	val = readl(tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> > +	val &= ~(IMX_TPM_CnSC_ELSnB | IMX_TPM_CnSC_ELSnA |
> IMX_TPM_CnSC_MSnA);
> > +	val |= IMX_TPM_CnSC_MSnB;
> > +	val |= state->polarity ? IMX_TPM_CnSC_ELSnA :
> IMX_TPM_CnSC_ELSnB;
> > +	/*
> > +	 * polarity settings will enabled/disable output statue
> > +	 * immediately, so here ONLY save the config and will be
> > +	 * written into register when channel is enabled/disabled.
> > +	 */
> > +	tpm->chn_config[pwm->hwpwm] = val;
> > +}
> > +
> > +static void imx_tpm_pwm_enable(struct pwm_chip *chip,
> > +			       struct pwm_device *pwm,
> > +			       bool enable)
> > +{
> > +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> > +	u32 val, i;
> > +
> > +	val = readl(tpm->base + IMX_TPM_SC);
> > +	if (enable) {
> > +		/* restore channel config */
> > +		writel(tpm->chn_config[pwm->hwpwm],
> > +			tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> > +
> > +		/* start TPM counter anyway */
> > +		val |= 0x1 << IMX_TPM_SC_CMOD_SHIFT;
> > +		writel(val, tpm->base + IMX_TPM_SC);
> > +	} else {
> > +		/* save channel config */
> > +		tpm->chn_config[pwm->hwpwm] = readl(tpm->base +
> > +			IMX_TPM_CnSC(pwm->hwpwm));
> > +		/* disable channel */
> > +		writel(IMX_TPM_CnSC_CHF, tpm->base +
> IMX_TPM_CnSC(pwm->hwpwm));
> > +
> > +		for (i = 0; i < chip->npwm; i++)
> > +			if (i != pwm->hwpwm && tpm->chn_status[i])
> > +				break;
> > +		if (i == chip->npwm) {
> > +			/* stop TPM counter since all channels are disabled
> */
> > +			val &= ~IMX_TPM_SC_CMOD_MASK;
> > +			writel(val, tpm->base + IMX_TPM_SC);
> > +		}
> > +	}
> > +
> > +	/* update channel statue */
> > +	tpm->chn_status[pwm->hwpwm] = enable; }
> > +
> > +static void imx_tpm_pwm_get_state(struct pwm_chip *chip,
> > +				  struct pwm_device *pwm,
> > +				  struct pwm_state *state)
> > +{
> > +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> > +	unsigned long rate, flags;
> > +	u64 tmp;
> > +	u32 val;
> > +
> > +	spin_lock_irqsave(&tpm->lock, flags);
> > +
> > +	/* get period */
> > +	rate = clk_get_rate(tpm->clk);
> > +	tmp = readl(tpm->base + IMX_TPM_MOD);
> > +	val = readl(tpm->base + IMX_TPM_SC);
> > +	val &= IMX_TPM_SC_PS_MASK;
> > +	tmp *= (1 << val) * NSEC_PER_SEC;
> > +	state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate);
> > +
> > +	/* get duty cycle */
> > +	tmp = readl(tpm->base + IMX_TPM_CnV(pwm->hwpwm));
> > +	tmp *= (1 << val) * NSEC_PER_SEC;
> > +	state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate);
> > +
> > +	/* get polarity */
> > +	val = readl(tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> > +	if (val & IMX_TPM_CnSC_ELSnA)
> > +		state->polarity = PWM_POLARITY_INVERSED;
> > +	else
> > +		state->polarity = PWM_POLARITY_NORMAL;
> > +
> > +	/* get channel status */
> > +	state->enabled = tpm->chn_status[pwm->hwpwm] ? true : false;
> > +
> > +	spin_unlock_irqrestore(&tpm->lock, flags); }
> > +
> > +static int imx_tpm_pwm_apply(struct pwm_chip *chip, struct pwm_device
> *pwm,
> > +			  struct pwm_state *state)
> > +{
> > +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> > +	struct pwm_state curstate;
> > +	unsigned long flags;
> > +
> > +	imx_tpm_pwm_get_state(chip, pwm, &curstate);
> > +
> > +	spin_lock_irqsave(&tpm->lock, flags);
> > +
> > +	if (state->period != curstate.period ||
> > +	    state->duty_cycle != curstate.duty_cycle ||
> > +	    state->polarity != curstate.polarity)
> > +		imx_tpm_pwm_config(chip, pwm, state);
> > +
> > +	if (state->enabled != curstate.enabled)
> > +		imx_tpm_pwm_enable(chip, pwm, state->enabled);
> > +
> > +	spin_unlock_irqrestore(&tpm->lock, flags);
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct pwm_ops imx_tpm_pwm_ops = {
> > +	.get_state = imx_tpm_pwm_get_state,
> > +	.apply = imx_tpm_pwm_apply,
> > +	.owner = THIS_MODULE,
> > +};
> > +
> > +static int imx_tpm_pwm_probe(struct platform_device *pdev) {
> > +	struct imx_tpm_pwm_chip *tpm;
> > +	struct resource *res;
> > +	int ret;
> > +
> > +	tpm = devm_kzalloc(&pdev->dev, sizeof(*tpm), GFP_KERNEL);
> > +	if (!tpm)
> > +		return -ENOMEM;
> > +
> > +	platform_set_drvdata(pdev, tpm);
> > +
> > +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > +	tpm->base = devm_ioremap_resource(&pdev->dev, res);
> > +	if (IS_ERR(tpm->base)) {
> > +		ret = PTR_ERR(tpm->base);
> > +		if (ret != -EPROBE_DEFER)
> > +			dev_err(&pdev->dev, "pwm ioremap failed %d\n",
> ret);
> > +		return ret;
> > +	}
> > +
> > +	tpm->clk = devm_clk_get(&pdev->dev, NULL);
> > +	if (IS_ERR(tpm->clk)) {
> > +		ret = PTR_ERR(tpm->clk);
> > +		if (ret != -EPROBE_DEFER)
> > +			dev_err(&pdev->dev, "failed to get pwm clk %d\n",
> ret);
> > +		return ret;
> > +	}
> > +
> > +	ret = clk_prepare_enable(tpm->clk);
> > +	if (ret) {
> > +		dev_err(&pdev->dev,
> > +			"failed to prepare or enable clk %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	tpm->chip.dev = &pdev->dev;
> > +	tpm->chip.ops = &imx_tpm_pwm_ops;
> > +	tpm->chip.base = -1;
> > +	tpm->chip.npwm = IMX_TPM_DEFAULT_PWM_CHANNEL_NUM;
> > +
> > +	spin_lock_init(&tpm->lock);
> > +
> > +	ret = pwmchip_add(&tpm->chip);
> > +	if (ret)
> > +		dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret);
> 
> clk_disable_unprepare() in case of failure? Although I would use
> clk_prepare()/clk_unprepare() in probe and clk_prepare_enable() only when
> PWM is active.

I missed clk disable here for error handling, for the prepare/enable operation, actually
ONLY enable is useful for i.MX7ULP PWM clock, and since the PWM clock is used for
both function and register access, some of the registers writing need to use PWM clock
to sync into register, so just enable clock before register write and disable it immediately
after writing is NOT working, unless we add the register read back check after writing, then
disable the clock, that makes the register writing much more complicated, given that the PWM clock
is normally from OSC and it does NOT consume much power, so here I just make it enabled
after probed, and ONLY disable it after driver is removed or suspended.

> 
> > +
> > +	return ret;
> > +}
> > +
> > +static int imx_tpm_pwm_remove(struct platform_device *pdev) {
> > +	struct imx_tpm_pwm_chip *tpm = platform_get_drvdata(pdev);
> > +
> 
> Ditto

Fix it in V4.


Thanks,
Anson.

> 
> > +	return pwmchip_remove(&tpm->chip);
> > +}
> > +
> > +static int __maybe_unused imx_tpm_pwm_suspend(struct device *dev) {
> > +	struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
> > +
> > +	clk_disable_unprepare(tpm->clk);
> > +
> > +	return 0;
> > +}
> > +
> > +static int __maybe_unused imx_tpm_pwm_resume(struct device *dev) {
> > +	struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
> > +	int ret = clk_prepare_enable(tpm->clk);
> > +
> > +	if (ret)
> > +		dev_err(dev,
> > +			"failed to prepare or enable clk %d\n", ret);
> > +
> > +	return ret;
> > +};
> > +
> > +static SIMPLE_DEV_PM_OPS(imx_tpm_pwm_pm,
> > +			 imx_tpm_pwm_suspend, imx_tpm_pwm_resume);
> > +
> > +static const struct of_device_id imx_tpm_pwm_dt_ids[] = {
> > +	{ .compatible = "fsl,imx-tpm-pwm", },
> > +	{ /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, imx_tpm_pwm_dt_ids);
> > +
> > +static struct platform_driver imx_tpm_pwm_driver = {
> > +	.driver = {
> > +		.name = "imx-tpm-pwm",
> > +		.of_match_table = imx_tpm_pwm_dt_ids,
> > +		.pm = &imx_tpm_pwm_pm,
> > +	},
> > +	.probe	= imx_tpm_pwm_probe,
> > +	.remove = imx_tpm_pwm_remove,
> > +};
> > +module_platform_driver(imx_tpm_pwm_driver);
> > +
> > +MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>");
> > +MODULE_DESCRIPTION("i.MX TPM PWM Driver"); MODULE_LICENSE("GPL
> v2");
> >

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

* RE: [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support
@ 2019-03-14 13:07       ` Anson Huang
  0 siblings, 0 replies; 30+ messages in thread
From: Anson Huang @ 2019-03-14 13:07 UTC (permalink / raw)
  To: Claudiu.Beznea, thierry.reding, robh+dt, mark.rutland, shawnguo,
	s.hauer, kernel, festevam, linux, stefan, otavio,
	Leonard Crestez, schnitzeltony, jan.tuerk, Robin Gong, linux-pwm,
	devicetree, linux-arm-kernel, linux-kernel, u.kleine-koenig
  Cc: dl-linux-imx

Hi, Claudiu

Best Regards!
Anson Huang

> -----Original Message-----
> From: Claudiu.Beznea@microchip.com
> [mailto:Claudiu.Beznea@microchip.com]
> Sent: 2019年3月14日 17:40
> To: Anson Huang <anson.huang@nxp.com>; thierry.reding@gmail.com;
> robh+dt@kernel.org; mark.rutland@arm.com; shawnguo@kernel.org;
> s.hauer@pengutronix.de; kernel@pengutronix.de; festevam@gmail.com;
> linux@armlinux.org.uk; stefan@agner.ch; otavio@ossystems.com.br;
> Leonard Crestez <leonard.crestez@nxp.com>; schnitzeltony@gmail.com;
> jan.tuerk@emtrion.com; Robin Gong <yibin.gong@nxp.com>; linux-
> pwm@vger.kernel.org; devicetree@vger.kernel.org; linux-arm-
> kernel@lists.infradead.org; linux-kernel@vger.kernel.org; u.kleine-
> koenig@pengutronix.de
> Cc: dl-linux-imx <linux-imx@nxp.com>
> Subject: Re: [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support
> 
> 
> 
> On 14.03.2019 08:27, Anson Huang wrote:
> > i.MX7ULP has TPM(Low Power Timer/Pulse Width Modulation Module)
> > inside, add TPM PWM driver support.
> >
> > Signed-off-by: Anson Huang <Anson.Huang@nxp.com>
> > ---
> > Changes since V2:
> > 	- Add "IMX_" as prefix to macro define as TPM is already used;
> > 	- Use macro define for channel registers address instead of
> calculation in everywhere.
> > 	- Add limitations statement in copyright.
> > ---
> >  drivers/pwm/Kconfig       |  10 ++
> >  drivers/pwm/Makefile      |   1 +
> >  drivers/pwm/pwm-imx-tpm.c | 343
> > ++++++++++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 354 insertions(+)
> >  create mode 100644 drivers/pwm/pwm-imx-tpm.c
> >
> > diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index
> > a8f47df..c1cbb43 100644
> > --- a/drivers/pwm/Kconfig
> > +++ b/drivers/pwm/Kconfig
> > @@ -201,6 +201,16 @@ config PWM_IMX
> >  	  To compile this driver as a module, choose M here: the module
> >  	  will be called pwm-imx.
> >
> > +config PWM_IMX_TPM
> > +	tristate "i.MX TPM PWM support"
> > +	depends on ARCH_MXC
> > +	help
> > +	  Generic PWM framework driver for i.MX7ULP TPM module, TPM's
> full
> > +	  name is Low Power Timer/Pulse Width Modulation Module.
> > +
> > +	  To compile this driver as a module, choose M here: the module
> > +	  will be called pwm-imx-tpm.
> > +
> >  config PWM_JZ4740
> >  	tristate "Ingenic JZ47xx PWM support"
> >  	depends on MACH_INGENIC
> > diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index
> > 9c676a0..64e036c 100644
> > --- a/drivers/pwm/Makefile
> > +++ b/drivers/pwm/Makefile
> > @@ -18,6 +18,7 @@ obj-$(CONFIG_PWM_FSL_FTM)	+= pwm-fsl-ftm.o
> >  obj-$(CONFIG_PWM_HIBVT)		+= pwm-hibvt.o
> >  obj-$(CONFIG_PWM_IMG)		+= pwm-img.o
> >  obj-$(CONFIG_PWM_IMX)		+= pwm-imx.o
> > +obj-$(CONFIG_PWM_IMX_TPM)	+= pwm-imx-tpm.o
> >  obj-$(CONFIG_PWM_JZ4740)	+= pwm-jz4740.o
> >  obj-$(CONFIG_PWM_LP3943)	+= pwm-lp3943.o
> >  obj-$(CONFIG_PWM_LPC18XX_SCT)	+= pwm-lpc18xx-sct.o
> > diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c
> new
> > file mode 100644 index 0000000..cdb29f6
> > --- /dev/null
> > +++ b/drivers/pwm/pwm-imx-tpm.c
> > @@ -0,0 +1,343 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright 2018-2019 NXP.
> > + *
> > + * Limitations:
> > + * - The TPM counter and period counter are shared between
> > + *   multiple channels, so all channels should use same period
> > + *   settings.
> > + * - When a channel is disabled, its polarity settings will be
> > + *   saved and its output will be disabled by clearing polarity
> > + *   setting, when channel is enabled, polarity settings will be
> > + *   restored and output will be enabled again.
> > + * - ONLY when all channels are disabled, then TPM counter will
> > + *   be disabled.
> > + */
> > +
> > +#include <linux/bitops.h>
> > +#include <linux/clk.h>
> > +#include <linux/err.h>
> > +#include <linux/io.h>
> > +#include <linux/log2.h>
> > +#include <linux/module.h>
> > +#include <linux/of.h>
> > +#include <linux/of_address.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/pwm.h>
> > +#include <linux/slab.h>
> > +#include <linux/spinlock.h>
> > +
> > +#define IMX_TPM_GLOBAL		0x8
> > +#define IMX_TPM_SC		0x10
> > +#define IMX_TPM_CNT		0x14
> > +#define IMX_TPM_MOD		0x18
> > +#define IMX_TPM_C0SC		0x20
> > +#define IMX_TPM_C0V		0x24
> > +
> > +#define IMX_TPM_SC_CMOD_SHIFT	3
> > +#define IMX_TPM_SC_CMOD_MASK	GENMASK(4, 3)
> > +#define IMX_TPM_SC_CPWMS	BIT(5)
> > +
> > +#define IMX_TPM_CnSC_CHF	BIT(7)
> > +#define IMX_TPM_CnSC_MSnB	BIT(5)
> > +#define IMX_TPM_CnSC_MSnA	BIT(4)
> > +#define IMX_TPM_CnSC_ELSnB	BIT(3)
> > +#define IMX_TPM_CnSC_ELSnA	BIT(2)
> > +
> > +#define IMX_TPM_SC_PS_MASK	0x7
> > +#define IMX_TPM_MOD_MOD_MASK	0xffff
> > +
> > +#define IMX_TPM_COUNT_MAX	0xffff
> > +
> > +#define IMX_TPM_DEFAULT_PWM_CHANNEL_NUM	2
> > +
> > +#define IMX_TPM_CnSC(n)	(IMX_TPM_C0SC + n * 0x8)
> > +#define IMX_TPM_CnV(n)	(IMX_TPM_C0V + n * 0x8)
> > +
> > +struct imx_tpm_pwm_chip {
> > +	struct pwm_chip chip;
> > +	struct clk *clk;
> > +	void __iomem *base;
> > +	spinlock_t lock;
> > +	u32 chn_config[IMX_TPM_DEFAULT_PWM_CHANNEL_NUM];
> > +	bool chn_status[IMX_TPM_DEFAULT_PWM_CHANNEL_NUM];
> > +};
> > +
> > +#define to_imx_tpm_pwm_chip(_chip)	container_of(_chip, struct
> imx_tpm_pwm_chip, chip)
> > +
> > +static void imx_tpm_pwm_config_counter(struct pwm_chip *chip, u32
> > +period) {
> > +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> > +	unsigned int period_cnt;
> > +	u32 val, div;
> > +	u64 tmp;
> > +
> > +	tmp = clk_get_rate(tpm->clk);
> > +	tmp *= period;
> > +	val = DIV_ROUND_CLOSEST_ULL(tmp, NSEC_PER_SEC);
> > +	if (val < IMX_TPM_COUNT_MAX)
> > +		div = 0;
> > +	else
> > +		div = ilog2(roundup_pow_of_two(val /
> IMX_TPM_COUNT_MAX));
> > +	if (div > IMX_TPM_SC_PS_MASK) {
> > +		dev_err(chip->dev,
> > +			"failed to find valid prescale value!\n");
> > +		return;
> > +	}
> > +	/* set TPM counter prescale */
> > +	val = readl(tpm->base + IMX_TPM_SC);
> > +	val &= ~IMX_TPM_SC_PS_MASK;
> > +	val |= div;
> > +	writel(val, tpm->base + IMX_TPM_SC);
> > +
> > +	/* set period counter */
> > +	do_div(tmp, NSEC_PER_SEC);
> > +	period_cnt = DIV_ROUND_CLOSEST_ULL(tmp, 1 << div);
> > +	writel(period_cnt & IMX_TPM_MOD_MOD_MASK, tpm->base +
> IMX_TPM_MOD);
> > +}
> > +
> > +static void imx_tpm_pwm_config(struct pwm_chip *chip,
> > +			       struct pwm_device *pwm,
> > +			       struct pwm_state *state)
> > +{
> > +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> > +	static bool tpm_cnt_initialized;
> > +	unsigned int duty_cnt;
> > +	u32 val;
> > +	u64 tmp;
> > +
> > +	/*
> > +	 * TPM counter is shared by multi channels, let's make it to be
> > +	 * ONLY first channel can config TPM counter's precale and period
> > +	 * count.
> > +	 */
> > +	if (!tpm_cnt_initialized) {
> > +		imx_tpm_pwm_config_counter(chip, state->period);
> > +		tpm_cnt_initialized = true;
> > +	}
> > +
> > +	/* set duty counter */
> > +	tmp = readl(tpm->base + IMX_TPM_MOD) &
> IMX_TPM_MOD_MOD_MASK;
> > +	tmp *= state->duty_cycle;
> > +	duty_cnt = DIV_ROUND_CLOSEST_ULL(tmp, state->period);
> > +	writel(duty_cnt & IMX_TPM_MOD_MOD_MASK,
> > +		 tpm->base + IMX_TPM_CnV(pwm->hwpwm));
> > +
> > +	/* set polarity */
> > +	val = readl(tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> > +	val &= ~(IMX_TPM_CnSC_ELSnB | IMX_TPM_CnSC_ELSnA |
> IMX_TPM_CnSC_MSnA);
> > +	val |= IMX_TPM_CnSC_MSnB;
> > +	val |= state->polarity ? IMX_TPM_CnSC_ELSnA :
> IMX_TPM_CnSC_ELSnB;
> > +	/*
> > +	 * polarity settings will enabled/disable output statue
> > +	 * immediately, so here ONLY save the config and will be
> > +	 * written into register when channel is enabled/disabled.
> > +	 */
> > +	tpm->chn_config[pwm->hwpwm] = val;
> > +}
> > +
> > +static void imx_tpm_pwm_enable(struct pwm_chip *chip,
> > +			       struct pwm_device *pwm,
> > +			       bool enable)
> > +{
> > +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> > +	u32 val, i;
> > +
> > +	val = readl(tpm->base + IMX_TPM_SC);
> > +	if (enable) {
> > +		/* restore channel config */
> > +		writel(tpm->chn_config[pwm->hwpwm],
> > +			tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> > +
> > +		/* start TPM counter anyway */
> > +		val |= 0x1 << IMX_TPM_SC_CMOD_SHIFT;
> > +		writel(val, tpm->base + IMX_TPM_SC);
> > +	} else {
> > +		/* save channel config */
> > +		tpm->chn_config[pwm->hwpwm] = readl(tpm->base +
> > +			IMX_TPM_CnSC(pwm->hwpwm));
> > +		/* disable channel */
> > +		writel(IMX_TPM_CnSC_CHF, tpm->base +
> IMX_TPM_CnSC(pwm->hwpwm));
> > +
> > +		for (i = 0; i < chip->npwm; i++)
> > +			if (i != pwm->hwpwm && tpm->chn_status[i])
> > +				break;
> > +		if (i == chip->npwm) {
> > +			/* stop TPM counter since all channels are disabled
> */
> > +			val &= ~IMX_TPM_SC_CMOD_MASK;
> > +			writel(val, tpm->base + IMX_TPM_SC);
> > +		}
> > +	}
> > +
> > +	/* update channel statue */
> > +	tpm->chn_status[pwm->hwpwm] = enable; }
> > +
> > +static void imx_tpm_pwm_get_state(struct pwm_chip *chip,
> > +				  struct pwm_device *pwm,
> > +				  struct pwm_state *state)
> > +{
> > +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> > +	unsigned long rate, flags;
> > +	u64 tmp;
> > +	u32 val;
> > +
> > +	spin_lock_irqsave(&tpm->lock, flags);
> > +
> > +	/* get period */
> > +	rate = clk_get_rate(tpm->clk);
> > +	tmp = readl(tpm->base + IMX_TPM_MOD);
> > +	val = readl(tpm->base + IMX_TPM_SC);
> > +	val &= IMX_TPM_SC_PS_MASK;
> > +	tmp *= (1 << val) * NSEC_PER_SEC;
> > +	state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate);
> > +
> > +	/* get duty cycle */
> > +	tmp = readl(tpm->base + IMX_TPM_CnV(pwm->hwpwm));
> > +	tmp *= (1 << val) * NSEC_PER_SEC;
> > +	state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate);
> > +
> > +	/* get polarity */
> > +	val = readl(tpm->base + IMX_TPM_CnSC(pwm->hwpwm));
> > +	if (val & IMX_TPM_CnSC_ELSnA)
> > +		state->polarity = PWM_POLARITY_INVERSED;
> > +	else
> > +		state->polarity = PWM_POLARITY_NORMAL;
> > +
> > +	/* get channel status */
> > +	state->enabled = tpm->chn_status[pwm->hwpwm] ? true : false;
> > +
> > +	spin_unlock_irqrestore(&tpm->lock, flags); }
> > +
> > +static int imx_tpm_pwm_apply(struct pwm_chip *chip, struct pwm_device
> *pwm,
> > +			  struct pwm_state *state)
> > +{
> > +	struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip);
> > +	struct pwm_state curstate;
> > +	unsigned long flags;
> > +
> > +	imx_tpm_pwm_get_state(chip, pwm, &curstate);
> > +
> > +	spin_lock_irqsave(&tpm->lock, flags);
> > +
> > +	if (state->period != curstate.period ||
> > +	    state->duty_cycle != curstate.duty_cycle ||
> > +	    state->polarity != curstate.polarity)
> > +		imx_tpm_pwm_config(chip, pwm, state);
> > +
> > +	if (state->enabled != curstate.enabled)
> > +		imx_tpm_pwm_enable(chip, pwm, state->enabled);
> > +
> > +	spin_unlock_irqrestore(&tpm->lock, flags);
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct pwm_ops imx_tpm_pwm_ops = {
> > +	.get_state = imx_tpm_pwm_get_state,
> > +	.apply = imx_tpm_pwm_apply,
> > +	.owner = THIS_MODULE,
> > +};
> > +
> > +static int imx_tpm_pwm_probe(struct platform_device *pdev) {
> > +	struct imx_tpm_pwm_chip *tpm;
> > +	struct resource *res;
> > +	int ret;
> > +
> > +	tpm = devm_kzalloc(&pdev->dev, sizeof(*tpm), GFP_KERNEL);
> > +	if (!tpm)
> > +		return -ENOMEM;
> > +
> > +	platform_set_drvdata(pdev, tpm);
> > +
> > +	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> > +	tpm->base = devm_ioremap_resource(&pdev->dev, res);
> > +	if (IS_ERR(tpm->base)) {
> > +		ret = PTR_ERR(tpm->base);
> > +		if (ret != -EPROBE_DEFER)
> > +			dev_err(&pdev->dev, "pwm ioremap failed %d\n",
> ret);
> > +		return ret;
> > +	}
> > +
> > +	tpm->clk = devm_clk_get(&pdev->dev, NULL);
> > +	if (IS_ERR(tpm->clk)) {
> > +		ret = PTR_ERR(tpm->clk);
> > +		if (ret != -EPROBE_DEFER)
> > +			dev_err(&pdev->dev, "failed to get pwm clk %d\n",
> ret);
> > +		return ret;
> > +	}
> > +
> > +	ret = clk_prepare_enable(tpm->clk);
> > +	if (ret) {
> > +		dev_err(&pdev->dev,
> > +			"failed to prepare or enable clk %d\n", ret);
> > +		return ret;
> > +	}
> > +
> > +	tpm->chip.dev = &pdev->dev;
> > +	tpm->chip.ops = &imx_tpm_pwm_ops;
> > +	tpm->chip.base = -1;
> > +	tpm->chip.npwm = IMX_TPM_DEFAULT_PWM_CHANNEL_NUM;
> > +
> > +	spin_lock_init(&tpm->lock);
> > +
> > +	ret = pwmchip_add(&tpm->chip);
> > +	if (ret)
> > +		dev_err(&pdev->dev, "failed to add pwm chip %d\n", ret);
> 
> clk_disable_unprepare() in case of failure? Although I would use
> clk_prepare()/clk_unprepare() in probe and clk_prepare_enable() only when
> PWM is active.

I missed clk disable here for error handling, for the prepare/enable operation, actually
ONLY enable is useful for i.MX7ULP PWM clock, and since the PWM clock is used for
both function and register access, some of the registers writing need to use PWM clock
to sync into register, so just enable clock before register write and disable it immediately
after writing is NOT working, unless we add the register read back check after writing, then
disable the clock, that makes the register writing much more complicated, given that the PWM clock
is normally from OSC and it does NOT consume much power, so here I just make it enabled
after probed, and ONLY disable it after driver is removed or suspended.

> 
> > +
> > +	return ret;
> > +}
> > +
> > +static int imx_tpm_pwm_remove(struct platform_device *pdev) {
> > +	struct imx_tpm_pwm_chip *tpm = platform_get_drvdata(pdev);
> > +
> 
> Ditto

Fix it in V4.


Thanks,
Anson.

> 
> > +	return pwmchip_remove(&tpm->chip);
> > +}
> > +
> > +static int __maybe_unused imx_tpm_pwm_suspend(struct device *dev) {
> > +	struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
> > +
> > +	clk_disable_unprepare(tpm->clk);
> > +
> > +	return 0;
> > +}
> > +
> > +static int __maybe_unused imx_tpm_pwm_resume(struct device *dev) {
> > +	struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev);
> > +	int ret = clk_prepare_enable(tpm->clk);
> > +
> > +	if (ret)
> > +		dev_err(dev,
> > +			"failed to prepare or enable clk %d\n", ret);
> > +
> > +	return ret;
> > +};
> > +
> > +static SIMPLE_DEV_PM_OPS(imx_tpm_pwm_pm,
> > +			 imx_tpm_pwm_suspend, imx_tpm_pwm_resume);
> > +
> > +static const struct of_device_id imx_tpm_pwm_dt_ids[] = {
> > +	{ .compatible = "fsl,imx-tpm-pwm", },
> > +	{ /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, imx_tpm_pwm_dt_ids);
> > +
> > +static struct platform_driver imx_tpm_pwm_driver = {
> > +	.driver = {
> > +		.name = "imx-tpm-pwm",
> > +		.of_match_table = imx_tpm_pwm_dt_ids,
> > +		.pm = &imx_tpm_pwm_pm,
> > +	},
> > +	.probe	= imx_tpm_pwm_probe,
> > +	.remove = imx_tpm_pwm_remove,
> > +};
> > +module_platform_driver(imx_tpm_pwm_driver);
> > +
> > +MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>");
> > +MODULE_DESCRIPTION("i.MX TPM PWM Driver"); MODULE_LICENSE("GPL
> v2");
> >
_______________________________________________
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] 30+ messages in thread

* Re: [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support
  2019-03-14 13:07       ` Anson Huang
  (?)
@ 2019-03-14 14:32         ` Claudiu.Beznea
  -1 siblings, 0 replies; 30+ messages in thread
From: Claudiu.Beznea @ 2019-03-14 14:32 UTC (permalink / raw)
  To: anson.huang, thierry.reding, robh+dt, mark.rutland, shawnguo,
	s.hauer, kernel, festevam, linux, stefan, otavio,
	leonard.crestez, schnitzeltony, jan.tuerk, yibin.gong, linux-pwm,
	devicetree, linux-arm-kernel, linux-kernel, u.kleine-koenig
  Cc: linux-imx



On 14.03.2019 15:07, Anson Huang wrote:
> so here I just make it enabled
> after probed, and ONLY disable it after driver is removed or suspended.

Ok, agree!

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

* Re: [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support
@ 2019-03-14 14:32         ` Claudiu.Beznea
  0 siblings, 0 replies; 30+ messages in thread
From: Claudiu.Beznea @ 2019-03-14 14:32 UTC (permalink / raw)
  To: anson.huang, thierry.reding, robh+dt, mark.rutland, shawnguo,
	s.hauer, kernel, festevam, linux, stefan, otavio,
	leonard.crestez, schnitzeltony, jan.tuerk, yibin.gong, linux-pwm,
	devicetree, linux-arm-kernel, linux-kernel, u.kleine-koenig
  Cc: linux-imx



On 14.03.2019 15:07, Anson Huang wrote:
> so here I just make it enabled
> after probed, and ONLY disable it after driver is removed or suspended.

Ok, agree!

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

* Re: [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support
@ 2019-03-14 14:32         ` Claudiu.Beznea
  0 siblings, 0 replies; 30+ messages in thread
From: Claudiu.Beznea @ 2019-03-14 14:32 UTC (permalink / raw)
  To: anson.huang, thierry.reding, robh+dt, mark.rutland, shawnguo,
	s.hauer, kernel, festevam, linux, stefan, otavio,
	leonard.crestez, schnitzeltony, jan.tuerk, yibin.gong, linux-pwm,
	devicetree, linux-arm-kernel, linux-kernel, u.kleine-koenig
  Cc: linux-imx



On 14.03.2019 15:07, Anson Huang wrote:
> so here I just make it enabled
> after probed, and ONLY disable it after driver is removed or suspended.

Ok, agree!
_______________________________________________
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] 30+ messages in thread

end of thread, other threads:[~2019-03-14 14:32 UTC | newest]

Thread overview: 30+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-03-14  6:27 [PATCH V3 0/5] Add i.MX7ULP EVK PWM backlight support Anson Huang
2019-03-14  6:27 ` Anson Huang
2019-03-14  6:27 ` Anson Huang
2019-03-14  6:27 ` [PATCH V3 1/5] dt-bindings: pwm: Add i.MX TPM PWM binding Anson Huang
2019-03-14  6:27   ` Anson Huang
2019-03-14  6:27   ` Anson Huang
2019-03-14  6:27 ` [PATCH V3 2/5] pwm: Add i.MX TPM PWM driver support Anson Huang
2019-03-14  6:27   ` Anson Huang
2019-03-14  6:27   ` Anson Huang
2019-03-14  9:22   ` Uwe Kleine-König
2019-03-14  9:22     ` Uwe Kleine-König
2019-03-14  9:22     ` Uwe Kleine-König
2019-03-14  9:39   ` Claudiu.Beznea
2019-03-14  9:39     ` Claudiu.Beznea
2019-03-14  9:39     ` Claudiu.Beznea
2019-03-14 13:07     ` Anson Huang
2019-03-14 13:07       ` Anson Huang
2019-03-14 13:07       ` Anson Huang
2019-03-14 14:32       ` Claudiu.Beznea
2019-03-14 14:32         ` Claudiu.Beznea
2019-03-14 14:32         ` Claudiu.Beznea
2019-03-14  6:27 ` [PATCH V3 3/5] ARM: imx_v6_v7_defconfig: Add TPM PWM support by default Anson Huang
2019-03-14  6:27   ` Anson Huang
2019-03-14  6:27   ` Anson Huang
2019-03-14  6:27 ` [PATCH V3 4/5] ARM: dts: imx7ulp: Add pwm0 support Anson Huang
2019-03-14  6:27   ` Anson Huang
2019-03-14  6:27   ` Anson Huang
2019-03-14  6:27 ` [PATCH V3 5/5] ARM: dts: imx7ulp-evk: Add backlight support Anson Huang
2019-03-14  6:27   ` Anson Huang
2019-03-14  6:27   ` Anson Huang

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.