diff --git a/Documentation/devicetree/bindings/pwm/pwm-nexus-node.yaml b/Documentation/devicetree/bindings/pwm/pwm-nexus-node.yaml new file mode 100644 index 0000000000000000000000000000000000000000..3b40e271fe8d858d250a562bba360906f8320c8c --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/pwm-nexus-node.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/pwm-nexus-node.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: PWM Nexus node properties + +description: > + Platforms can have a standardized connector/expansion slot that exposes PWMs + signals to expansion boards. + + A nexus node allows to remap a phandle list in a consumer node through a + connector node in a generic way. With this remapping, the consumer node needs + to know only about the nexus node. Resources behind the nexus node are + decoupled by the nexus node itself. + +maintainers: + - Herve Codina <herve.codina@bootlin.com> + +select: true + +properties: + '#pwm-cells': true + + pwm-map: + $ref: /schemas/types.yaml#/definitions/uint32-matrix + + pwm-map-mask: + $ref: /schemas/types.yaml#/definitions/uint32-array + + pwm-map-pass-thru: + $ref: /schemas/types.yaml#/definitions/uint32-array + +dependentRequired: + pwm-map: ['#pwm-cells'] + pwm-map-mask: [ pwm-map ] + pwm-map-pass-thru: [ pwm-map ] + +additionalProperties: true + +examples: + - | + pwm1: pwm@100 { + reg = <0x100 0x10>; + #pwm-cells = <3>; + }; + + pwm2: pwm@200 { + reg = <0x200 0x10>; + #pwm-cells = <3>; + }; + + connector: connector { + #pwm-cells = <3>; + pwm-map = <0 0 0 &pwm1 1 0 0>, + <1 0 0 &pwm2 4 0 0>, + <2 0 0 &pwm1 3 0 0>; + pwm-map-mask = <0xffffffff 0x0 0x0>; + pwm-map-pass-thru = <0x0 0xffffffff 0xffffffff>; + }; + + device { + pwms = <&connector 1 57000 0>; + }; diff --git a/Documentation/devicetree/bindings/pwm/sophgo,sg2042-pwm.yaml b/Documentation/devicetree/bindings/pwm/sophgo,sg2042-pwm.yaml new file mode 100644 index 0000000000000000000000000000000000000000..bbb6326d47d76fcd26d6bae5b235063d71a2c434 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/sophgo,sg2042-pwm.yaml @@ -0,0 +1,58 @@ +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/pwm/sophgo,sg2042-pwm.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Sophgo SG2042 PWM controller + +maintainers: + - Chen Wang <unicorn_wang@outlook.com> + +description: + This controller contains 4 channels which can generate PWM waveforms. + +allOf: + - $ref: pwm.yaml# + +properties: + compatible: + const: sophgo,sg2042-pwm + + reg: + maxItems: 1 + + clocks: + maxItems: 1 + + clock-names: + items: + - const: apb + + resets: + maxItems: 1 + + "#pwm-cells": + const: 3 + +required: + - compatible + - reg + - clocks + - clock-names + - resets + +unevaluatedProperties: false + +examples: + - | + #include <dt-bindings/reset/sophgo,sg2042-reset.h> + + pwm@7f006000 { + compatible = "sophgo,sg2042-pwm"; + reg = <0x7f006000 0x1000>; + #pwm-cells = <3>; + clocks = <&clock 67>; + clock-names = "apb"; + resets = <&rstgen RST_PWM>; + }; diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 0915c1e7df16d451e987dcc5f10e0b57edc32ee1..63beb0010e3e491efaf653d24187037ec0147ee4 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -567,7 +567,7 @@ config PWM_SIFIVE tristate "SiFive PWM support" depends on OF depends on COMMON_CLK && HAS_IOMEM - depends on RISCV || COMPILE_TEST + depends on ARCH_SIFIVE || COMPILE_TEST help Generic PWM framework driver for SiFive SoCs. @@ -584,6 +584,16 @@ config PWM_SL28CPLD To compile this driver as a module, choose M here: the module will be called pwm-sl28cpld. +config PWM_SOPHGO_SG2042 + tristate "Sophgo SG2042 PWM support" + depends on ARCH_SOPHGO || COMPILE_TEST + help + PWM driver for the PWM controller on Sophgo SG2042 SoC. The PWM + controller supports outputing 4 channels of PWM waveforms. + + To compile this driver as a module, choose M here: the module + will be called pwm_sophgo_sg2042. + config PWM_SPEAR tristate "STMicroelectronics SPEAr PWM support" depends on PLAT_SPEAR || COMPILE_TEST diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 9081e0c0e9e09713fe05479c257eebe5f02b91e9..539e0def3f82fcb866ab83a0346a15f7efdd7127 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -53,6 +53,7 @@ obj-$(CONFIG_PWM_RZ_MTU3) += pwm-rz-mtu3.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o obj-$(CONFIG_PWM_SL28CPLD) += pwm-sl28cpld.o +obj-$(CONFIG_PWM_SOPHGO_SG2042) += pwm-sophgo-sg2042.o obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o obj-$(CONFIG_PWM_SPRD) += pwm-sprd.o obj-$(CONFIG_PWM_STI) += pwm-sti.o diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index ccd54c089bab8b975f1fa480b9956f0df9ce7c14..a40c511e0096526c1023217558c698fddc99f3d1 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -1000,11 +1000,27 @@ of_pwm_xlate_with_flags(struct pwm_chip *chip, const struct of_phandle_args *arg } EXPORT_SYMBOL_GPL(of_pwm_xlate_with_flags); +/* + * This callback is used for PXA PWM chips that only have a single PWM line. + * For such chips you could argue that passing the line number (i.e. the first + * parameter in the common case) is useless as it's always zero. So compared to + * the default xlate function of_pwm_xlate_with_flags() the first parameter is + * the default period and the second are flags. + * + * Note that if #pwm-cells = <3>, the semantic is the same as for + * of_pwm_xlate_with_flags() to allow converting the affected driver to + * #pwm-cells = <3> without breaking the legacy binding. + * + * Don't use for new drivers. + */ struct pwm_device * of_pwm_single_xlate(struct pwm_chip *chip, const struct of_phandle_args *args) { struct pwm_device *pwm; + if (args->args_count >= 3) + return of_pwm_xlate_with_flags(chip, args); + pwm = pwm_request_from_chip(chip, 0, NULL); if (IS_ERR(pwm)) return pwm; @@ -1716,8 +1732,7 @@ static struct pwm_device *of_pwm_get(struct device *dev, struct device_node *np, return ERR_PTR(index); } - err = of_parse_phandle_with_args(np, "pwms", "#pwm-cells", index, - &args); + err = of_parse_phandle_with_args_map(np, "pwms", "pwm", index, &args); if (err) { pr_err("%s(): can't parse \"pwms\" property\n", __func__); return ERR_PTR(err); diff --git a/drivers/pwm/pwm-clps711x.c b/drivers/pwm/pwm-clps711x.c index c950e1dbd2b8e179f9412d1e39a49af6f8bd431e..04559a9de718bc38ea8870d34909f2cf3a2248e5 100644 --- a/drivers/pwm/pwm-clps711x.c +++ b/drivers/pwm/pwm-clps711x.c @@ -98,7 +98,7 @@ static int clps711x_pwm_probe(struct platform_device *pdev) return devm_pwmchip_add(&pdev->dev, chip); } -static const struct of_device_id __maybe_unused clps711x_pwm_dt_ids[] = { +static const struct of_device_id clps711x_pwm_dt_ids[] = { { .compatible = "cirrus,ep7209-pwm", }, { } }; @@ -107,7 +107,7 @@ MODULE_DEVICE_TABLE(of, clps711x_pwm_dt_ids); static struct platform_driver clps711x_pwm_driver = { .driver = { .name = "clps711x-pwm", - .of_match_table = of_match_ptr(clps711x_pwm_dt_ids), + .of_match_table = clps711x_pwm_dt_ids, }, .probe = clps711x_pwm_probe, }; diff --git a/drivers/pwm/pwm-gpio.c b/drivers/pwm/pwm-gpio.c index 9f8884ac75047f05298146940a193481d585a3c6..5f4edeb394a954275d4bcbe36dc4256b424a17dd 100644 --- a/drivers/pwm/pwm-gpio.c +++ b/drivers/pwm/pwm-gpio.c @@ -207,13 +207,12 @@ static int pwm_gpio_probe(struct platform_device *pdev) chip->ops = &pwm_gpio_ops; chip->atomic = true; - hrtimer_init(&gpwm->gpio_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + hrtimer_setup(&gpwm->gpio_timer, pwm_gpio_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + ret = devm_add_action_or_reset(dev, pwm_gpio_disable_hrtimer, gpwm); if (ret) return ret; - gpwm->gpio_timer.function = pwm_gpio_timer; - ret = pwmchip_add(chip); if (ret < 0) return dev_err_probe(dev, ret, "could not add pwmchip\n"); diff --git a/drivers/pwm/pwm-lpss.c b/drivers/pwm/pwm-lpss.c index e3c72ed7fff1dcb9667dc4691aebaaa1881e2dd0..c976ff1c8ed9f487fdd3b17de7443915ff6601b4 100644 --- a/drivers/pwm/pwm-lpss.c +++ b/drivers/pwm/pwm-lpss.c @@ -19,6 +19,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/pm_runtime.h> +#include <linux/pwm.h> #include <linux/time.h> #include "pwm-lpss.h" diff --git a/drivers/pwm/pwm-lpss.h b/drivers/pwm/pwm-lpss.h index b5267ab5193b6e361a6b64b3c40657810827d192..60792181401ee4115ef8d3f28cfd7a001e5427ba 100644 --- a/drivers/pwm/pwm-lpss.h +++ b/drivers/pwm/pwm-lpss.h @@ -10,7 +10,6 @@ #ifndef __PWM_LPSS_H #define __PWM_LPSS_H -#include <linux/pwm.h> #include <linux/types.h> #include <linux/platform_data/x86/pwm-lpss.h> diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index 1298b29183e55412ec3b5fb01f3d9f695101864e..5162f39916443d582d49eaa69e65f524bf9bd0c8 100644 --- a/drivers/pwm/pwm-pca9685.c +++ b/drivers/pwm/pwm-pca9685.c @@ -8,7 +8,6 @@ * based on the pwm-twl-led.c driver */ -#include <linux/acpi.h> #include <linux/gpio/driver.h> #include <linux/i2c.h> #include <linux/module.h> @@ -639,21 +638,17 @@ static const struct i2c_device_id pca9685_id[] = { }; MODULE_DEVICE_TABLE(i2c, pca9685_id); -#ifdef CONFIG_ACPI static const struct acpi_device_id pca9685_acpi_ids[] = { { "INT3492", 0 }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(acpi, pca9685_acpi_ids); -#endif -#ifdef CONFIG_OF static const struct of_device_id pca9685_dt_ids[] = { { .compatible = "nxp,pca9685-pwm", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, pca9685_dt_ids); -#endif static const struct dev_pm_ops pca9685_pwm_pm = { SET_RUNTIME_PM_OPS(pca9685_pwm_runtime_suspend, @@ -663,8 +658,8 @@ static const struct dev_pm_ops pca9685_pwm_pm = { static struct i2c_driver pca9685_i2c_driver = { .driver = { .name = "pca9685-pwm", - .acpi_match_table = ACPI_PTR(pca9685_acpi_ids), - .of_match_table = of_match_ptr(pca9685_dt_ids), + .acpi_match_table = pca9685_acpi_ids, + .of_match_table = pca9685_dt_ids, .pm = &pca9685_pwm_pm, }, .probe = pca9685_pwm_probe, diff --git a/drivers/pwm/pwm-sophgo-sg2042.c b/drivers/pwm/pwm-sophgo-sg2042.c new file mode 100644 index 0000000000000000000000000000000000000000..ff4639d849ce1aa26371998420e6844cd99fd7ba --- /dev/null +++ b/drivers/pwm/pwm-sophgo-sg2042.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Sophgo SG2042 PWM Controller Driver + * + * Copyright (C) 2024 Sophgo Technology Inc. + * Copyright (C) 2024 Chen Wang <unicorn_wang@outlook.com> + * + * Limitations: + * - After reset, the output of the PWM channel is always high. + * The value of HLPERIOD/PERIOD is 0. + * - When HLPERIOD or PERIOD is reconfigured, PWM will start to + * output waveforms with the new configuration after completing + * the running period. + * - When PERIOD and HLPERIOD is set to 0, the PWM wave output will + * be stopped and the output is pulled to high. + * See the datasheet [1] for more details. + * [1]:https://github.com/sophgo/sophgo-doc/tree/main/SG2042/TRM + */ + +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#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 SG2042_PWM_CHANNELNUM 4 + +/** + * struct sg2042_pwm_ddata - private driver data + * @base: base address of mapped PWM registers + * @clk_rate_hz: rate of base clock in HZ + */ +struct sg2042_pwm_ddata { + void __iomem *base; + unsigned long clk_rate_hz; +}; + +/* + * period_ticks: PERIOD + * hlperiod_ticks: HLPERIOD + */ +static void pwm_sg2042_config(struct sg2042_pwm_ddata *ddata, unsigned int chan, + u32 period_ticks, u32 hlperiod_ticks) +{ + void __iomem *base = ddata->base; + + writel(period_ticks, base + SG2042_PWM_PERIOD(chan)); + writel(hlperiod_ticks, base + SG2042_PWM_HLPERIOD(chan)); +} + +static int pwm_sg2042_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->polarity == PWM_POLARITY_INVERSED) + return -EINVAL; + + if (!state->enabled) { + pwm_sg2042_config(ddata, pwm->hwpwm, 0, 0); + return 0; + } + + /* + * 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); + + return 0; +} + +static int pwm_sg2042_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct sg2042_pwm_ddata *ddata = pwmchip_get_drvdata(chip); + unsigned int chan = pwm->hwpwm; + u32 hlperiod_ticks; + u32 period_ticks; + + period_ticks = readl(ddata->base + SG2042_PWM_PERIOD(chan)); + hlperiod_ticks = readl(ddata->base + SG2042_PWM_HLPERIOD(chan)); + + if (!period_ticks) { + state->enabled = false; + return 0; + } + + if (hlperiod_ticks > period_ticks) + hlperiod_ticks = period_ticks; + + state->enabled = true; + state->period = DIV_ROUND_UP_ULL((u64)period_ticks * NSEC_PER_SEC, ddata->clk_rate_hz); + state->duty_cycle = DIV_ROUND_UP_ULL((u64)hlperiod_ticks * NSEC_PER_SEC, ddata->clk_rate_hz); + state->polarity = PWM_POLARITY_NORMAL; + + return 0; +} + +static const struct pwm_ops pwm_sg2042_ops = { + .apply = pwm_sg2042_apply, + .get_state = pwm_sg2042_get_state, +}; + +static const struct of_device_id sg2042_pwm_ids[] = { + { .compatible = "sophgo,sg2042-pwm" }, + { } +}; +MODULE_DEVICE_TABLE(of, sg2042_pwm_ids); + +static int pwm_sg2042_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct sg2042_pwm_ddata *ddata; + struct reset_control *rst; + struct pwm_chip *chip; + struct clk *clk; + int ret; + + chip = devm_pwmchip_alloc(dev, SG2042_PWM_CHANNELNUM, sizeof(*ddata)); + if (IS_ERR(chip)) + return PTR_ERR(chip); + ddata = pwmchip_get_drvdata(chip); + + ddata->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ddata->base)) + return PTR_ERR(ddata->base); + + clk = devm_clk_get_enabled(dev, "apb"); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "Failed to get base clk\n"); + + ret = devm_clk_rate_exclusive_get(dev, clk); + if (ret) + return dev_err_probe(dev, ret, "Failed to get exclusive rate\n"); + + ddata->clk_rate_hz = clk_get_rate(clk); + /* period = PERIOD * NSEC_PER_SEC / clk_rate_hz */ + if (!ddata->clk_rate_hz || ddata->clk_rate_hz > NSEC_PER_SEC) + return dev_err_probe(dev, -EINVAL, + "Invalid clock rate: %lu\n", ddata->clk_rate_hz); + + rst = devm_reset_control_get_optional_shared_deasserted(dev, NULL); + 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; + + ret = devm_pwmchip_add(dev, chip); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to register PWM chip\n"); + + return 0; +} + +static struct platform_driver pwm_sg2042_driver = { + .driver = { + .name = "sg2042-pwm", + .of_match_table = sg2042_pwm_ids, + }, + .probe = pwm_sg2042_probe, +}; +module_platform_driver(pwm_sg2042_driver); + +MODULE_AUTHOR("Chen Wang"); +MODULE_DESCRIPTION("Sophgo SG2042 PWM driver"); +MODULE_LICENSE("GPL");