linux-tegra.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
From: Penny Chiu <pchiu@nvidia.com>
To: swarren@wwwdotorg.org, thierry.reding@gmail.com,
	gnurou@gmail.com, pdeschrijver@nvidia.com, pgaikwad@nvidia.com,
	rjw@rjwysocki.net, viresh.kumar@linaro.org
Cc: mturquette@baylibre.com, sboyd@codeaurora.org,
	linux-tegra@vger.kernel.org, linux-clk@vger.kernel.org,
	linux-pwm@vger.kernel.org, linux-pm@vger.kernel.org,
	linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org, Penny Chiu <pchiu@nvidia.com>
Subject: [PATCH 05/11] pwm: tegra-dfll: Add driver for Tegra DFLL PWM controller
Date: Fri, 22 Apr 2016 18:31:05 +0800	[thread overview]
Message-ID: <1461321071-6431-6-git-send-email-pchiu@nvidia.com> (raw)
In-Reply-To: <1461321071-6431-1-git-send-email-pchiu@nvidia.com>

Tegra DFLL IP block controls off-chip PMIC via I2C bus or PWM
signals. This driver exposes DFLL as a PWM controller to generate
PWM signals to PWM regulator.

Tegra DFLL HW changes regulator voltage by adjusting PWM signals
duty cycle automatically based on required DVCO frequency, so PWM
regulator client doesn't need to change voltage by SW.

In this driver, it only configs proper PWM rate in the driver
initialization, and provides two APIs for clients to determine when
to enable and disable generating PWM signals by setting related
pinmux tristate.

Signed-off-by: Penny Chiu <pchiu@nvidia.com>
---
 .../bindings/pwm/nvidia,tegra-dfll-pwm.txt         |  48 +++
 drivers/pwm/Kconfig                                |  10 +
 drivers/pwm/Makefile                               |   1 +
 drivers/pwm/pwm-tegra-dfll.c                       | 322 +++++++++++++++++++++
 include/soc/tegra/pwm-tegra-dfll.h                 |  27 ++
 5 files changed, 408 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/pwm/nvidia,tegra-dfll-pwm.txt
 create mode 100644 drivers/pwm/pwm-tegra-dfll.c
 create mode 100644 include/soc/tegra/pwm-tegra-dfll.h

diff --git a/Documentation/devicetree/bindings/pwm/nvidia,tegra-dfll-pwm.txt b/Documentation/devicetree/bindings/pwm/nvidia,tegra-dfll-pwm.txt
new file mode 100644
index 0000000..bd0d247
--- /dev/null
+++ b/Documentation/devicetree/bindings/pwm/nvidia,tegra-dfll-pwm.txt
@@ -0,0 +1,48 @@
+Tegra SoC DFLL PWM controller
+
+Required properties:
+- compatible: For Tegra210, must contain "nvidia,tegra210-dfll-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.
+- clock-names: Must include the "ref" entry.
+- clocks: Must contain one entry, for the DFLL closed loop reference clock.
+  See ../clocks/clock-bindings.txt for details.
+- pwm-regulator: phandle to PWM regulator for using this PWM controller.
+- pinctrl-names: Must contain two entries to enable and disable PWM signals
+  output pinmux states.
+- pinctrl-0: pinmux state to enable PWM signals output
+- pinctrl-1: pinmux state to disable PWM signals output
+
+Example:
+
+	pinmux: pinmux@700008d4 {
+		dvfs_pwm_active_state: dvfs_pwm_active {
+			dvfs_pwm_pbb1 {
+				nvidia,pins = "dvfs_pwm_pbb1";
+				nvidia,tristate = <TEGRA_PIN_DISABLE>;
+			};
+		};
+
+		dvfs_pwm_inactive_state: dvfs_pwm_inactive {
+			dvfs_pwm_pbb1 {
+				nvidia,pins = "dvfs_pwm_pbb1";
+				nvidia,tristate = <TEGRA_PIN_ENABLE>;
+			};
+		};
+	};
+
+	pwm_dfll: pwm@70110000 {
+		compatible = "nvidia,tegra210-dfll-pwm";
+		reg = <0x0 0x70110000 0x0 0x400>;
+		clocks = <&tegra_car TEGRA210_CLK_DFLL_REF>;
+		clock-names = "ref";
+		#pwm-cells = <2>;
+		pwm-regulator = <&cpu_ovr_reg>;
+
+		pinctrl-names = "dvfs_pwm_enable", "dvfs_pwm_disable";
+		pinctrl-0 = <&dvfs_pwm_active_state>;
+		pinctrl-1 = <&dvfs_pwm_inactive_state>;
+
+		status = "okay";
+	};
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index c182efc..dd67cad 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -382,6 +382,16 @@ config PWM_TEGRA
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-tegra.
 
+config PWM_TEGRA_DFLL
+	tristate "NVIDIA Tegra DFLL PWM support"
+	depends on ARCH_TEGRA
+	help
+	  PWM driver support for the Tegra DFLL module found on NVIDIA
+	  Tegra SoCs.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called pwm-tegra-dfll.
+
 config  PWM_TIECAP
 	tristate "ECAP PWM support"
 	depends on ARCH_OMAP2PLUS || ARCH_DAVINCI_DA8XX
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index dd35bc1..22414cb 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_PWM_SPEAR)		+= pwm-spear.o
 obj-$(CONFIG_PWM_STI)		+= pwm-sti.o
 obj-$(CONFIG_PWM_SUN4I)		+= pwm-sun4i.o
 obj-$(CONFIG_PWM_TEGRA)		+= pwm-tegra.o
+obj-$(CONFIG_PWM_TEGRA_DFLL)	+= pwm-tegra-dfll.o
 obj-$(CONFIG_PWM_TIECAP)	+= pwm-tiecap.o
 obj-$(CONFIG_PWM_TIEHRPWM)	+= pwm-tiehrpwm.o
 obj-$(CONFIG_PWM_TIPWMSS)	+= pwm-tipwmss.o
diff --git a/drivers/pwm/pwm-tegra-dfll.c b/drivers/pwm/pwm-tegra-dfll.c
new file mode 100644
index 0000000..74d6b97
--- /dev/null
+++ b/drivers/pwm/pwm-tegra-dfll.c
@@ -0,0 +1,322 @@
+/*
+ * drivers/pwm/pwm-tegra-dfll.c
+ *
+ * Tegra DFLL PWM controller driver
+ *
+ * Copyright (c) 2016, NVIDIA Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ */
+
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pwm.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/pinctrl/consumer.h>
+#include <soc/tegra/pwm-tegra-dfll.h>
+
+/* DFLL_CTRL: DFLL control register */
+#define DFLL_CTRL			0x00
+
+/* DFLL_OUTPUT_CFG: closed loop mode control registers */
+#define DFLL_OUTPUT_CFG			0x20
+#define OUT_MASK			0x3f
+#define DFLL_OUTPUT_CFG_PWM_DELTA	(0x1 << 7)
+#define DFLL_OUTPUT_CFG_PWM_ENABLE	(0x1 << 6)
+#define DFLL_OUTPUT_CFG_PWM_DIV_SHIFT	0
+#define DFLL_OUTPUT_CFG_PWM_DIV_MASK	\
+		(OUT_MASK << DFLL_OUTPUT_CFG_PWM_DIV_SHIFT)
+
+/* MAX_DFLL_VOLTAGES: number of LUT entries in the DFLL IP block */
+#define DFLL_MAX_VOLTAGES		33
+
+#define DFLL_OF_PWM_PERIOD_CELL		1
+
+/**
+ * struct tegra_dfll_pwm_chip - DFLL PWM controller data
+ * @pwm_pin: pinmux for PWM signals output
+ * @pwm_enable_state: enabled states of pinmux for PWM signals output
+ * @pwm_disable_state: disabled states of pinmux for PWM signals output
+ * @mmio_base: mmio base for access DFLL registers
+ * @ref_clk: referenced source clock
+ * @pwm_rate: PWM rate for DFLL PWM output config register
+ */
+struct tegra_dfll_pwm_chip {
+	struct pwm_chip		chip;
+	struct device		*dev;
+
+	struct pinctrl		*pwm_pin;
+	struct pinctrl_state	*pwm_enable_state;
+	struct pinctrl_state	*pwm_disable_state;
+
+	void __iomem		*mmio_base;
+	struct clk		*ref_clk;
+
+	unsigned long		ref_rate;
+	unsigned long		pwm_rate;
+};
+
+static struct tegra_dfll_pwm_chip *tdpc;
+
+/*
+ * Register accessors
+ */
+static inline u32 pwm_readl(u32 offs)
+{
+	return __raw_readl(tdpc->mmio_base + offs);
+}
+
+static inline void pwm_writel(u32 val, u32 offs)
+{
+	__raw_writel(val, tdpc->mmio_base + offs);
+	pwm_readl(DFLL_CTRL);
+}
+
+static int tegra_dfll_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+				int duty_ns, int period_ns)
+{
+	return 0;
+}
+
+static int tegra_dfll_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	u32 val;
+
+	val = pwm_readl(DFLL_OUTPUT_CFG);
+	val |= DFLL_OUTPUT_CFG_PWM_ENABLE;
+	pwm_writel(val, DFLL_OUTPUT_CFG);
+
+	dev_info(tdpc->dev, "DFLL_PWM is enabled\n");
+
+	return 0;
+}
+
+static void tegra_dfll_pwm_disable(struct pwm_chip *chip,
+				   struct pwm_device *pwm)
+{
+	u32 val;
+
+	val = pwm_readl(DFLL_OUTPUT_CFG);
+	val &= ~DFLL_OUTPUT_CFG_PWM_ENABLE;
+	pwm_writel(val, DFLL_OUTPUT_CFG);
+
+	dev_info(tdpc->dev, "DFLL_PWM is disabled\n");
+
+	return 0;
+}
+
+/**
+ * tegra_dfll_pwm_output_enable - enable DFLL PWM signals output
+ *
+ * Enable DFLL PWM signals output by changing related pinmux state
+ */
+int tegra_dfll_pwm_output_enable(void)
+{
+	int ret;
+
+	ret = pinctrl_select_state(tdpc->pwm_pin, tdpc->pwm_enable_state);
+	if (ret < 0) {
+		dev_err(tdpc->dev, "setting enable state failed\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(tegra_dfll_pwm_output_enable);
+
+/**
+ * tegra_dfll_pwm_output_disable - disable DFLL PWM signals output
+ *
+ * Disable DFLL PWM signals output by changing related pinmux state
+ */
+int tegra_dfll_pwm_output_disable(void)
+{
+	int ret;
+
+	ret = pinctrl_select_state(tdpc->pwm_pin, tdpc->pwm_disable_state);
+	if (ret < 0) {
+		dev_err(tdpc->dev, "setting enable state failed\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(tegra_dfll_pwm_output_disable);
+
+static const struct pwm_ops tegra_dfll_pwm_ops = {
+	.config = tegra_dfll_pwm_config,
+	.enable = tegra_dfll_pwm_enable,
+	.disable = tegra_dfll_pwm_disable,
+	.owner = THIS_MODULE,
+};
+
+/**
+ * dt_parse_pwm_regulator - parse PWM regulator from device-tree
+ *
+ * Parse DFLL PWM controller client to get and calcluate initialized
+ * DFLL PWM rate.
+ */
+static int dt_parse_pwm_regulator(void)
+{
+	struct device_node *r_dn =
+		of_parse_phandle(tdpc->dev->of_node, "pwm-regulator", 0);
+	struct of_phandle_args args;
+	unsigned long val;
+	int ret;
+
+	/* pwm regulator device */
+	if (!r_dn) {
+		dev_err(tdpc->dev, "DT: missing pwm-regulator property\n");
+		return -EINVAL;
+	}
+
+	ret = of_parse_phandle_with_args(r_dn, "pwms", "#pwm-cells", 0, &args);
+	if (ret) {
+		dev_err(tdpc->dev, "DT: failed to parse pwms property\n");
+		return -EINVAL;
+	}
+	of_node_put(args.np);
+
+	if (args.args_count <= DFLL_OF_PWM_PERIOD_CELL) {
+		dev_err(tdpc->dev, "DT: low #pwm-cells %d\n", args.args_count);
+		return -EINVAL;
+	}
+
+	/* convert pwm period in ns to DFLL pwm rate in Hz */
+	val = args.args[DFLL_OF_PWM_PERIOD_CELL];
+	val = (NSEC_PER_SEC / val) * (DFLL_MAX_VOLTAGES - 1);
+	tdpc->pwm_rate = val;
+	dev_info(tdpc->dev, "DFLL pwm-rate: %lu\n", val);
+
+	return 0;
+}
+
+/**
+ * tegra_dfll_pwm_init - init Tegra DFLL PWM controller
+ *
+ * Calculate the DIV value and write into DFLL register
+ */
+static int tegra_dfll_pwm_init(void)
+{
+	u32 div, val;
+
+	pwm_writel(0, DFLL_OUTPUT_CFG);
+
+	div = DIV_ROUND_UP(tdpc->ref_rate, tdpc->pwm_rate);
+	val = (div << DFLL_OUTPUT_CFG_PWM_DIV_SHIFT) &
+	      DFLL_OUTPUT_CFG_PWM_DIV_MASK;
+	pwm_writel(val, DFLL_OUTPUT_CFG);
+
+	return 0;
+}
+
+static int tegra_dfll_pwm_probe(struct platform_device *pdev)
+{
+	struct resource *res;
+	int ret;
+
+	tdpc = devm_kzalloc(&pdev->dev, sizeof(*tdpc), GFP_KERNEL);
+	if (!tdpc)
+		return -ENOMEM;
+
+	tdpc->dev = &pdev->dev;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	tdpc->mmio_base = devm_ioremap_resource(tdpc->dev, res);
+	if (IS_ERR(tdpc->mmio_base))
+		return PTR_ERR(tdpc->mmio_base);
+
+	platform_set_drvdata(pdev, tdpc);
+
+	tdpc->chip.dev = tdpc->dev;
+	tdpc->chip.ops = &tegra_dfll_pwm_ops;
+	tdpc->chip.base = -1;
+	tdpc->chip.npwm = 1;
+
+	ret = pwmchip_add(&tdpc->chip);
+	if (ret < 0) {
+		dev_err(tdpc->dev, "pwmchip_add() failed: %d\n", ret);
+		return ret;
+	}
+
+	tdpc->ref_clk = devm_clk_get(tdpc->dev, "ref");
+	if (IS_ERR(tdpc->ref_clk)) {
+		dev_err(tdpc->dev, "DT: missing ref clock\n");
+		return PTR_ERR(tdpc->ref_clk);
+	}
+
+	tdpc->ref_rate = clk_get_rate(tdpc->ref_clk);
+
+	tdpc->pwm_pin = devm_pinctrl_get(tdpc->dev);
+	if (IS_ERR(tdpc->pwm_pin)) {
+		dev_err(tdpc->dev, "DT: missing pinctrl device\n");
+		return PTR_ERR(tdpc->pwm_pin);
+	}
+
+	tdpc->pwm_enable_state = pinctrl_lookup_state(tdpc->pwm_pin,
+						"dvfs_pwm_enable");
+	if (IS_ERR(tdpc->pwm_enable_state)) {
+		dev_err(tdpc->dev, "DT: missing pwm enabled state\n");
+		return PTR_ERR(tdpc->pwm_enable_state);
+	}
+
+	tdpc->pwm_disable_state = pinctrl_lookup_state(tdpc->pwm_pin,
+						"dvfs_pwm_disable");
+	if (IS_ERR(tdpc->pwm_disable_state)) {
+		dev_err(tdpc->dev, "DT: missing pwm disabled state\n");
+		return PTR_ERR(tdpc->pwm_disable_state);
+	}
+
+	ret = dt_parse_pwm_regulator();
+	if (ret < 0) {
+		dev_err(tdpc->dev, "failed to parse pwm regulator\n");
+		return ret;
+	}
+
+	ret = tegra_dfll_pwm_init();
+	if (ret < 0) {
+		dev_err(tdpc->dev, "failed to init DFLL pwm\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int tegra_dfll_pwm_remove(struct platform_device *pdev)
+{
+	return pwmchip_remove(&tdpc->chip);
+}
+
+static const struct of_device_id tegra_dfll_pwm_of_match[] = {
+	{ .compatible = "nvidia,tegra210-dfll-pwm" },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(of, tegra_dfll_pwm_of_match);
+
+static struct platform_driver tegra_dfll_pwm_driver = {
+	.driver = {
+		.name = "tegra-dfll-pwm",
+		.of_match_table = tegra_dfll_pwm_of_match,
+	},
+	.probe = tegra_dfll_pwm_probe,
+	.remove = tegra_dfll_pwm_remove,
+};
+
+module_platform_driver(tegra_dfll_pwm_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("NVIDIA Corporation");
+MODULE_ALIAS("platform:tegra-dfll-pwm");
diff --git a/include/soc/tegra/pwm-tegra-dfll.h b/include/soc/tegra/pwm-tegra-dfll.h
new file mode 100644
index 0000000..d34c6e8
--- /dev/null
+++ b/include/soc/tegra/pwm-tegra-dfll.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 NVIDIA Corporation
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __SOC_TEGRA_PWM_TEGRA_DFLL_H__
+#define __SOC_TEGRA_PWM_TEGRA_DFLL_H__
+
+#ifdef CONFIG_PWM_TEGRA_DFLL
+int tegra_dfll_pwm_output_enable(void);
+int tegra_dfll_pwm_output_disable(void);
+#else
+static inline int tegra_dfll_pwm_output_enable(void)
+{
+	return -ENOTSUPP;
+}
+
+static inline int tegra_dfll_pwm_output_disable(void)
+{
+	return -ENOTSUPP;
+}
+#endif
+
+#endif /* __SOC_TEGRA_PWM_TEGRA_DFLL_H__ */
-- 
2.8.1

  parent reply	other threads:[~2016-04-22 10:31 UTC|newest]

Thread overview: 23+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2016-04-22 10:31 [PATCH 00/11] arm64: tegra: Add Tegra DFLL for Tegra210 Jetson TX1 Penny Chiu
2016-04-22 10:31 ` [PATCH 01/11] clk: tegra: dfll: Fix voltage comparison Penny Chiu
2016-04-22 10:31 ` [PATCH 02/11] clk: tegra: dfll: Move SoC specific data into of_device_id Penny Chiu
2016-04-22 13:04   ` Thierry Reding
2016-04-22 10:31 ` [PATCH 03/11] clk: tegra: Add DFLL DVCO reset control for Tegra210 Penny Chiu
2016-04-22 13:11   ` Thierry Reding
2016-04-22 10:31 ` [PATCH 04/11] clk: tegra: Add Tegra210 support in DFLL driver Penny Chiu
2016-04-22 13:16   ` Thierry Reding
2016-04-22 10:31 ` Penny Chiu [this message]
2016-04-22 12:55   ` [PATCH 05/11] pwm: tegra-dfll: Add driver for Tegra DFLL PWM controller Thierry Reding
2016-05-06 23:15     ` Stephen Boyd
2016-05-06 23:21       ` Stephen Warren
2016-04-22 10:31 ` [PATCH 06/11] clk: tegra: dfll: Add PWM inferface Penny Chiu
2016-04-22 10:31 ` [PATCH 07/11] cpufreq: tegra124: Add Tegra210 support Penny Chiu
     [not found]   ` <1461321071-6431-8-git-send-email-pchiu-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
2016-04-22 11:00     ` Viresh Kumar
2016-04-22 10:31 ` [PATCH 08/11] arm64: tegra: Add PWM regulator for CPU rail on Jetson TX1 Penny Chiu
2016-04-22 10:31 ` [PATCH 09/11] arm64: tegra: Add DFLL clock node " Penny Chiu
     [not found]   ` <1461321071-6431-10-git-send-email-pchiu-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
2016-04-22 13:28     ` Thierry Reding
2016-04-22 10:31 ` [PATCH 10/11] arm64: tegra: Add clock properties on cpu0 for Tegra210 Penny Chiu
2016-04-22 11:44   ` Jon Hunter
2016-04-22 13:23     ` Thierry Reding
2016-04-22 13:36       ` Jon Hunter
     [not found] ` <1461321071-6431-1-git-send-email-pchiu-DDmLM1+adcrQT0dZR+AlfA@public.gmane.org>
2016-04-22 10:31   ` [PATCH 11/11] arm64: config: Enable CPUFreq-DT, Tegra DFLL PWM, and PWM regulator Penny Chiu

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1461321071-6431-6-git-send-email-pchiu@nvidia.com \
    --to=pchiu@nvidia.com \
    --cc=gnurou@gmail.com \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-clk@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-pm@vger.kernel.org \
    --cc=linux-pwm@vger.kernel.org \
    --cc=linux-tegra@vger.kernel.org \
    --cc=mturquette@baylibre.com \
    --cc=pdeschrijver@nvidia.com \
    --cc=pgaikwad@nvidia.com \
    --cc=rjw@rjwysocki.net \
    --cc=sboyd@codeaurora.org \
    --cc=swarren@wwwdotorg.org \
    --cc=thierry.reding@gmail.com \
    --cc=viresh.kumar@linaro.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).