[PATCH 2/2] pwm: sophgo: add driver for SG2044

Longbin Li
2025-04-07T07:22:07Z

From: ghost <2990955050@qq.com>

Add PWM controller for SG2044.

Signed-off-by: Longbin Li <looong.bin@gmail.com>
---
drivers/pwm/pwm-sophgo-sg2042.c | 162 +++++++++++++++++++++++++++-----
1 file changed, 138 insertions(+), 24 deletions(-)

diff --git a/drivers/pwm/pwm-sophgo-sg2042.c b/drivers/pwm/pwm-sophgo-sg2042.c
index ff4639d849ce..c62e8c758d87 100644
--- a/drivers/pwm/pwm-sophgo-sg2042.c
+++ b/drivers/pwm/pwm-sophgo-sg2042.c
@@ -26,20 +26,22 @@
#include <linux/pwm.h>
#include <linux/reset.h>

-/*
- * Offset RegisterName
- * 0x0000 HLPERIOD0
- * 0x0004 PERIOD0
- * 0x0008 HLPERIOD1
- * 0x000C PERIOD1
- * 0x0010 HLPERIOD2
- * 0x0014 PERIOD2
- * 0x0018 HLPERIOD3
- * 0x001C PERIOD3
- * Four groups and every group is composed of HLPERIOD & PERIOD
- */
-#define SG2042_PWM_HLPERIOD(chan) ((chan) * 8 + 0)
-#define SG2042_PWM_PERIOD(chan) ((chan) * 8 + 4)
+#define REG_HLPERIOD 0x0
+#define REG_PERIOD 0x4
+#define REG_GROUP 0x8
+#define REG_POLARITY 0x40
+
+#define REG_PWMSTART 0x44
+#define REG_PWMUPDATE 0x4C
+#define REG_SHIFTCOUNT 0x80
+#define REG_SHIFTSTART 0x90
+#define REG_PWM_OE 0xD0
+
+#define PWM_REG_NUM 0x80
+
+#define PWM_POLARITY_MASK(n) BIT(n)
+#define PWM_HLPERIOD(chan) ((chan) * REG_GROUP + REG_HLPERIOD)
+#define PWM_PERIOD(chan) ((chan) * REG_GROUP + REG_PERIOD)

#define SG2042_PWM_CHANNELNUM 4

@@ -51,6 +53,12 @@
struct sg2042_pwm_ddata {
void __iomem *base;
unsigned long clk_rate_hz;
+ struct mutex lock;
+};
+
+struct sg2042_chip_data {
+ const struct pwm_ops ops;
+ bool atomic;
};

/*
@@ -62,8 +70,8 @@ static void pwm_sg2042_config(struct sg2042_pwm_ddata *ddata, unsigned int chan,
{
void __iomem *base = ddata->base;

- writel(period_ticks, base + SG2042_PWM_PERIOD(chan));
- writel(hlperiod_ticks, base + SG2042_PWM_HLPERIOD(chan));
+ writel(period_ticks, base + PWM_PERIOD(chan));
+ writel(hlperiod_ticks, base + PWM_HLPERIOD(chan));
}

static int pwm_sg2042_apply(struct pwm_chip *chip, struct pwm_device *pwm,
@@ -104,8 +112,8 @@ static int pwm_sg2042_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
u32 hlperiod_ticks;
u32 period_ticks;

- period_ticks = readl(ddata->base + SG2042_PWM_PERIOD(chan));
- hlperiod_ticks = readl(ddata->base + SG2042_PWM_HLPERIOD(chan));
+ period_ticks = readl(ddata->base + PWM_PERIOD(chan));
+ hlperiod_ticks = readl(ddata->base + PWM_HLPERIOD(chan));

if (!period_ticks) {
state->enabled = false;
@@ -123,13 +131,112 @@ static int pwm_sg2042_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
return 0;
}

-static const struct pwm_ops pwm_sg2042_ops = {
- .apply = pwm_sg2042_apply,
- .get_state = pwm_sg2042_get_state,
+static void pwm_sg2044_config(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm, bool enabled)
+{
+ u32 pwm_value;
+
+ pwm_value = readl(ddata->base + REG_PWMSTART);
+
+ if (enabled)
+ writel(pwm_value | BIT(pwm->hwpwm), ddata->base + REG_PWMSTART);
+ else
+ writel(pwm_value & ~BIT(pwm->hwpwm), ddata->base + REG_PWMSTART);
+}
+
+static void pwm_sg2044_set_outputenable(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm,
+ bool enabled)
+{
+ u32 pwm_value;
+
+ pwm_value = readl(ddata->base + REG_PWM_OE);
+
+ if (enabled)
+ writel(pwm_value | BIT(pwm->hwpwm), ddata->base + REG_PWM_OE);
+ else
+ writel(pwm_value & ~BIT(pwm->hwpwm), ddata->base + REG_PWM_OE);
+}
+
+static int pwm_sg2044_set_polarity(struct sg2042_pwm_ddata *ddata, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ enum pwm_polarity polarity;
+ u32 pwm_value;
+
+ pwm_value = readl(ddata->base + REG_POLARITY);
+
+ polarity = state->polarity;
+
+ if (polarity == PWM_POLARITY_NORMAL)
+ pwm_value &= ~BIT(pwm->hwpwm);
+ else
+ pwm_value |= BIT(pwm->hwpwm);
+
+ writel(pwm_value, ddata->base + REG_POLARITY);
+
+ return 0;
+}
+
+static int pwm_sg2044_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct sg2042_pwm_ddata *ddata = pwmchip_get_drvdata(chip);
+ u32 hlperiod_ticks;
+ u32 period_ticks;
+
+ if (!state->enabled) {
+ pwm_sg2044_config(ddata, pwm, false);
+ return 0;
+ }
+
+ pwm_sg2044_set_polarity(ddata, pwm, state);
+
+ /*
+ * Duration of High level (duty_cycle) = HLPERIOD x Period_of_input_clk
+ * Duration of One Cycle (period) = PERIOD x Period_of_input_clk
+ */
+ period_ticks = min(mul_u64_u64_div_u64(ddata->clk_rate_hz, state->period,
+ NSEC_PER_SEC), U32_MAX);
+ hlperiod_ticks = min(mul_u64_u64_div_u64(ddata->clk_rate_hz, state->duty_cycle,
+ NSEC_PER_SEC), U32_MAX);
+
+ dev_dbg(pwmchip_parent(chip), "chan[%u]: PERIOD=%u, HLPERIOD=%u\n",
+ pwm->hwpwm, period_ticks, hlperiod_ticks);
+
+ pwm_sg2042_config(ddata, pwm->hwpwm, period_ticks, hlperiod_ticks);
+
+ guard(mutex)(&ddata->lock);
+
+ /*
+ * re-enable PWMSTART to refresh the register period
+ */
+ pwm_sg2044_config(ddata, pwm, false);
+ pwm_sg2044_set_outputenable(ddata, pwm, true);
+ pwm_sg2044_config(ddata, pwm, true);
+
+ return 0;
+}
+
+static const struct sg2042_chip_data sg2042_chip_data = {
+ .ops = {
+ .apply = pwm_sg2042_apply,
+ .get_state = pwm_sg2042_get_state,
+ },
+ .atomic = true,
+};
+
+static const struct sg2042_chip_data sg2044_chip_data = {
+ .ops = {
+ .apply = pwm_sg2044_apply,
+ .get_state = pwm_sg2042_get_state,
+ },
+ .atomic = false,
};

static const struct of_device_id sg2042_pwm_ids[] = {
- { .compatible = "sophgo,sg2042-pwm" },
+ { .compatible = "sophgo,sg2042-pwm",
+ .data = &sg2042_chip_data },
+ { .compatible = "sophgo,sg2044-pwm",
+ .data = &sg2044_chip_data },
{ }
};
MODULE_DEVICE_TABLE(of, sg2042_pwm_ids);
@@ -137,17 +244,24 @@ MODULE_DEVICE_TABLE(of, sg2042_pwm_ids);
static int pwm_sg2042_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
+ const struct sg2042_chip_data *chip_data;
struct sg2042_pwm_ddata *ddata;
struct reset_control *rst;
struct pwm_chip *chip;
struct clk *clk;
int ret;

+ chip_data = device_get_match_data(dev);
+ if (!chip_data)
+ return -ENODEV;
+
chip = devm_pwmchip_alloc(dev, SG2042_PWM_CHANNELNUM, sizeof(*ddata));
if (IS_ERR(chip))
return PTR_ERR(chip);
ddata = pwmchip_get_drvdata(chip);

+ mutex_init(&ddata->lock);
+
ddata->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(ddata->base))
return PTR_ERR(ddata->base);
@@ -170,8 +284,8 @@ static int pwm_sg2042_probe(struct platform_device *pdev)
if (IS_ERR(rst))
return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset\n");

- chip->ops = &pwm_sg2042_ops;
- chip->atomic = true;
+ chip->ops = &chip_data->ops;
+ chip->atomic = chip_data->atomic;

ret = devm_pwmchip_add(dev, chip);
if (ret < 0)
--
2.48.1