pwm: s4: support for pwm driver [1/1]
PD#SWPL-109001
Problem:
U-Boot 2023 bringup on s4.
Solution:
U-Boot 2023 bringup on s4.
Verify:
S4 AP229
Change-Id: Iedd2f8a02f9298c431bbd2005c6f827b3154a73c
Signed-off-by: junyi.zhao <junyi.zhao@amlogic.com>
diff --git a/drivers/amlogic/pwm/Makefile b/drivers/amlogic/pwm/Makefile
new file mode 100644
index 0000000..d467ee2
--- /dev/null
+++ b/drivers/amlogic/pwm/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0+
+
+obj-$(CONFIG_PWM_MESON) += pwm-meson.o
diff --git a/drivers/amlogic/pwm/pwm-meson.c b/drivers/amlogic/pwm/pwm-meson.c
new file mode 100644
index 0000000..93c1a2c
--- /dev/null
+++ b/drivers/amlogic/pwm/pwm-meson.c
@@ -0,0 +1,578 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
+/*
+ * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <pwm.h>
+#include <regmap.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/sizes.h>
+#include <linux/printk.h>
+#include <div64.h>
+
+#include "pwm-meson.h"
+
+struct meson_pwm_priv {
+ struct meson_pwm_reg *regs;
+ struct meson_pwm_state *pwm_state;
+ struct meson_pwm_data *pwm_data;
+ u32 extern_clk_addr;
+};
+
+static u64 meson_pwm_clock_get_rate(void)
+{
+ return 24000000;
+}
+
+static int pwm_meson_get_polarity(struct udevice *dev, uint channel)
+{
+ struct meson_pwm_priv *priv = dev_get_priv(dev);
+#ifdef PWM_REG_POLAR
+ struct meson_pwm_reg *regs = priv->regs;
+
+ unsigned int tmp, val;
+
+ switch (channel) {
+ case MESON_PWM0:
+ case MESON_PWM2:
+ val = 0x1 << 26;
+ break;
+
+ case MESON_PWM1:
+ case MESON_PWM3:
+ val = 0x1 << 27;
+ break;
+
+ default:
+ pr_err("Id is invalid\n");
+ return 0;
+ }
+
+ tmp = readl(®s->miscr);
+ tmp = tmp & val;
+ if (tmp == 0)
+ return 0;
+ else
+ return 1;
+#else
+ struct meson_pwm_state *pwm_state = priv->pwm_state;
+
+ return pwm_state[channel].polarity;
+#endif
+}
+
+static void pwm_constant_enable(struct udevice *dev, uint channel)
+{
+ struct meson_pwm_priv *priv = dev_get_priv(dev);
+ struct meson_pwm_reg *regs = priv->regs;
+
+ switch (channel) {
+ case MESON_PWM0:
+ case MESON_PWM2:
+ setbits_le32(®s->miscr, 1 << 28);
+ break;
+
+ case MESON_PWM1:
+ case MESON_PWM3:
+ setbits_le32(®s->miscr, 1 << 29);
+ break;
+
+ default:
+ pr_err("Id is invalid\n");
+ break;
+ }
+}
+
+static void pwm_constant_disable(struct udevice *dev, uint channel)
+{
+ struct meson_pwm_priv *priv = dev_get_priv(dev);
+ struct meson_pwm_reg *regs = priv->regs;
+
+ switch (channel) {
+ case MESON_PWM0:
+ case MESON_PWM2:
+ clrbits_le32(®s->miscr, 1 << 28);
+ break;
+
+ case MESON_PWM1:
+ case MESON_PWM3:
+ clrbits_le32(®s->miscr, 1 << 29);
+ break;
+
+ default:
+ pr_err("Id is invalid\n");
+ break;
+ }
+}
+
+static void pwm_meson_config(struct udevice *dev, unsigned int channel)
+{
+ struct meson_pwm_priv *priv = dev_get_priv(dev);
+ struct meson_pwm_reg *regs = priv->regs;
+ struct meson_pwm_state *pwm_state = priv->pwm_state;
+ fdt_addr_t clk_addr = priv->extern_clk_addr;
+
+ switch (channel) {
+ case MESON_PWM0:
+ /*set div and clock enable*/
+ if (priv->pwm_data->extern_clk)
+ clrsetbits_le32(clk_addr,
+ (0xff << 0) | (3 << 9), (pwm_state[channel].pre_div << 0 | 1 << 8));
+ else
+ setbits_le32(®s->miscr,
+ (pwm_state[channel].pre_div << 8 | 1 << 15));
+ /*set duty*/
+ writel((pwm_state[channel].hi << 16 | pwm_state[channel].lo), ®s->dar);
+ break;
+ case MESON_PWM1:
+ /*set div and clock enable*/
+ if (priv->pwm_data->extern_clk)
+ clrsetbits_le32(clk_addr, (0xff << 16) | (3 << 25),
+ (pwm_state[channel].pre_div << 16 | 1 << 24));
+ else
+ setbits_le32(®s->miscr,
+ (pwm_state[channel].pre_div << 16 | 1 << 23));
+ /*set duty*/
+ writel((pwm_state[channel].hi << 16 | pwm_state[channel].lo), ®s->dbr);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void pwm_meson_config_ext(struct udevice *dev, unsigned int channel)
+{
+ struct meson_pwm_priv *priv = dev_get_priv(dev);
+ struct meson_pwm_reg *regs = priv->regs;
+ struct meson_pwm_state *pwm_state = priv->pwm_state;
+
+ switch (channel) {
+ case MESON_PWM2:
+ /*set div and clock enable*/
+ /*setbits_le32(®s->miscr, (pwm_state[channel].pre_div << 8 | 1 << 15));*/
+ /*set duty*/
+ writel((pwm_state[channel].hi << 16 | pwm_state[channel].lo), ®s->da2r);
+ break;
+
+ case MESON_PWM3:
+ /*set div and clock enable*/
+ /*setbits_le32(®s->miscr,
+ *(pwm_state[channel].pre_div << 16 | 1 << 23));
+ */
+ /*set duty*/
+ writel((pwm_state[channel].hi << 16 | pwm_state[channel].lo), ®s->db2r);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static int meson_pwm_cacl(struct udevice *dev, uint channel, uint period,
+ uint duty)
+{
+ struct meson_pwm_priv *priv = dev_get_priv(dev);
+ struct meson_pwm_state *pwm_state = priv->pwm_state;
+ unsigned int pre_div, cnt, duty_cnt, inv;
+ unsigned long fin_freq = -1;
+ u64 fin_ps;
+
+ inv = pwm_meson_get_polarity(dev, channel);
+ if (inv)
+ duty = period - duty;
+
+ fin_freq = meson_pwm_clock_get_rate();
+ fin_ps = (u64)NSEC_PER_SEC * 1000;
+ do_div(fin_ps, fin_freq);
+
+ for (pre_div = 0; pre_div < 0x7f; pre_div++) {
+ cnt = DIV_ROUND_CLOSEST_ULL((u64)period * 1000,
+ fin_ps * (pre_div + 1));
+ if (cnt <= 0xffff)
+ break;
+ }
+
+ if (pre_div >= 0x7f) {
+ pr_err("unable to get period pre_div\n");
+ return -EINVAL;
+ }
+
+ if (duty == period) {
+ pwm_state[channel].pre_div = pre_div;
+ pwm_state[channel].hi = cnt;
+ pwm_state[channel].lo = 0;
+ } else if (duty == 0) {
+ pwm_state[channel].pre_div = pre_div;
+ pwm_state[channel].hi = 0;
+ pwm_state[channel].lo = cnt;
+ } else {
+ /* Then check is we can have the duty with the same pre_div */
+ duty_cnt = DIV_ROUND_CLOSEST_ULL((u64)duty * 1000,
+ fin_ps * (pre_div + 1));
+ if (duty_cnt > 0xffff) {
+ pr_err("unable to get duty cycle\n");
+ return -EINVAL;
+ }
+
+ if (duty_cnt == 0)
+ duty_cnt = 1;
+
+ if (cnt == duty_cnt)
+ duty_cnt -= 1;
+
+ pwm_state[channel].pre_div = pre_div;
+ pwm_state[channel].hi = duty_cnt - 1;
+ pwm_state[channel].lo = cnt - duty_cnt - 1;
+ }
+
+ if (duty == period || duty == 0)
+ pwm_constant_enable(dev, channel);
+ else
+ pwm_constant_disable(dev, channel);
+
+ return 0;
+}
+
+static int meson_pwm_set_config(struct udevice *dev, uint channel, uint period_ns,
+ uint duty_ns)
+{
+ struct meson_pwm_priv *priv = dev_get_priv(dev);
+ struct meson_pwm_state *pwm_state = priv->pwm_state;
+
+ if (duty_ns < 0 || period_ns <= 0) {
+ pr_err("Not available duty_ns period_ns error\n");
+ return -1;
+ }
+
+ if (duty_ns > period_ns) {
+ pr_err("Not available duty_ns period_ns error\n");
+ return -1;
+ }
+
+ if (pwm_state[channel].period != period_ns ||
+ pwm_state[channel].duty_cycle != duty_ns) {
+ meson_pwm_cacl(dev, channel, period_ns, duty_ns);
+ switch (channel) {
+ case MESON_PWM0:
+ case MESON_PWM1:
+ pwm_meson_config(dev, channel);
+ break;
+
+ case MESON_PWM2:
+ case MESON_PWM3:
+ pwm_meson_config_ext(dev, channel);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ pwm_state[channel].period = period_ns;
+ pwm_state[channel].duty_cycle = duty_ns;
+
+ return 0;
+};
+
+static void meson_pwm_meson_enable(struct udevice *dev, unsigned int channel)
+{
+ struct meson_pwm_priv *priv = dev_get_priv(dev);
+ struct meson_pwm_reg *regs = priv->regs;
+ struct meson_pwm_state *pwm_state = priv->pwm_state;
+ fdt_addr_t clk_addr = priv->extern_clk_addr;
+ unsigned int val, val_clk, orig;
+
+ switch (channel) {
+ case MESON_PWM0:
+ val = 1 << 0;
+ val_clk = 1 << 8;
+ break;
+ case MESON_PWM1:
+ val = 1 << 1;
+ val_clk = 1 << 24;
+ break;
+ case MESON_PWM2:
+ val = 1 << 25;
+ val_clk = 1 << 8;
+ break;
+ case MESON_PWM3:
+ val = 1 << 24;
+ val_clk = 1 << 24;
+ break;
+ default:
+ pr_err("channel is not legal\n");
+ return;
+ }
+
+ orig = readl(®s->miscr);
+ orig |= val;
+ writel(orig, ®s->miscr);
+ if (priv->pwm_data->extern_clk) {
+ orig = readl(clk_addr);
+ orig |= val_clk;
+ writel(orig, clk_addr);
+ }
+ pwm_state[channel].enabled = 1;
+}
+
+static void meson_pwm_meson_disable(struct udevice *dev, unsigned int channel)
+{
+ struct meson_pwm_priv *priv = dev_get_priv(dev);
+ struct meson_pwm_reg *regs = priv->regs;
+ struct meson_pwm_state *pwm_state = priv->pwm_state;
+ fdt_addr_t clk_addr = priv->extern_clk_addr;
+ unsigned int val, val_clk, orig;
+
+ switch (channel) {
+ case MESON_PWM0:
+ val = 1 << 0;
+ val_clk = 1 << 8;
+ break;
+ case MESON_PWM1:
+ val = 1 << 1;
+ val_clk = 1 << 24;
+ break;
+ case MESON_PWM2:
+ val = 1 << 25;
+ val_clk = 1 << 8;
+ break;
+ case MESON_PWM3:
+ val = 1 << 24;
+ val_clk = 1 << 24;
+ break;
+ default:
+ pr_err("channel is not legal\n");
+ return;
+ }
+
+ orig = readl(®s->miscr);
+ orig &= ~val;
+ writel(orig, ®s->miscr);
+ if (priv->pwm_data->extern_clk) {
+ orig = readl(clk_addr);
+ orig &= ~val_clk;
+ writel(orig, clk_addr);
+ }
+ pwm_state[channel].enabled = 0;
+}
+
+static int meson_pwm_set_enable(struct udevice *dev, uint channel, bool enable)
+{
+ struct meson_pwm_priv *priv = dev_get_priv(dev);
+ struct meson_pwm_state *pwm_state = priv->pwm_state;
+
+ if (pwm_state[channel].enabled != enable) {
+ if (enable)
+ meson_pwm_meson_enable(dev, channel);
+ else
+ meson_pwm_meson_disable(dev, channel);
+ }
+
+ return 0;
+}
+
+static int meson_pwm_set_invert(struct udevice *dev, uint channel, bool polarity)
+{
+ struct meson_pwm_priv *priv = dev_get_priv(dev);
+ struct meson_pwm_state *pwm_state = priv->pwm_state;
+#ifdef PWM_REG_INVERT
+ struct meson_pwm_reg *regs = priv->regs;
+
+ switch (channel) {
+ case MESON_PWM0:
+ if (polarity)
+ setbits_le32(®s->miscr, 0x01 << 26);
+ else
+ clrbits_le32(®s->miscr, 0x01 << 26);
+ break;
+
+ case MESON_PWM1:
+ if (polarity)
+ setbits_le32(®s->miscr, 0x01 << 27);
+ else
+ clrbits_le32(®s->miscr, 0x01 << 27);
+ break;
+
+ default:
+ pr_err("Id is invalid\n");
+ break;
+ }
+#endif
+ if (polarity)
+ pwm_state[channel].polarity = 1;
+ else
+ pwm_state[channel].polarity = 0;
+ return 0;
+};
+
+static int meson_pwm_set_times(struct udevice *dev, uint channel, uint times)
+{
+ struct meson_pwm_priv *priv = dev_get_priv(dev);
+ struct meson_pwm_reg *regs = priv->regs;
+
+ if (times > 256 || times <= 0) {
+ pr_err("Not available times error\n");
+ return -1;
+ }
+
+ switch (channel) {
+ case MESON_PWM0:
+ clrbits_le32(®s->tr, 0xff << 24);
+ setbits_le32(®s->tr, (times - 1) << 24);
+ break;
+
+ case MESON_PWM1:
+ clrbits_le32(®s->tr, 0xff << 8);
+ setbits_le32(®s->tr, (times - 1) << 8);
+ break;
+
+ case MESON_PWM2:
+ clrbits_le32(®s->tr, 0xff << 16);
+ setbits_le32(®s->tr, (times - 1) << 16);
+ break;
+
+ case MESON_PWM3:
+ clrbits_le32(®s->tr, 0xff << 0);
+ setbits_le32(®s->tr, (times - 1) << 0);
+ break;
+
+ default:
+ pr_err("Id is invalid\n");
+ break;
+ }
+
+ return 0;
+};
+
+static int meson_pwm_set_blink_times(struct udevice *dev, uint channel, uint times)
+{
+ struct meson_pwm_priv *priv = dev_get_priv(dev);
+ struct meson_pwm_reg *regs = priv->regs;
+
+ if (times > 16 || times <= 0) {
+ pr_err("Not available times error\n");
+ return -1;
+ }
+
+ switch (channel) {
+ case MESON_PWM0:
+ case MESON_PWM2:
+ clrbits_le32(®s->br, 0xf);
+ setbits_le32(®s->br, times - 1);
+ break;
+
+ case MESON_PWM1:
+ case MESON_PWM3:
+ clrbits_le32(®s->br, 0xf << 4);
+ setbits_le32(®s->br, (times - 1) << 4);
+ break;
+
+ default:
+ pr_err("Id is invalid\n");
+ break;
+ }
+
+ return 0;
+};
+
+static int meson_pwm_blink_enable(struct udevice *dev, uint channel, bool enable)
+{
+ struct meson_pwm_priv *priv = dev_get_priv(dev);
+ struct meson_pwm_reg *regs = priv->regs;
+
+ switch (channel) {
+ case MESON_PWM0:
+ case MESON_PWM2:
+ if (enable)
+ setbits_le32(®s->br, 1 << 8);
+ else
+ clrbits_le32(®s->br, 1 << 8);
+ break;
+
+ case MESON_PWM1:
+ case MESON_PWM3:
+ if (enable)
+ setbits_le32(®s->br, 1 << 9);
+ else
+ clrbits_le32(®s->br, 1 << 9);
+ break;
+
+ default:
+ pr_err("Id is invalid\n");
+ break;
+ }
+
+ return 0;
+};
+
+static int meson_pwm_probe(struct udevice *dev)
+{
+ struct meson_pwm_priv *priv = dev_get_priv(dev);
+
+ priv->pwm_data = (struct meson_pwm_data *)dev_get_driver_data(dev);
+ priv->regs = (struct meson_pwm_reg *)dev_read_addr_index(dev, 0);
+ if (priv->regs == (void *)FDT_ADDR_T_NONE) {
+ pr_err("Coun't get pwm base regs addr\n");
+ return -1;
+ }
+
+ /* If you use external clk, get clk regs addr */
+ if (priv->pwm_data->extern_clk) {
+ priv->extern_clk_addr = dev_read_addr_index(dev, 1);
+ if (priv->extern_clk_addr == FDT_ADDR_T_NONE) {
+ pr_err("Coun't get pwm clk regs addr\n");
+ return -1;
+ }
+ }
+
+ priv->pwm_state = (struct meson_pwm_state *)calloc(4, sizeof(struct meson_pwm_state));
+
+ return 0;
+}
+
+int meson_pwm_remove(struct udevice *dev)
+{
+ struct meson_pwm_priv *priv = dev_get_priv(dev);
+
+ free(priv->pwm_state);
+
+ return 0;
+}
+
+static const struct meson_pwm_data pwm_meson_g12a_data = {
+ .extern_clk = 0,
+};
+
+static const struct meson_pwm_data pwm_meson_v2_data = {
+ .extern_clk = 1,
+};
+
+static const struct pwm_ops meson_pwm_ops = {
+ .set_config = meson_pwm_set_config,
+ .set_enable = meson_pwm_set_enable,
+ .set_invert = meson_pwm_set_invert,
+ .set_times = meson_pwm_set_times,
+ .set_blink_times = meson_pwm_set_blink_times,
+ .set_blink_enable = meson_pwm_blink_enable,
+};
+
+static const struct udevice_id meson_pwm_ids[] = {
+ {.compatible = "amlogic,g12a-ee-pwm", .data = (long)&pwm_meson_g12a_data},
+ {.compatible = "amlogic,g12a-ao-pwm", .data = (long)&pwm_meson_g12a_data},
+ {.compatible = "amlogic,meson-v2-pwm", .data = (long)&pwm_meson_v2_data},
+ {}
+};
+
+U_BOOT_DRIVER(meson_pwm) = {
+ .name = "meson_pwm",
+ .id = UCLASS_PWM,
+ .of_match = meson_pwm_ids,
+ .ops = &meson_pwm_ops,
+ .probe = meson_pwm_probe,
+ .remove = meson_pwm_remove,
+ .priv_auto = sizeof(struct meson_pwm_priv),
+};
diff --git a/drivers/amlogic/pwm/pwm-meson.h b/drivers/amlogic/pwm/pwm-meson.h
new file mode 100644
index 0000000..9c129c4
--- /dev/null
+++ b/drivers/amlogic/pwm/pwm-meson.h
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */
+/*
+ * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
+ */
+
+#ifndef PWM_MESON_H
+#define PWM_MESON_H
+
+#define NSEC_PER_SEC 1000000000ULL
+
+#define MESON_PWM0 0
+#define MESON_PWM1 1
+#define MESON_PWM2 2
+#define MESON_PWM3 3
+
+struct meson_pwm_reg {
+ u32 dar;/* A/C/E Duty Register */
+ u32 dbr;/* B/D/F Duty Register */
+ u32 miscr;/* misc Register */
+ u32 dsr;/*DS Register*/
+ u32 tr;/*times Register*/
+ u32 da2r;/* A2/C2/E2 Duty Register */
+ u32 db2r;/* B2/D2/F2 Duty Register */
+ u32 br;/*Blink Register*/
+};
+
+struct meson_pwm_data {
+ u8 extern_clk; /* use external clktree */
+};
+
+enum pwm_polarity {
+ PWM_POLARITY_NORMAL,
+ PWM_POLARITY_INVERSED,
+};
+
+struct meson_pwm_state {
+ unsigned int period;
+ unsigned int duty_cycle;
+ unsigned int hi;
+ unsigned int lo;
+ unsigned int pre_div;
+ enum pwm_polarity polarity;
+ bool enabled;
+};
+
+/*
+ * Same as above but for u64 dividends. divisor must be a 32-bit
+ * number.
+ */
+#define DIV_ROUND_CLOSEST_ULL(x, divisor)( \
+{ \
+ typeof(divisor) __d = divisor; \
+ unsigned long long _tmp = (x) + (__d) / 2; \
+ do_div(_tmp, __d); \
+ _tmp; \
+} \
+)
+
+#endif