diff --git a/Documentation/devicetree/bindings/pwm/hisilicon,pmc-pwm.yaml b/Documentation/devicetree/bindings/pwm/hisilicon,pmc-pwm.yaml new file mode 100644 index 00000000000000..9018f30909135a --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/hisilicon,pmc-pwm.yaml @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/hisilicon,pmc-pwm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: HiSilicon PMC PWM device + +maintainers: + - Yang Xiwen + +allOf: + - $ref: pwm.yaml# + +properties: + compatible: + items: + - enum: + - hisilicon,hi3798mv200-pwm + - const: hisilicon,pmc-pwm + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + "#pwm-cells": + const: 3 + +required: + - compatible + - reg + - clocks + +additionalProperties: false + +examples: + - | + pwm@18 { + compatible = "hisilicon,hi3798mv200-pwm", "hisilicon,pmc-pwm"; + reg = <0x18 0x4>; + clocks = <&clk>; + #pwm-cells = <3>; + }; diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 4b956d661755d6..1d71a56f083ef9 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -237,6 +237,16 @@ config PWM_HIBVT To compile this driver as a module, choose M here: the module will be called pwm-hibvt. +config PWM_HISI_PMC_PWM + tristate "HiSilicon PMC PWM support" + depends on ARCH_HISI || COMPILE_TEST + depends on COMMON_CLK + help + Generic PMC PWM driver for HiSilicon SoCs + + To compile this driver as a module, choose M here: the module + will be called pwm-hisi-pmc-pwm. + config PWM_IMG tristate "Imagination Technologies PWM driver" depends on HAS_IOMEM diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index c5ec9e168ee7c5..95006e1b34aaa8 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_PWM_DWC) += pwm-dwc.o obj-$(CONFIG_PWM_EP93XX) += pwm-ep93xx.o obj-$(CONFIG_PWM_FSL_FTM) += pwm-fsl-ftm.o obj-$(CONFIG_PWM_HIBVT) += pwm-hibvt.o +obj-$(CONFIG_PWM_HISI_PMC_PWM) += pwm-hisi-pmc-pwm.o obj-$(CONFIG_PWM_IMG) += pwm-img.o obj-$(CONFIG_PWM_IMX1) += pwm-imx1.o obj-$(CONFIG_PWM_IMX27) += pwm-imx27.o diff --git a/drivers/pwm/pwn-hisi-pmc.c b/drivers/pwm/pwn-hisi-pmc.c new file mode 100644 index 00000000000000..10a463e261b9e4 --- /dev/null +++ b/drivers/pwm/pwn-hisi-pmc.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Driver for the integrated PWM module in HiSilicon PMC core + * + * Copyright 2024 (c) Yang Xiwen + */ + +#include +#include +#include +#include +#include +#include + +#define HISI_PWM_PERIOD GENMASK(15, 0) +#define HISI_PWM_DUTY GENMASK(31, 16) + +struct hisi_pmc_pwm { + struct pwm_chip chip; + void __iomem *base; + ulong rate; +}; + +#define to_hisi_pmc_pwm(chip) container_of(chip, struct hisi_pmc_pwm, chip) + +static int hisi_pmc_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + const struct pwm_state *state) +{ + struct hisi_pmc_pwm *fpwm; + u64 period_cycles, duty_cycles; + u32 reg; + + /* Turned on by boot loader, and PWMs in PMC are always critical */ + if (!state->enabled || state->polarity == PWM_POLARITY_INVERSED) + return -EINVAL; + + fpwm = to_hisi_pmc_pwm(chip); + + period_cycles = mul_u64_u64_div_u64(fpwm->rate, state->period, NSEC_PER_SEC); + period_cycles = clamp(period_cycles, 0, 0xFFFF); + + duty_cycles = mul_u64_u64_div_u64(fpwm->rate, state->duty_cycle, NSEC_PER_SEC); + duty_cycles = clamp(duty_cycles, 0, 0xFFFF); + + reg = FIELD_PREP(HISI_PWM_PERIOD, period_cycles) | + FIELD_PREP(HISI_PWM_DUTY, duty_cycles); + writel(reg, fpwm->base); + + return 0; +} + +static int hisi_pmc_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct hisi_pmc_pwm *fpwm; + u16 period_cycles, duty_cycles; + u32 reg; + + fpwm = to_hisi_pmc_pwm(chip); + + reg = readl(fpwm->base); + + period_cycles = FIELD_GET(HISI_PWM_PERIOD, reg); + duty_cycles = FIELD_GET(HISI_PWM_DUTY, reg); + + state->enabled = true; + state->polarity = PWM_POLARITY_NORMAL; + + state->duty_cycle = DIV64_U64_ROUND_CLOSEST((u64)duty_cycles * NSEC_PER_SEC, fpwm->rate); + state->period = DIV64_U64_ROUND_CLOSEST((u64)period_cycles * NSEC_PER_SEC, fpwm->rate); + + return 0; +} + +static const struct pwm_ops hisi_pmc_pwm_ops = { + .apply = hisi_pmc_pwm_apply, + .get_state = hisi_pmc_pwm_get_state, +}; + +static int hisi_pmc_pwm_probe(struct platform_device *pdev) +{ + struct hisi_pmc_pwm *fpwm; + struct clk *clk; + int ret; + + fpwm = devm_kzalloc(&pdev->dev, sizeof(*fpwm), GFP_KERNEL); + if (!fpwm) + return -ENOMEM; + + fpwm->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(fpwm->base)) + return PTR_ERR(fpwm->base); + + clk = devm_clk_get_enabled(&pdev->dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(&pdev->dev, PTR_ERR(clk), "unable to get the clock"); + + /* + * Uses the 24MHz system clock on all existing devices, can only + * happen if the device tree is broken + * + * This check is done to prevent an overflow in .apply + */ + fpwm->rate = clk_get_rate(clk); + if (fpwm->rate > NSEC_PER_SEC) + return dev_err_probe(&pdev->dev, -EINVAL, "pwm clock out of range"); + + fpwm->chip.dev = &pdev->dev; + fpwm->chip.npwm = 1; + fpwm->chip.ops = &hisi_pmc_pwm_ops; + + ret = devm_pwmchip_add(&pdev->dev, &fpwm->chip); + if (ret < 0) + return dev_err_probe(&pdev->dev, ret, "unable to add pwm chip"); + + return 0; +} + +static const struct of_device_id hisi_pmc_pwm_of_match[] = { + { .compatible = "hisilicon,pmc-pwm" }, + { .compatible = "hisilicon,hi3798mv200-pwm" }, + { }, +}; +MODULE_DEVICE_TABLE(of, hisi_pmc_pwm_of_match); + +static struct platform_driver hisi_pmc_pwm_driver = { + .probe = hisi_pmc_pwm_probe, + .driver = { + .name = "hisi-pmc-pwm", + .of_match_table = hisi_pmc_pwm_of_match, + }, +}; +module_platform_driver(hisi_pmc_pwm_driver); + +MODULE_DESCRIPTION("HiSilicon SoC PMC PWM driver"); +MODULE_AUTHOR("Yang Xiwen "); +MODULE_LICENSE("GPL");