From abec461f7f012e881e66121b3ce6c331e4fb0394 Mon Sep 17 00:00:00 2001 From: ilikara <3435193369@qq.com> Date: Tue, 18 Feb 2025 15:18:27 +0000 Subject: [PATCH] feat(pwm): add ATIM and GTIM pwm driver --- arch/loongarch/boot/dts/loongson2k300.dtsi | 123 +++++++ drivers/pwm/Kconfig | 20 ++ drivers/pwm/Makefile | 2 + drivers/pwm/pwm-loongson-atim.c | 374 +++++++++++++++++++++ drivers/pwm/pwm-loongson-gtim.c | 367 ++++++++++++++++++++ 5 files changed, 886 insertions(+) create mode 100644 drivers/pwm/pwm-loongson-atim.c create mode 100644 drivers/pwm/pwm-loongson-gtim.c diff --git a/arch/loongarch/boot/dts/loongson2k300.dtsi b/arch/loongarch/boot/dts/loongson2k300.dtsi index 589649743..62d4f1189 100644 --- a/arch/loongarch/boot/dts/loongson2k300.dtsi +++ b/arch/loongarch/boot/dts/loongson2k300.dtsi @@ -237,6 +237,105 @@ 0x14 0x00000003 0x00000003>; }; + atim1_pin: atim1-pin { + atim1_pin_m0: pinmux_G28_as_atim1 { + pinctrl-single,bits = <0x4 0x02000000 0x03000000>; + }; + atim1_pin_m1: pinmux_G81_as_atim1 { + pinctrl-single,bits = <0x14 0x0000000c 0x0000000c>; + }; + }; + + atim2_pin: atim2-pin { + atim2_pin_m0: pinmux_G29_as_atim1 { + pinctrl-single,bits = <0x4 0x08000000 0x0c000000>; + }; + atim2_pin_m1: pinmux_G82_as_atim1 { + pinctrl-single,bits = <0x14 0x00000030 0x00000030>; + }; + }; + + atim3_pin: atim3-pin { + atim3_pin_m0: pinmux_G30_as_atim3 { + pinctrl-single,bits = <0x4 0x20000000 0x30000000>; + }; + atim3_pin_m1: pinmux_G83_as_atim3 { + pinctrl-single,bits = <0x14 0x000000c0 0x000000c0>; + }; + }; + + atim1n_pin: atim1n-pin { + atim1n_pin_m0: pinmux_G31_as_atim1n { + pinctrl-single,bits = <0x4 0x80000000 0xc0000000>; + }; + atim1n_pin_m1: pinmux_G84_as_atim1n { + pinctrl-single,bits = <0x14 0x00000300 0x00000300>; + }; + }; + + atim2n_pin: atim2n-pin { + atim2n_pin_m0: pinmux_G32_as_atim1n { + pinctrl-single,bits = <0x8 0x00000002 0x00000003>; + }; + atim2n_pin_m1: pinmux_G85_as_atim1n { + pinctrl-single,bits = <0x14 0x00000c00 0x00000c00>; + }; + }; + + atim3n_pin: atim3n-pin { + atim3n_pin_m0: pinmux_G33_as_atim1n { + pinctrl-single,bits = <0x8 0x00000008 0x0000000c>; + }; + atim3n_pin_m1: pinmux_G86_as_atim1n { + pinctrl-single,bits = <0x14 0x00003000 0x00003000>; + }; + }; + + atim4_pin: atim4-pin { + atim4_pin_m0: pinmux_G76_as_atim4 { + pinctrl-single,bits = <0x10 0x01000000 0x03000000>; + }; + atim4_pin_m1: pinmux_G101_as_atim4 { + pinctrl-single,bits = <0x18 0x00000400 0x00000c00>; + }; + }; + + gtim1_pin: gtim1-pin { + gtim1_pin_m0: pinmux_G34_as_gtim1 { + pinctrl-single,bits = <0x8 0x00000020 0x00000030>; + }; + gtim1_pin_m1: pinmux_G87_as_gtim1 { + pinctrl-single,bits = <0x14 0x0000c000 0x0000c000>; + }; + }; + + gtim2_pin: gtim2-pin { + gtim2_pin_m0: pinmux_G35_as_gtim1 { + pinctrl-single,bits = <0x8 0x00000080 0x000000c0>; + }; + gtim2_pin_m1: pinmux_G88_as_gtim1 { + pinctrl-single,bits = <0x14 0x00030000 0x00030000>; + }; + }; + + gtim3_pin: gtim3-pin { + gtim3_pin_m0: pinmux_G36_as_gtim3 { + pinctrl-single,bits = <0x8 0x00000200 0x00000300>; + }; + gtim3_pin_m1: pinmux_G89_as_gtim3 { + pinctrl-single,bits = <0x14 0x000c0000 0x000c0000>; + }; + }; + + gtim4_pin: gtim4-pin { + gtim4_pin_m0: pinmux_G77_as_gtim4 { + pinctrl-single,bits = <0x10 0x04000000 0x0c000000>; + }; + gtim4_pin_m1: pinmux_G102_as_gtim4 { + pinctrl-single,bits = <0x18 0x00001000 0x00003000>; + }; + }; + pwm0_pins: pinmux_G86_as_pwm0 { pinctrl-single,bits = <0x14 0x00002000 0x00003000>; }; @@ -780,6 +879,30 @@ /* Optional for that dr_mode = "host" or dr_mode = "peripheral" */ }; + pwm_atim: pwm@16118000{ + compatible = "loongson,ls2k-pwm-atim"; + reg = <0 0x16118000 0 0x1000>; + interrupt-parent = <&liointc0>; + interrupts = <13 IRQ_TYPE_LEVEL_HIGH>; + #pwm-cells = <2>; + pinctrl-0 = <&atim3_pin_m1>, <&atim3n_pin_m1>; + pinctrl-names = "default"; + clock-frequency = <200000000>; + status = "disabled"; + }; + + pwm_gtim: pwm@16119000{ + compatible = "loongson,ls2k-pwm-gtim"; + reg = <0 0x16119000 0 0x1000>; + interrupt-parent = <&liointc0>; + interrupts = <14 IRQ_TYPE_LEVEL_HIGH>; + #pwm-cells = <2>; + pinctrl-0 = <>im2_pin_m1>, <>im3_pin_m1>; + pinctrl-names = "default"; + clock-frequency = <200000000>; + status = "disabled"; + }; + pwm0: pwm@0x1611b000 { compatible = "loongson,ls2k-pwm"; reg = <0 0x1611b000 0 0xf>; diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index ef02a44d8..9f7f25e1c 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -363,6 +363,26 @@ config PWM_LOONGSON To compile this driver as a module, choose M here: the module will be called pwm-loongson. +config PWM_LOONGSON_GTIM + tristate "Loongson GTIM PWM support" + depends on MACH_LOONGSON64 || COMPILE_TEST + depends on COMMON_CLK + help + GTIM PWM framework driver for loongson2k. + + To compile this driver as a module, choose M here: the module + will be called pwm-ls-gtim. + +config PWM_LOONGSON_ATIM + tristate "Loongson ATIM PWM support" + depends on MACH_LOONGSON64 || COMPILE_TEST + depends on COMMON_CLK + help + ATIM PWM framework driver for loongson2k. + + To compile this driver as a module, choose M here: the module + will be called pwm-ls-atim. + config PWM_LP3943 tristate "TI/National Semiconductor LP3943 PWM support" depends on MFD_LP3943 diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 7c18c9be4..345c9d348 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -31,6 +31,8 @@ obj-$(CONFIG_PWM_IQS620A) += pwm-iqs620a.o obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o obj-$(CONFIG_PWM_KEEMBAY) += pwm-keembay.o obj-$(CONFIG_PWM_LOONGSON) += pwm-loongson.o +obj-$(CONFIG_PWM_LOONGSON_ATIM) += pwm-loongson-atim.o +obj-$(CONFIG_PWM_LOONGSON_GTIM) += pwm-loongson-gtim.o obj-$(CONFIG_PWM_LP3943) += pwm-lp3943.o obj-$(CONFIG_PWM_LPC18XX_SCT) += pwm-lpc18xx-sct.o obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o diff --git a/drivers/pwm/pwm-loongson-atim.c b/drivers/pwm/pwm-loongson-atim.c new file mode 100644 index 000000000..f78a59a4b --- /dev/null +++ b/drivers/pwm/pwm-loongson-atim.c @@ -0,0 +1,374 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 by ilikara 3435193369@qq.com. + * + * Loongson ATIM PWM driver + * + * For Loongson's ATIM IP block documentation please refer Chapter 18 of + * Reference Manual: https://loongson.cn/uploads/images/2024120211205269050.%E9%BE%99%E8%8A%AF2K0300%E5%A4%84%E7%90%86%E5%99%A8%E7%94%A8%E6%88%B7%E6%89%8B%E5%86%8C_V1.0.pdf + * + * Author: Ilikara <3435193369@qq.com> + * + * Limitations: + * - ATIM_CNT will be set to 0 when ls_pwm_atim_config is called. + * - All the 8 channels share the same period. + * - Channel n and n+4 share the same duty cycle. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* counter offest */ +#define ATIM_CR1 0x00 +#define ATIM_CR2 0x04 +#define ATIM_SMCR 0x08 +#define ATIM_DIER 0x0C +#define ATIM_SR 0x10 +#define ATIM_EGR 0x14 +#define ATIM_CCMR(n) 0x18 + (((n) % 4) / 2) * 0x04 +#define ATIM_CCER 0x20 +#define ATIM_CNT 0x24 +#define ATIM_PSC 0x28 +#define ATIM_ARR 0x2C +#define ATIM_RCR 0x30 +#define ATIM_CCR(n) 0x34 + ((n) % 4) * 0x04 +#define ATIM_BDTR 0x44 +#define ATIM_INSTA 0x50 + +/* CR1 each bit */ +#define CR1_CEN BIT(0) + +/* EGR each bit */ +#define EGR_UG BIT(0) + +/* CCMR each bit */ +#define CCMR_CCnS(n) BIT((n) % 2 * 8 + 0) +#define CCMR_OCnFE(n) BIT((n) % 2 * 8 + 2) +#define CCMR_OCnPE(n) BIT((n) % 2 * 8 + 3) +#define CCMR_OCnM(n) BIT((n) % 2 * 8 + 4) +#define CCMR_OCnCE(n) BIT((n) % 2 * 8 + 7) + +/* CCER each bit */ +#define CCER_CCnE(n) BIT(((n) % 4) * 4 + 0 + ((n) / 4) * 2) +#define CCER_CCnP(n) BIT(((n) % 4) * 4 + 1 + ((n) / 4) * 2) + +/* BDTR each bit */ +#define BDTR_MOE BIT(15) + +/* default input clk frequency for the ACPI case */ +#define LOONGSON_PWM_FREQ_DEFAULT 50000000 /* Hz */ + +struct pwm_loongson_suspend_store { + u32 arr_reg; + u32 ccr_reg[4]; + u32 ccer_reg; + u32 en_mark; +}; + +struct pwm_loongson_ddata { + struct clk *clk; + void __iomem *base; + u64 clk_rate; + struct pwm_loongson_suspend_store lss; +}; + +static inline struct pwm_loongson_ddata * +to_pwm_loongson_ddata(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +static inline u32 pwm_loongson_readl(struct pwm_loongson_ddata *ddata, + u32 offset) +{ + return readl(ddata->base + offset); +} + +static inline void pwm_loongson_writel(struct pwm_loongson_ddata *ddata, + u32 val, u32 offset) +{ + writel(val, ddata->base + offset); +} + +static int ls_pwm_atim_set_polarity(struct pwm_chip *chip, + struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + u16 val; + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + val = pwm_loongson_readl(ddata, ATIM_CCER); + + if (polarity == PWM_POLARITY_INVERSED) + /* Duty cycle defines LOW period of PWM */ + val &= ~CCER_CCnP(pwm->hwpwm); + else + /* Duty cycle defines HIGH period of PWM */ + val |= CCER_CCnP(pwm->hwpwm); + + pwm_loongson_writel(ddata, val, ATIM_CCER); + + return 0; +} + +static void ls_pwm_atim_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + u32 val; + + if (pwm->state.polarity == PWM_POLARITY_NORMAL) + pwm_loongson_writel(ddata, ddata->lss.arr_reg, + ATIM_CCR(pwm->hwpwm)); + else if (pwm->state.polarity == PWM_POLARITY_INVERSED) + pwm_loongson_writel(ddata, 0, ATIM_CCR(pwm->hwpwm)); + + val = pwm_loongson_readl(ddata, ATIM_CCMR(pwm->hwpwm)); + val &= ~(0b111 * CCMR_OCnM(pwm->hwpwm)); + pwm_loongson_writel(ddata, val, ATIM_CCMR(pwm->hwpwm)); + + val = pwm_loongson_readl(ddata, ATIM_CCER); + val &= ~CCER_CCnE(pwm->hwpwm); + pwm_loongson_writel(ddata, val, ATIM_CCER); + + ddata->lss.en_mark &= ~BIT(pwm->hwpwm); + if (ddata->lss.en_mark == 0b00000000) { + val = pwm_loongson_readl(ddata, ATIM_CR1); + val &= ~CR1_CEN; + pwm_loongson_writel(ddata, val, ATIM_CR1); + pwm_loongson_writel(ddata, 0x0, ATIM_CNT); + } +} + +static int ls_pwm_atim_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + u32 val; + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + pwm_loongson_writel(ddata, ddata->lss.ccr_reg[pwm->hwpwm % 4], + ATIM_CCR(pwm->hwpwm)); + pwm_loongson_writel(ddata, ddata->lss.arr_reg, ATIM_ARR); + + // todo:类似config函数中的特殊处理 + val = pwm_loongson_readl(ddata, ATIM_CCMR(pwm->hwpwm)); + val |= 0b111 * CCMR_OCnM(pwm->hwpwm); + pwm_loongson_writel(ddata, val, ATIM_CCMR(pwm->hwpwm)); + + val = pwm_loongson_readl(ddata, ATIM_CCER); + val |= CCER_CCnE(pwm->hwpwm); + pwm_loongson_writel(ddata, val, ATIM_CCER); + + if (ddata->lss.en_mark == 0b00000000) { + pwm_loongson_writel(ddata, 0x0, ATIM_CNT); + val = pwm_loongson_readl(ddata, ATIM_CR1); + val |= CR1_CEN; + pwm_loongson_writel(ddata, val, ATIM_CR1); + } + ddata->lss.en_mark |= BIT(pwm->hwpwm); + + return 0; +} + +static int ls_pwm_atim_config(struct pwm_chip *chip, struct pwm_device *pwm, + u64 duty_ns, u64 period_ns) +{ + u32 duty, period; + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + /* duty = duty_ns * ddata->clk_rate / NSEC_PER_SEC */ + duty = mul_u64_u64_div_u64(duty_ns, ddata->clk_rate, NSEC_PER_SEC); + pwm_loongson_writel(ddata, duty, ATIM_CCR(pwm->hwpwm)); + + /* period = period_ns * ddata->clk_rate / NSEC_PER_SEC */ + period = mul_u64_u64_div_u64(period_ns, ddata->clk_rate, NSEC_PER_SEC); + pwm_loongson_writel(ddata, period, ATIM_ARR); + + pwm_loongson_writel(ddata, 0, ATIM_CNT); + + ddata->lss.ccr_reg[pwm->hwpwm % 4] = duty; + ddata->lss.arr_reg = period; + + return 0; +} + +static int ls_pwm_atim_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + int ret; + u64 period, duty_cycle; + bool enabled = pwm->state.enabled; + + if (!state->enabled) { + if (enabled) + ls_pwm_atim_disable(chip, pwm); + return 0; + } + + ret = ls_pwm_atim_set_polarity(chip, pwm, state->polarity); + if (ret) + return ret; + + period = min(state->period, NSEC_PER_SEC); + duty_cycle = min(state->duty_cycle, NSEC_PER_SEC); + + ret = ls_pwm_atim_config(chip, pwm, state->duty_cycle, state->period); + if (ret) + return ret; + + if (!enabled && state->enabled) + ret = ls_pwm_atim_enable(chip, pwm); + + return ret; +} + +static int ls_pwm_atim_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + u32 arr_reg, ccr_reg, ccer_reg; + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + arr_reg = pwm_loongson_readl(ddata, ATIM_ARR); + ccr_reg = pwm_loongson_readl(ddata, ATIM_CCR(pwm->hwpwm)); + ccer_reg = pwm_loongson_readl(ddata, ATIM_CCER); + + /* duty & period have a max of 2^32, so we can't overflow */ + state->duty_cycle = DIV64_U64_ROUND_UP((u64)ccr_reg * NSEC_PER_SEC, + ddata->clk_rate); + state->period = DIV64_U64_ROUND_UP((u64)arr_reg * NSEC_PER_SEC, + ddata->clk_rate); + state->polarity = (ccer_reg & CCER_CCnP(pwm->hwpwm)) ? + PWM_POLARITY_INVERSED : + PWM_POLARITY_NORMAL; + state->enabled = (ccer_reg & CCER_CCnE(pwm->hwpwm)) ? true : false; + + return 0; +} + +static const struct pwm_ops ls_pwm_atim_ops = { + .apply = ls_pwm_atim_apply, + .get_state = ls_pwm_atim_get_state, +}; + +static int ls_pwm_atim_probe(struct platform_device *pdev) +{ + int ret; + struct pwm_chip *chip; + struct pwm_loongson_ddata *ddata; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + u32 of_clk_freq = 0; + + chip = devm_pwmchip_alloc(dev, 8, sizeof(*ddata)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + ddata = to_pwm_loongson_ddata(chip); + + ddata->lss.en_mark = 0b00000000; + + ddata->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ddata->base)) + return PTR_ERR(ddata->base); + + ddata->clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(ddata->clk)) + return dev_err_probe(dev, PTR_ERR(ddata->clk), + "failed to get pwm clock\n"); + ddata->clk_rate = LOONGSON_PWM_FREQ_DEFAULT; + if (ddata->clk) { + ret = devm_clk_rate_exclusive_get(dev, ddata->clk); + if (ret) + return ret; + + ddata->clk_rate = clk_get_rate(ddata->clk); + } else { +#ifdef CONFIG_OF + if (!of_property_read_u32(np, "clock-frequency", &of_clk_freq)) + ddata->clk_rate = of_clk_freq; +#endif + } + + /* Explicitly initialize the CCER register */ + pwm_loongson_writel(ddata, 0, ATIM_CCER); + + /* Additional configuration for ATIM */ + pwm_loongson_writel(ddata, EGR_UG, ATIM_EGR); + pwm_loongson_writel(ddata, BDTR_MOE, ATIM_BDTR); + + chip->ops = &ls_pwm_atim_ops; + chip->atomic = true; + dev_set_drvdata(dev, chip); + + ret = devm_pwmchip_add(dev, chip); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to add PWM chip\n"); + + return 0; +} + +static int ls_pwm_atim_suspend(struct device *dev) +{ + struct pwm_chip *chip = dev_get_drvdata(dev); + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + ddata->lss.ccer_reg = pwm_loongson_readl(ddata, ATIM_CCER); + ddata->lss.ccr_reg[0] = pwm_loongson_readl(ddata, ATIM_CCR(0)); + ddata->lss.ccr_reg[1] = pwm_loongson_readl(ddata, ATIM_CCR(1)); + ddata->lss.ccr_reg[2] = pwm_loongson_readl(ddata, ATIM_CCR(2)); + ddata->lss.ccr_reg[3] = pwm_loongson_readl(ddata, ATIM_CCR(3)); + ddata->lss.arr_reg = pwm_loongson_readl(ddata, ATIM_ARR); + + clk_disable_unprepare(ddata->clk); + + return 0; +} + +static int ls_pwm_atim_resume(struct device *dev) +{ + int ret; + struct pwm_chip *chip = dev_get_drvdata(dev); + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + ret = clk_prepare_enable(ddata->clk); + if (ret) + return ret; + + pwm_loongson_writel(ddata, ddata->lss.ccer_reg, ATIM_CCER); + pwm_loongson_writel(ddata, ddata->lss.ccr_reg[0], ATIM_CCR(0)); + pwm_loongson_writel(ddata, ddata->lss.ccr_reg[1], ATIM_CCR(1)); + pwm_loongson_writel(ddata, ddata->lss.ccr_reg[2], ATIM_CCR(2)); + pwm_loongson_writel(ddata, ddata->lss.ccr_reg[3], ATIM_CCR(3)); + pwm_loongson_writel(ddata, ddata->lss.arr_reg, ATIM_ARR); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ls_pwm_atim_pm_ops, ls_pwm_atim_suspend, + ls_pwm_atim_resume); + +static struct of_device_id ls_pwm_atim_id_table[] = { + { .compatible = "loongson,ls2k-pwm-atim" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ls_pwm_atim_id_table); + +static struct platform_driver ls_pwm_atim_driver = { + .driver = { + .name = "ls-pwm-atim", + .pm = pm_ptr(&ls_pwm_atim_pm_ops), + .of_match_table = of_match_ptr(ls_pwm_atim_id_table), + }, + .probe = ls_pwm_atim_probe, +}; +module_platform_driver(ls_pwm_atim_driver); + +MODULE_DESCRIPTION("Loongson Atimer Pwm Driver"); +MODULE_AUTHOR("Ilikara <3435193369@qq.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/pwm/pwm-loongson-gtim.c b/drivers/pwm/pwm-loongson-gtim.c new file mode 100644 index 000000000..202c1bad5 --- /dev/null +++ b/drivers/pwm/pwm-loongson-gtim.c @@ -0,0 +1,367 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 by ilikara 3435193369@qq.com. + * + * Loongson GTIM PWM driver + * + * For Loongson's GTIM IP block documentation please refer Chapter 19 of + * Reference Manual: https://loongson.cn/uploads/images/2024120211205269050.%E9%BE%99%E8%8A%AF2K0300%E5%A4%84%E7%90%86%E5%99%A8%E7%94%A8%E6%88%B7%E6%89%8B%E5%86%8C_V1.0.pdf + * + * Author: Ilikara <3435193369@qq.com> + * + * Limitations: + * - GTIM_CNT will be set to 0 when ls_pwm_gtim_config is called. + * - All the 4 channels share the same period. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* counter offest */ +#define GTIM_CR1 0x00 +#define GTIM_CR2 0x04 +#define GTIM_SMCR 0x08 +#define GTIM_DIER 0x0C +#define GTIM_SR 0x10 +#define GTIM_EGR 0x14 +#define GTIM_CCMR(n) 0x18 + ((n) / 2) * 0x04 +#define GTIM_CCER 0x20 +#define GTIM_CNT 0x24 +#define GTIM_PSC 0x28 +#define GTIM_ARR 0x2C +#define GTIM_CCR(n) 0x34 + (n) * 0x04 +#define GTIM_INSTA 0x50 + +/* CR1 each bit */ +#define CR1_CEN BIT(0) + +/* EGR each bit */ +#define EGR_UG BIT(0) + +/* CCMR each bit */ +#define CCMR_CCnS(n) BIT((n) % 2 * 8 + 0) +#define CCMR_OCnFE(n) BIT((n) % 2 * 8 + 2) +#define CCMR_OCnPE(n) BIT((n) % 2 * 8 + 3) +#define CCMR_OCnM(n) BIT((n) % 2 * 8 + 4) +#define CCMR_OCnCE(n) BIT((n) % 2 * 8 + 7) + +/* CCER each bit */ +#define CCER_CCnE(n) BIT((n) * 4 + 0) +#define CCER_CCnP(n) BIT((n) * 4 + 1) + +/* default input clk frequency for the ACPI case */ +#define LOONGSON_PWM_FREQ_DEFAULT 50000000 /* Hz */ + +struct pwm_loongson_suspend_store { + u32 arr_reg; + u32 ccr_reg[4]; + u32 ccer_reg; + u32 en_mark; +}; + +struct pwm_loongson_ddata { + struct clk *clk; + void __iomem *base; + u64 clk_rate; + struct pwm_loongson_suspend_store lss; +}; + +static inline struct pwm_loongson_ddata * +to_pwm_loongson_ddata(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + +static inline u32 pwm_loongson_readl(struct pwm_loongson_ddata *ddata, + u32 offset) +{ + return readl(ddata->base + offset); +} + +static inline void pwm_loongson_writel(struct pwm_loongson_ddata *ddata, + u32 val, u32 offset) +{ + writel(val, ddata->base + offset); +} + +static int ls_pwm_gtim_set_polarity(struct pwm_chip *chip, + struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + u16 val; + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + val = pwm_loongson_readl(ddata, GTIM_CCER); + + if (polarity == PWM_POLARITY_INVERSED) + /* Duty cycle defines LOW period of PWM */ + val &= ~CCER_CCnP(pwm->hwpwm); + else + /* Duty cycle defines HIGH period of PWM */ + val |= CCER_CCnP(pwm->hwpwm); + + pwm_loongson_writel(ddata, val, GTIM_CCER); + + return 0; +} + +static void ls_pwm_gtim_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + u32 val; + + if (pwm->state.polarity == PWM_POLARITY_NORMAL) + pwm_loongson_writel(ddata, ddata->lss.arr_reg, + GTIM_CCR(pwm->hwpwm)); + else if (pwm->state.polarity == PWM_POLARITY_INVERSED) + pwm_loongson_writel(ddata, 0, GTIM_CCR(pwm->hwpwm)); + + val = pwm_loongson_readl(ddata, GTIM_CCMR(pwm->hwpwm)); + val &= ~(0b111 * CCMR_OCnM(pwm->hwpwm)); + pwm_loongson_writel(ddata, val, GTIM_CCMR(pwm->hwpwm)); + + val = pwm_loongson_readl(ddata, GTIM_CCER); + val &= ~CCER_CCnE(pwm->hwpwm); + pwm_loongson_writel(ddata, val, GTIM_CCER); + + ddata->lss.en_mark &= ~BIT(pwm->hwpwm); + if (ddata->lss.en_mark == 0b0000) { + val = pwm_loongson_readl(ddata, GTIM_CR1); + val &= ~CR1_CEN; + pwm_loongson_writel(ddata, val, GTIM_CR1); + pwm_loongson_writel(ddata, 0x0, GTIM_CNT); + } +} + +static int ls_pwm_gtim_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + u32 val; + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + pwm_loongson_writel(ddata, ddata->lss.ccr_reg[pwm->hwpwm % 4], + GTIM_CCR(pwm->hwpwm)); + pwm_loongson_writel(ddata, ddata->lss.arr_reg, GTIM_ARR); + + // todo:类似config函数中的特殊处理 + val = pwm_loongson_readl(ddata, GTIM_CCMR(pwm->hwpwm)); + val |= 0b111 * CCMR_OCnM(pwm->hwpwm); + pwm_loongson_writel(ddata, val, GTIM_CCMR(pwm->hwpwm)); + + val = pwm_loongson_readl(ddata, GTIM_CCER); + val |= CCER_CCnE(pwm->hwpwm); + pwm_loongson_writel(ddata, val, GTIM_CCER); + + if (ddata->lss.en_mark == 0b0000) { + pwm_loongson_writel(ddata, 0x0, GTIM_CNT); + val = pwm_loongson_readl(ddata, GTIM_CR1); + val |= CR1_CEN; + pwm_loongson_writel(ddata, val, GTIM_CR1); + } + ddata->lss.en_mark |= BIT(pwm->hwpwm); + + return 0; +} + +static int ls_pwm_gtim_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + u32 duty, period; + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + /* duty = duty_ns * ddata->clk_rate / NSEC_PER_SEC */ + duty = mul_u64_u64_div_u64(duty_ns, ddata->clk_rate, NSEC_PER_SEC); + pwm_loongson_writel(ddata, duty, GTIM_CCR(pwm->hwpwm)); + + /* period = period_ns * ddata->clk_rate / NSEC_PER_SEC */ + period = mul_u64_u64_div_u64(period_ns, ddata->clk_rate, NSEC_PER_SEC); + pwm_loongson_writel(ddata, period, GTIM_ARR); + + pwm_loongson_writel(ddata, 0, GTIM_CNT); + + ddata->lss.ccr_reg[pwm->hwpwm] = duty; + ddata->lss.arr_reg = period; + + return 0; +} + +static int ls_pwm_gtim_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + int ret; + u64 period, duty_cycle; + bool enabled = pwm->state.enabled; + + if (!state->enabled) { + if (enabled) + ls_pwm_gtim_disable(chip, pwm); + return 0; + } + + ret = ls_pwm_gtim_set_polarity(chip, pwm, state->polarity); + if (ret) + return ret; + + period = min(state->period, NSEC_PER_SEC); + duty_cycle = min(state->duty_cycle, NSEC_PER_SEC); + + ret = ls_pwm_gtim_config(chip, pwm, state->duty_cycle, state->period); + if (ret) + return ret; + + if (!enabled && state->enabled) + ret = ls_pwm_gtim_enable(chip, pwm); + + return ret; +} + +static int ls_pwm_gtim_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + u32 arr_reg, ccr_reg, ccer_reg; + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + arr_reg = pwm_loongson_readl(ddata, GTIM_ARR); + ccr_reg = pwm_loongson_readl(ddata, GTIM_CCR(pwm->hwpwm)); + ccer_reg = pwm_loongson_readl(ddata, GTIM_CCER); + + /* duty & period have a max of 2^32, so we can't overflow */ + state->duty_cycle = DIV64_U64_ROUND_UP((u64)ccr_reg * NSEC_PER_SEC, + ddata->clk_rate); + state->period = DIV64_U64_ROUND_UP((u64)arr_reg * NSEC_PER_SEC, + ddata->clk_rate); + state->polarity = (ccer_reg & CCER_CCnP(pwm->hwpwm)) ? + PWM_POLARITY_INVERSED : + PWM_POLARITY_NORMAL; + state->enabled = (ccer_reg & CCER_CCnE(pwm->hwpwm)) ? true : false; + + return 0; +} + +static const struct pwm_ops ls_pwm_gtim_ops = { + .apply = ls_pwm_gtim_apply, + .get_state = ls_pwm_gtim_get_state, +}; + +static int ls_pwm_gtim_probe(struct platform_device *pdev) +{ + int ret; + struct pwm_chip *chip; + struct pwm_loongson_ddata *ddata; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + u32 of_clk_freq = 0; + + chip = devm_pwmchip_alloc(dev, 4, sizeof(*ddata)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + ddata = to_pwm_loongson_ddata(chip); + + ddata->lss.en_mark = 0b00000000; + + ddata->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ddata->base)) + return PTR_ERR(ddata->base); + + ddata->clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(ddata->clk)) + return dev_err_probe(dev, PTR_ERR(ddata->clk), + "failed to get pwm clock\n"); + ddata->clk_rate = LOONGSON_PWM_FREQ_DEFAULT; + if (ddata->clk) { + ret = devm_clk_rate_exclusive_get(dev, ddata->clk); + if (ret) + return ret; + + ddata->clk_rate = clk_get_rate(ddata->clk); + } else { +#ifdef CONFIG_OF + if (!of_property_read_u32(np, "clock-frequency", &of_clk_freq)) + ddata->clk_rate = of_clk_freq; +#endif + } + + /* Explicitly initialize the CCER register */ + pwm_loongson_writel(ddata, 0, GTIM_CCER); + + /* Additional configuration for GTIM */ + pwm_loongson_writel(ddata, EGR_UG, GTIM_EGR); + + chip->ops = &ls_pwm_gtim_ops; + chip->atomic = true; + dev_set_drvdata(dev, chip); + + ret = devm_pwmchip_add(dev, chip); + if (ret < 0) + return dev_err_probe(dev, ret, "failed to add PWM chip\n"); + + return 0; +} + +static int ls_pwm_gtim_suspend(struct device *dev) +{ + struct pwm_chip *chip = dev_get_drvdata(dev); + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + ddata->lss.ccer_reg = pwm_loongson_readl(ddata, GTIM_CCER); + ddata->lss.ccr_reg[0] = pwm_loongson_readl(ddata, GTIM_CCR(0)); + ddata->lss.ccr_reg[1] = pwm_loongson_readl(ddata, GTIM_CCR(1)); + ddata->lss.ccr_reg[2] = pwm_loongson_readl(ddata, GTIM_CCR(2)); + ddata->lss.ccr_reg[3] = pwm_loongson_readl(ddata, GTIM_CCR(3)); + ddata->lss.arr_reg = pwm_loongson_readl(ddata, GTIM_ARR); + + clk_disable_unprepare(ddata->clk); + + return 0; +} + +static int ls_pwm_gtim_resume(struct device *dev) +{ + int ret; + struct pwm_chip *chip = dev_get_drvdata(dev); + struct pwm_loongson_ddata *ddata = to_pwm_loongson_ddata(chip); + + ret = clk_prepare_enable(ddata->clk); + if (ret) + return ret; + + pwm_loongson_writel(ddata, ddata->lss.ccer_reg, GTIM_CCER); + pwm_loongson_writel(ddata, ddata->lss.ccr_reg[0], GTIM_CCR(0)); + pwm_loongson_writel(ddata, ddata->lss.ccr_reg[1], GTIM_CCR(1)); + pwm_loongson_writel(ddata, ddata->lss.ccr_reg[2], GTIM_CCR(2)); + pwm_loongson_writel(ddata, ddata->lss.ccr_reg[3], GTIM_CCR(3)); + pwm_loongson_writel(ddata, ddata->lss.arr_reg, GTIM_ARR); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(ls_pwm_gtim_pm_ops, ls_pwm_gtim_suspend, + ls_pwm_gtim_resume); + +static struct of_device_id ls_pwm_gtim_id_table[] = { + { .compatible = "loongson,ls2k-pwm-gtim" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, ls_pwm_gtim_id_table); + +static struct platform_driver ls_pwm_gtim_driver = { + .driver = { + .name = "ls-pwm-gtim", + .pm = pm_ptr(&ls_pwm_gtim_pm_ops), + .of_match_table = of_match_ptr(ls_pwm_gtim_id_table), + }, + .probe = ls_pwm_gtim_probe, +}; +module_platform_driver(ls_pwm_gtim_driver); + +MODULE_DESCRIPTION("Loongson Gtimer Pwm Driver"); +MODULE_AUTHOR("Ilikara <3435193369@qq.com>"); +MODULE_LICENSE("GPL");