diff options
Diffstat (limited to 'drivers/pwm')
-rw-r--r-- | drivers/pwm/Kconfig | 14 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pwm/core.c | 32 | ||||
-rw-r--r-- | drivers/pwm/pwm-axi-pwmgen.c | 10 | ||||
-rw-r--r-- | drivers/pwm/pwm-clps711x.c | 4 | ||||
-rw-r--r-- | drivers/pwm/pwm-fsl-ftm.c | 6 | ||||
-rw-r--r-- | drivers/pwm/pwm-gpio.c | 5 | ||||
-rw-r--r-- | drivers/pwm/pwm-lpss.c | 5 | ||||
-rw-r--r-- | drivers/pwm/pwm-lpss.h | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-mediatek.c | 8 | ||||
-rw-r--r-- | drivers/pwm/pwm-pca9685.c | 9 | ||||
-rw-r--r-- | drivers/pwm/pwm-rcar.c | 24 | ||||
-rw-r--r-- | drivers/pwm/pwm-sophgo-sg2042.c | 194 | ||||
-rw-r--r-- | drivers/pwm/pwm-stm32.c | 12 | ||||
-rw-r--r-- | drivers/pwm/pwm-stmpe.c | 25 |
15 files changed, 298 insertions, 52 deletions
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 0915c1e7df16..4731d5b90d7e 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 @@ -636,7 +646,7 @@ config PWM_STM32_LP will be called pwm-stm32-lp. config PWM_STMPE - bool "STMPE expander PWM export" + tristate "STMPE expander PWM export" depends on MFD_STMPE help This enables support for the PWMs found in the STMPE I/O diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 9081e0c0e9e0..539e0def3f82 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 ccd54c089bab..0387bd838487 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -322,7 +322,7 @@ static int __pwm_set_waveform(struct pwm_device *pwm, const struct pwm_ops *ops = chip->ops; char wfhw[WFHWSIZE]; struct pwm_waveform wf_rounded; - int err; + int err, ret_tohw; BUG_ON(WFHWSIZE < ops->sizeof_wfhw); @@ -332,16 +332,16 @@ static int __pwm_set_waveform(struct pwm_device *pwm, if (!pwm_wf_valid(wf)) return -EINVAL; - err = __pwm_round_waveform_tohw(chip, pwm, wf, &wfhw); - if (err) - return err; + ret_tohw = __pwm_round_waveform_tohw(chip, pwm, wf, &wfhw); + if (ret_tohw < 0) + return ret_tohw; if ((IS_ENABLED(CONFIG_PWM_DEBUG) || exact) && wf->period_length_ns) { err = __pwm_round_waveform_fromhw(chip, pwm, &wfhw, &wf_rounded); if (err) return err; - if (IS_ENABLED(CONFIG_PWM_DEBUG) && !pwm_check_rounding(wf, &wf_rounded)) + if (IS_ENABLED(CONFIG_PWM_DEBUG) && ret_tohw == 0 && !pwm_check_rounding(wf, &wf_rounded)) dev_err(&chip->dev, "Wrong rounding: requested %llu/%llu [+%llu], result %llu/%llu [+%llu]\n", wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns); @@ -382,7 +382,8 @@ static int __pwm_set_waveform(struct pwm_device *pwm, wf_rounded.duty_length_ns, wf_rounded.period_length_ns, wf_rounded.duty_offset_ns, wf_set.duty_length_ns, wf_set.period_length_ns, wf_set.duty_offset_ns); } - return 0; + + return ret_tohw; } /** @@ -1000,11 +1001,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 +1733,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-axi-pwmgen.c b/drivers/pwm/pwm-axi-pwmgen.c index 4259a0db9ff4..4337c8f5acf0 100644 --- a/drivers/pwm/pwm-axi-pwmgen.c +++ b/drivers/pwm/pwm-axi-pwmgen.c @@ -75,6 +75,7 @@ static int axi_pwmgen_round_waveform_tohw(struct pwm_chip *chip, { struct axi_pwmgen_waveform *wfhw = _wfhw; struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip); + int ret = 0; if (wf->period_length_ns == 0) { *wfhw = (struct axi_pwmgen_waveform){ @@ -91,12 +92,15 @@ static int axi_pwmgen_round_waveform_tohw(struct pwm_chip *chip, if (wfhw->period_cnt == 0) { /* * The specified period is too short for the hardware. - * Let's round .duty_cycle down to 0 to get a (somewhat) - * valid result. + * So round up .period_cnt to 1 (i.e. the smallest + * possible period). With .duty_cycle and .duty_offset + * being less than or equal to .period, their rounded + * value must be 0. */ wfhw->period_cnt = 1; wfhw->duty_cycle_cnt = 0; wfhw->duty_offset_cnt = 0; + ret = 1; } else { wfhw->duty_cycle_cnt = min_t(u64, mul_u64_u32_div(wf->duty_length_ns, ddata->clk_rate_hz, NSEC_PER_SEC), @@ -111,7 +115,7 @@ static int axi_pwmgen_round_waveform_tohw(struct pwm_chip *chip, pwm->hwpwm, wf->duty_length_ns, wf->period_length_ns, wf->duty_offset_ns, ddata->clk_rate_hz, wfhw->period_cnt, wfhw->duty_cycle_cnt, wfhw->duty_offset_cnt); - return 0; + return ret; } static int axi_pwmgen_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_device *pwm, diff --git a/drivers/pwm/pwm-clps711x.c b/drivers/pwm/pwm-clps711x.c index c950e1dbd2b8..04559a9de718 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-fsl-ftm.c b/drivers/pwm/pwm-fsl-ftm.c index 2510c10ca473..c45a5fca4cbb 100644 --- a/drivers/pwm/pwm-fsl-ftm.c +++ b/drivers/pwm/pwm-fsl-ftm.c @@ -118,6 +118,9 @@ static unsigned int fsl_pwm_ticks_to_ns(struct fsl_pwm_chip *fpc, unsigned long long exval; rate = clk_get_rate(fpc->clk[fpc->period.clk_select]); + if (rate >> fpc->period.clk_ps == 0) + return 0; + exval = ticks; exval *= 1000000000UL; do_div(exval, rate >> fpc->period.clk_ps); @@ -190,6 +193,9 @@ static unsigned int fsl_pwm_calculate_duty(struct fsl_pwm_chip *fpc, unsigned int period = fpc->period.mod_period + 1; unsigned int period_ns = fsl_pwm_ticks_to_ns(fpc, period); + if (!period_ns) + return 0; + duty = (unsigned long long)duty_ns * period; do_div(duty, period_ns); diff --git a/drivers/pwm/pwm-gpio.c b/drivers/pwm/pwm-gpio.c index 9f8884ac7504..5f4edeb394a9 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 3b99feb3bb49..c976ff1c8ed9 100644 --- a/drivers/pwm/pwm-lpss.c +++ b/drivers/pwm/pwm-lpss.c @@ -10,6 +10,8 @@ * Author: Alan Cox <alan@linux.intel.com> */ +#define DEFAULT_SYMBOL_NAMESPACE "PWM_LPSS" + #include <linux/bits.h> #include <linux/delay.h> #include <linux/io.h> @@ -17,10 +19,9 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/pm_runtime.h> +#include <linux/pwm.h> #include <linux/time.h> -#define DEFAULT_SYMBOL_NAMESPACE "PWM_LPSS" - #include "pwm-lpss.h" #define PWM 0x00000000 diff --git a/drivers/pwm/pwm-lpss.h b/drivers/pwm/pwm-lpss.h index b5267ab5193b..60792181401e 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-mediatek.c b/drivers/pwm/pwm-mediatek.c index 01dfa0fab80a..7eaab5831499 100644 --- a/drivers/pwm/pwm-mediatek.c +++ b/drivers/pwm/pwm-mediatek.c @@ -121,21 +121,25 @@ static int pwm_mediatek_config(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_mediatek_chip *pc = to_pwm_mediatek_chip(chip); u32 clkdiv = 0, cnt_period, cnt_duty, reg_width = PWMDWIDTH, reg_thres = PWMTHRES; + unsigned long clk_rate; u64 resolution; int ret; ret = pwm_mediatek_clk_enable(chip, pwm); - if (ret < 0) return ret; + clk_rate = clk_get_rate(pc->clk_pwms[pwm->hwpwm]); + if (!clk_rate) + return -EINVAL; + /* Make sure we use the bus clock and not the 26MHz clock */ if (pc->soc->has_ck_26m_sel) writel(0, pc->regs + PWM_CK_26M_SEL); /* Using resolution in picosecond gets accuracy higher */ resolution = (u64)NSEC_PER_SEC * 1000; - do_div(resolution, clk_get_rate(pc->clk_pwms[pwm->hwpwm])); + do_div(resolution, clk_rate); cnt_period = DIV_ROUND_CLOSEST_ULL((u64)period_ns * 1000, resolution); while (cnt_period > 8191) { diff --git a/drivers/pwm/pwm-pca9685.c b/drivers/pwm/pwm-pca9685.c index 1298b29183e5..5162f3991644 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-rcar.c b/drivers/pwm/pwm-rcar.c index 2261789cc27d..578dbdd2d5a7 100644 --- a/drivers/pwm/pwm-rcar.c +++ b/drivers/pwm/pwm-rcar.c @@ -8,6 +8,7 @@ * - The hardware cannot generate a 0% duty cycle. */ +#include <linux/bitfield.h> #include <linux/clk.h> #include <linux/err.h> #include <linux/io.h> @@ -102,23 +103,24 @@ static void rcar_pwm_set_clock_control(struct rcar_pwm_chip *rp, rcar_pwm_write(rp, value, RCAR_PWMCR); } -static int rcar_pwm_set_counter(struct rcar_pwm_chip *rp, int div, int duty_ns, - int period_ns) +static int rcar_pwm_set_counter(struct rcar_pwm_chip *rp, int div, u64 duty_ns, + u64 period_ns) { - unsigned long long one_cycle, tmp; /* 0.01 nanoseconds */ + unsigned long long tmp; unsigned long clk_rate = clk_get_rate(rp->clk); u32 cyc, ph; - one_cycle = NSEC_PER_SEC * 100ULL << div; - do_div(one_cycle, clk_rate); + /* div <= 24 == RCAR_PWM_MAX_DIVISION, so the shift doesn't overflow. */ + tmp = mul_u64_u64_div_u64(period_ns, clk_rate, (u64)NSEC_PER_SEC << div); + if (tmp > FIELD_MAX(RCAR_PWMCNT_CYC0_MASK)) + tmp = FIELD_MAX(RCAR_PWMCNT_CYC0_MASK); - tmp = period_ns * 100ULL; - do_div(tmp, one_cycle); - cyc = (tmp << RCAR_PWMCNT_CYC0_SHIFT) & RCAR_PWMCNT_CYC0_MASK; + cyc = FIELD_PREP(RCAR_PWMCNT_CYC0_MASK, tmp); - tmp = duty_ns * 100ULL; - do_div(tmp, one_cycle); - ph = tmp & RCAR_PWMCNT_PH0_MASK; + tmp = mul_u64_u64_div_u64(duty_ns, clk_rate, (u64)NSEC_PER_SEC << div); + if (tmp > FIELD_MAX(RCAR_PWMCNT_PH0_MASK)) + tmp = FIELD_MAX(RCAR_PWMCNT_PH0_MASK); + ph = FIELD_PREP(RCAR_PWMCNT_PH0_MASK, tmp); /* Avoid prohibited setting */ if (cyc == 0 || ph == 0) diff --git a/drivers/pwm/pwm-sophgo-sg2042.c b/drivers/pwm/pwm-sophgo-sg2042.c new file mode 100644 index 000000000000..ff4639d849ce --- /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"); diff --git a/drivers/pwm/pwm-stm32.c b/drivers/pwm/pwm-stm32.c index a59de4de18b6..ec2c05c9ee7a 100644 --- a/drivers/pwm/pwm-stm32.c +++ b/drivers/pwm/pwm-stm32.c @@ -103,22 +103,16 @@ static int stm32_pwm_round_waveform_tohw(struct pwm_chip *chip, if (ret) goto out; - /* - * calculate the best value for ARR for the given PSC, refuse if - * the resulting period gets bigger than the requested one. - */ arr = mul_u64_u64_div_u64(wf->period_length_ns, rate, (u64)NSEC_PER_SEC * (wfhw->psc + 1)); if (arr <= wfhw->arr) { /* - * requested period is small than the currently + * requested period is smaller than the currently * configured and unchangable period, report back the smallest - * possible period, i.e. the current state; Initialize - * ccr to anything valid. + * possible period, i.e. the current state and return 1 + * to indicate the wrong rounding direction. */ - wfhw->ccr = 0; ret = 1; - goto out; } } else { diff --git a/drivers/pwm/pwm-stmpe.c b/drivers/pwm/pwm-stmpe.c index bb91062d5f1d..73f12843999a 100644 --- a/drivers/pwm/pwm-stmpe.c +++ b/drivers/pwm/pwm-stmpe.c @@ -326,12 +326,33 @@ static int __init stmpe_pwm_probe(struct platform_device *pdev) return ret; } + platform_set_drvdata(pdev, chip); + return 0; } -static struct platform_driver stmpe_pwm_driver = { +static void __exit stmpe_pwm_remove(struct platform_device *pdev) +{ + struct stmpe *stmpe = dev_get_drvdata(pdev->dev.parent); + struct pwm_chip *chip = platform_get_drvdata(pdev); + + pwmchip_remove(chip); + stmpe_disable(stmpe, STMPE_BLOCK_PWM); +} + +/* + * stmpe_pwm_remove() lives in .exit.text. For drivers registered via + * module_platform_driver_probe() this is ok because they cannot get unbound at + * runtime. So mark the driver struct with __refdata to prevent modpost + * triggering a section mismatch warning. + */ +static struct platform_driver stmpe_pwm_driver __refdata = { .driver = { .name = "stmpe-pwm", }, + .remove = __exit_p(stmpe_pwm_remove), }; -builtin_platform_driver_probe(stmpe_pwm_driver, stmpe_pwm_probe); +module_platform_driver_probe(stmpe_pwm_driver, stmpe_pwm_probe); + +MODULE_DESCRIPTION("STMPE expander PWM"); +MODULE_LICENSE("GPL"); |