linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2] pwm: sunxi: Add Allwinner SoC PWM controller driver
@ 2021-02-03 12:53 Ban Tao
  2021-02-03 15:12 ` Uwe Kleine-König
  2021-02-03 15:46 ` Maxime Ripard
  0 siblings, 2 replies; 11+ messages in thread
From: Ban Tao @ 2021-02-03 12:53 UTC (permalink / raw)
  To: thierry.reding, u.kleine-koenig, mripard, wens
  Cc: linux-kernel, linux-pwm, Ban Tao

From: Ban Tao <fengzheng923@gmail.com>

The Allwinner R818, A133, R329, V536 and V833 has a new PWM controller
IP compared to the older Allwinner SoCs.

Signed-off-by: Ban Tao <fengzheng923@gmail.com>

---
v1->v2:
1.delete unnecessary code.
2.using a named define for some constants.
3.Add comment in sun50i_pwm_config function.
4.using dev_err_probe() for error handling.
5.disable the clock after pwmchip_remove().
---
 MAINTAINERS              |   6 +
 drivers/pwm/Kconfig      |  11 ++
 drivers/pwm/Makefile     |   1 +
 drivers/pwm/pwm-sun50i.c | 348 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 366 insertions(+)
 create mode 100644 drivers/pwm/pwm-sun50i.c

diff --git a/MAINTAINERS b/MAINTAINERS
index e73636b75f29..d33cf1b69b43 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -737,6 +737,12 @@ L:	linux-media@vger.kernel.org
 S:	Maintained
 F:	drivers/staging/media/sunxi/cedrus/
 
+ALLWINNER PWM DRIVER
+M:	Ban Tao <fengzheng923@gmail.com>
+L:	linux-pwm@vger.kernel.org
+S:	Maintained
+F:	drivers/pwm/pwm-sun50i.c
+
 ALPHA PORT
 M:	Richard Henderson <rth@twiddle.net>
 M:	Ivan Kokshaysky <ink@jurassic.park.msu.ru>
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 9a4f66ae8070..17635a8f2ed3 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -552,6 +552,17 @@ config PWM_SUN4I
 	  To compile this driver as a module, choose M here: the module
 	  will be called pwm-sun4i.
 
+config PWM_SUN50I
+	tristate "Allwinner enhanced PWM support"
+	depends on ARCH_SUNXI || COMPILE_TEST
+	depends on HAS_IOMEM && COMMON_CLK
+	help
+	  Enhanced PWM framework driver for Allwinner R818, A133, R329,
+	  V536 and V833 SoCs.
+
+	  To compile this driver as a module, choose M here: the module
+	  will be called pwm-sun50i.
+
 config PWM_TEGRA
 	tristate "NVIDIA Tegra PWM support"
 	depends on ARCH_TEGRA || COMPILE_TEST
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 6374d3b1d6f3..b4754927fd8f 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -51,6 +51,7 @@ obj-$(CONFIG_PWM_STM32)		+= pwm-stm32.o
 obj-$(CONFIG_PWM_STM32_LP)	+= pwm-stm32-lp.o
 obj-$(CONFIG_PWM_STMPE)		+= pwm-stmpe.o
 obj-$(CONFIG_PWM_SUN4I)		+= pwm-sun4i.o
+obj-$(CONFIG_PWM_SUN50I)	+= pwm-sun50i.o
 obj-$(CONFIG_PWM_TEGRA)		+= pwm-tegra.o
 obj-$(CONFIG_PWM_TIECAP)	+= pwm-tiecap.o
 obj-$(CONFIG_PWM_TIEHRPWM)	+= pwm-tiehrpwm.o
diff --git a/drivers/pwm/pwm-sun50i.c b/drivers/pwm/pwm-sun50i.c
new file mode 100644
index 000000000000..37285d771924
--- /dev/null
+++ b/drivers/pwm/pwm-sun50i.c
@@ -0,0 +1,348 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for Allwinner sun50i Pulse Width Modulation Controller
+ *
+ * Copyright (C) 2020 Ban Tao <fengzheng923@gmail.com>
+ */
+
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/pwm.h>
+#include <linux/clk.h>
+#include <linux/reset.h>
+
+#define PWM_GET_CLK_OFFSET(chan)	(0x20 + ((chan >> 1) * 0x4))
+#define PWM_CLK_APB_SCR			BIT(7)
+#define PWM_DIV_M			0
+#define PWM_DIV_M_WIDTH			0x4
+
+#define PWM_CLK_REG			0x40
+#define PWM_CLK_GATING			BIT(0)
+
+#define PWM_ENABLE_REG			0x80
+#define PWM_EN				BIT(0)
+
+#define PWM_CTL_REG(chan)		(0x100 + 0x20 * chan)
+#define PWM_ACT_STA			BIT(8)
+#define PWM_PRESCAL_K			0
+#define PWM_PRESCAL_K_WIDTH		0x8
+
+#define PWM_PERIOD_REG(chan)		(0x104 + 0x20 * chan)
+#define PWM_ENTIRE_CYCLE		16
+#define PWM_ENTIRE_CYCLE_WIDTH		0x10
+#define PWM_ACT_CYCLE			0
+#define PWM_ACT_CYCLE_WIDTH		0x10
+
+#define BIT_CH(bit, chan)		((bit) << (chan))
+
+#define SETMASK(width, shift)		((width?((-1U) >> (32-width)):0)  << (shift))
+#define CLRMASK(width, shift)		(~(SETMASK(width, shift)))
+#define GET_BITS(shift, width, reg)     \
+	    (((reg) & SETMASK(width, shift)) >> (shift))
+#define SET_BITS(shift, width, reg, val) \
+	    (((reg) & CLRMASK(width, shift)) | (val << (shift)))
+
+#define PWM_OSC_CLK			24000000
+#define PWM_PRESCALER_MAX		256
+#define PWM_CLK_DIV_M__MAX		9
+#define PWM_ENTIRE_CYCLE_MAX		65536
+
+struct sun50i_pwm_data {
+	unsigned int npwm;
+};
+
+struct sun50i_pwm_chip {
+	struct pwm_chip chip;
+	struct clk *clk;
+	struct reset_control *rst_clk;
+	void __iomem *base;
+	const struct sun50i_pwm_data *data;
+};
+
+static inline struct sun50i_pwm_chip *sun50i_pwm_from_chip(struct pwm_chip *chip)
+{
+	return container_of(chip, struct sun50i_pwm_chip, chip);
+}
+
+static inline u32 sun50i_pwm_readl(struct sun50i_pwm_chip *chip,
+				  unsigned long offset)
+{
+	return readl(chip->base + offset);
+}
+
+static inline void sun50i_pwm_writel(struct sun50i_pwm_chip *chip,
+				    u32 val, unsigned long offset)
+{
+	writel(val, chip->base + offset);
+}
+
+static int sun50i_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm,
+				enum pwm_polarity polarity)
+{
+	struct sun50i_pwm_chip *sun50i_pwm = sun50i_pwm_from_chip(chip);
+	u32 temp;
+
+	temp = sun50i_pwm_readl(sun50i_pwm, PWM_CTL_REG(pwm->hwpwm));
+
+	if (polarity == PWM_POLARITY_NORMAL)
+		temp |= PWM_ACT_STA;
+	else
+		temp &= ~PWM_ACT_STA;
+
+	sun50i_pwm_writel(sun50i_pwm, temp, PWM_CTL_REG(pwm->hwpwm));
+
+	return 0;
+}
+
+static int sun50i_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
+		int duty_ns, int period_ns)
+{
+	struct sun50i_pwm_chip *sun50i_pwm = sun50i_pwm_from_chip(chip);
+	unsigned long long c;
+	unsigned long entire_cycles, active_cycles;
+	unsigned int div_m, prescaler;
+	u32 tmp;
+
+	if (period_ns > 334) {
+		/* if freq < 3M, then select 24M clock */
+		c = PWM_OSC_CLK;
+		tmp = sun50i_pwm_readl(sun50i_pwm, PWM_GET_CLK_OFFSET(pwm->hwpwm));
+		tmp &= ~PWM_CLK_APB_SCR;
+		sun50i_pwm_writel(sun50i_pwm, tmp, PWM_GET_CLK_OFFSET(pwm->hwpwm));
+	} else {
+		/* if freq > 3M, then select APB as clock */
+		c = clk_get_rate(sun50i_pwm->clk);
+		tmp = sun50i_pwm_readl(sun50i_pwm, PWM_GET_CLK_OFFSET(pwm->hwpwm));
+		tmp |= PWM_CLK_APB_SCR;
+		sun50i_pwm_writel(sun50i_pwm, tmp, PWM_GET_CLK_OFFSET(pwm->hwpwm));
+	}
+
+	dev_dbg(chip->dev, "duty_ns=%d period_ns=%d c =%llu.\n",
+			duty_ns, period_ns, c);
+
+	/*
+	 * (clk / div_m / prescaler) / entire_cycles = NSEC_PER_SEC / period_ns.
+	 * So, entire_cycles = clk * period_ns / NSEC_PER_SEC / div_m / prescaler.
+	 */
+	c = c * period_ns;
+	do_div(c, NSEC_PER_SEC);
+	for (div_m = 0; div_m < PWM_CLK_DIV_M__MAX; div_m++) {
+		for (prescaler = 0; prescaler < PWM_PRESCALER_MAX; prescaler++) {
+			/*
+			 * actual prescaler = prescaler + 1.
+			 * actual div_m = 0x1 << div_m.
+			*/
+			entire_cycles = ((unsigned long)c/(0x1 << div_m))/(prescaler + 1);
+			if (entire_cycles <= PWM_ENTIRE_CYCLE_MAX) {
+				goto calc_end;
+			}
+		}
+	}
+
+calc_end:
+	/*
+	 * duty_ns / period_ns = active_cycles / entire_cycles.
+	 * So, active_cycles = entire_cycles * duty_ns / period_ns.
+	 */
+	c = (unsigned long long)entire_cycles * duty_ns;
+	do_div(c, period_ns);
+	active_cycles = c;
+	if (entire_cycles == 0)
+		entire_cycles++;
+
+	/* enable clk gating */
+	tmp = sun50i_pwm_readl(sun50i_pwm, PWM_CLK_REG);
+	tmp |= BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
+	sun50i_pwm_writel(sun50i_pwm, tmp, PWM_CLK_REG);
+
+	/* config  clk div_m*/
+	tmp = sun50i_pwm_readl(sun50i_pwm, PWM_GET_CLK_OFFSET(pwm->hwpwm));
+	tmp = SET_BITS(PWM_DIV_M, PWM_DIV_M_WIDTH, tmp, div_m);
+	sun50i_pwm_writel(sun50i_pwm, tmp, PWM_GET_CLK_OFFSET(pwm->hwpwm));
+
+	/* config prescal */
+	tmp = sun50i_pwm_readl(sun50i_pwm, PWM_CTL_REG(pwm->hwpwm));
+	tmp = SET_BITS(PWM_PRESCAL_K, PWM_PRESCAL_K_WIDTH, tmp, prescaler);
+	sun50i_pwm_writel(sun50i_pwm, tmp, PWM_CTL_REG(pwm->hwpwm));
+
+	/* config active and period cycles */
+	tmp = sun50i_pwm_readl(sun50i_pwm, PWM_PERIOD_REG(pwm->hwpwm));
+	tmp = SET_BITS(PWM_ACT_CYCLE, PWM_ACT_CYCLE_WIDTH, tmp, active_cycles);
+	tmp = SET_BITS(PWM_ENTIRE_CYCLE, PWM_ENTIRE_CYCLE_WIDTH, tmp, (entire_cycles - 1));
+	sun50i_pwm_writel(sun50i_pwm, tmp, PWM_PERIOD_REG(pwm->hwpwm));
+
+	dev_dbg(chip->dev, "active_cycles=%lu entire_cycles=%lu prescaler=%u div_m=%u\n",
+			active_cycles, entire_cycles, prescaler, div_m);
+
+	return 0;
+}
+
+static int sun50i_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct sun50i_pwm_chip *sun50i_pwm = sun50i_pwm_from_chip(chip);
+	u32 tmp;
+
+	/* enable pwm controller */
+	tmp = sun50i_pwm_readl(sun50i_pwm, PWM_ENABLE_REG);
+	tmp |= BIT_CH(PWM_EN, pwm->hwpwm);
+	sun50i_pwm_writel(sun50i_pwm, tmp, PWM_ENABLE_REG);
+
+	tmp = sun50i_pwm_readl(sun50i_pwm, PWM_CLK_REG);
+	tmp |= BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
+	sun50i_pwm_writel(sun50i_pwm, tmp, PWM_CLK_REG);
+
+	return 0;
+}
+
+static void sun50i_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+	struct sun50i_pwm_chip *sun50i_pwm = sun50i_pwm_from_chip(chip);
+	u32 tmp;
+
+	/* disable pwm controller */
+	tmp = sun50i_pwm_readl(sun50i_pwm, PWM_ENABLE_REG);
+	tmp &= ~BIT_CH(PWM_EN, pwm->hwpwm);
+	sun50i_pwm_writel(sun50i_pwm, tmp, PWM_ENABLE_REG);
+
+	tmp = sun50i_pwm_readl(sun50i_pwm, PWM_CLK_REG);
+	tmp &= ~BIT_CH(PWM_CLK_GATING, pwm->hwpwm);
+	sun50i_pwm_writel(sun50i_pwm, tmp, PWM_CLK_REG);
+}
+
+static const struct pwm_ops sun50i_pwm_ops = {
+	.config = sun50i_pwm_config,
+	.enable = sun50i_pwm_enable,
+	.disable = sun50i_pwm_disable,
+	.set_polarity = sun50i_pwm_set_polarity,
+	.owner = THIS_MODULE,
+};
+
+static const struct sun50i_pwm_data sun8i_pwm_data_c9 = {
+	.npwm = 9,
+};
+
+static const struct sun50i_pwm_data sun50i_pwm_data_c16 = {
+	.npwm = 16,
+};
+
+static const struct of_device_id sun50i_pwm_dt_ids[] = {
+	{
+		.compatible = "allwinner,sun8i-v833-pwm",
+		.data = &sun8i_pwm_data_c9,
+	}, {
+		.compatible = "allwinner,sun8i-v536-pwm",
+		.data = &sun8i_pwm_data_c9,
+	}, {
+		.compatible = "allwinner,sun50i-r818-pwm",
+		.data = &sun50i_pwm_data_c16,
+	}, {
+		.compatible = "allwinner,sun50i-a133-pwm",
+		.data = &sun50i_pwm_data_c16,
+	}, {
+		.compatible = "allwinner,sun50i-r329-pwm",
+		.data = &sun8i_pwm_data_c9,
+	}, {
+		/* sentinel */
+	},
+};
+MODULE_DEVICE_TABLE(of, sun50i_pwm_dt_ids);
+
+static int sun50i_pwm_probe(struct platform_device *pdev)
+{
+	struct sun50i_pwm_chip *pwm;
+	int ret;
+
+	pwm = devm_kzalloc(&pdev->dev, sizeof(*pwm), GFP_KERNEL);
+	if (!pwm)
+		return -ENOMEM;
+
+	pwm->data = of_device_get_match_data(&pdev->dev);
+	if (!pwm->data)
+		return -ENODEV;
+
+	pwm->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(pwm->base))
+		return dev_err_probe(&pdev->dev, PTR_ERR(pwm->base),
+				     "can't remap pwm resource\n");
+
+	pwm->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(pwm->clk))
+		return dev_err_probe(&pdev->dev, PTR_ERR(pwm->clk),
+				     "get unnamed clock failed\n");
+
+	pwm->rst_clk = devm_reset_control_get_exclusive(&pdev->dev, NULL);
+	if (IS_ERR(pwm->rst_clk))
+		return dev_err_probe(&pdev->dev, PTR_ERR(pwm->rst_clk),
+				     "get reset failed\n");
+
+	/* Deassert reset */
+	ret = reset_control_deassert(pwm->rst_clk);
+	if (ret) {
+		dev_err(&pdev->dev, "cannot deassert reset control: %pe\n",
+			ERR_PTR(ret));
+		return ret;
+	}
+
+	ret = clk_prepare_enable(pwm->clk);
+	if (ret) {
+		dev_err(&pdev->dev, "cannot prepare and enable clk %pe\n",
+			ERR_PTR(ret));
+		goto err_clk;
+	}
+
+	pwm->chip.dev = &pdev->dev;
+	pwm->chip.ops = &sun50i_pwm_ops;
+	pwm->chip.npwm = pwm->data->npwm;
+	pwm->chip.of_xlate = of_pwm_xlate_with_flags;
+	pwm->chip.base = -1;
+	pwm->chip.of_pwm_n_cells = 3;
+
+	ret = pwmchip_add(&pwm->chip);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret);
+		goto err_pwm_add;
+	}
+
+	platform_set_drvdata(pdev, pwm);
+
+	return 0;
+
+err_pwm_add:
+	clk_disable_unprepare(pwm->clk);
+err_clk:
+	reset_control_assert(pwm->rst_clk);
+
+	return ret;
+}
+
+static int sun50i_pwm_remove(struct platform_device *pdev)
+{
+	struct sun50i_pwm_chip *pwm = platform_get_drvdata(pdev);
+	int ret;
+
+	ret = pwmchip_remove(&pwm->chip);
+	if (ret)
+		return ret;
+
+	clk_disable_unprepare(pwm->clk);
+	reset_control_assert(pwm->rst_clk);
+
+	return 0;
+}
+
+static struct platform_driver sun50i_pwm_driver = {
+	.driver = {
+		.name = "sun50i-pwm",
+		.of_match_table = sun50i_pwm_dt_ids,
+	},
+	.probe = sun50i_pwm_probe,
+	.remove = sun50i_pwm_remove,
+};
+module_platform_driver(sun50i_pwm_driver);
+
+MODULE_ALIAS("platform:sun50i-pwm");
+MODULE_AUTHOR("Ban Tao <fengzheng923@gmail.com>");
+MODULE_DESCRIPTION("Allwinner sun50i PWM driver");
+MODULE_LICENSE("GPL v2");
-- 
2.22.0.windows.1


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

end of thread, other threads:[~2021-02-22  7:23 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-02-03 12:53 [PATCH v2] pwm: sunxi: Add Allwinner SoC PWM controller driver Ban Tao
2021-02-03 15:12 ` Uwe Kleine-König
2021-02-03 16:33   ` Thierry Reding
2021-02-03 20:59     ` Uwe Kleine-König
2021-02-04 13:38       ` Thierry Reding
2021-02-04 13:54         ` Uwe Kleine-König
2021-02-04 22:05           ` Thierry Reding
     [not found]   ` <CAE=m618FY6_Qq1gNmkivswKjCB984WfV_Cr_Cw253ffGQmAS5Q@mail.gmail.com>
2021-02-22  7:22     ` Uwe Kleine-König
2021-02-03 15:46 ` Maxime Ripard
     [not found]   ` <CAE=m61-oXn8CkzUpSxkuS-gLkxjwd8wSeL42Q5T+27_V89xgNw@mail.gmail.com>
2021-02-05 16:11     ` Maxime Ripard
     [not found]       ` <CAE=m6190zD55EL_VOmq=yKw471bxiRwZdjpVTyvNAAvofn9UPQ@mail.gmail.com>
2021-02-10  9:33         ` Maxime Ripard

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).