diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/leds/leds-pwm.c | 45 | ||||
-rw-r--r-- | drivers/pwm/Kconfig | 11 | ||||
-rw-r--r-- | drivers/pwm/Makefile | 1 | ||||
-rw-r--r-- | drivers/pwm/core.c | 172 | ||||
-rw-r--r-- | drivers/pwm/pwm-atmel-hlcdc.c | 1 | ||||
-rw-r--r-- | drivers/pwm/pwm-bcm2835.c | 8 | ||||
-rw-r--r-- | drivers/pwm/pwm-fsl-ftm.c | 383 | ||||
-rw-r--r-- | drivers/pwm/pwm-jz4740.c | 49 | ||||
-rw-r--r-- | drivers/pwm/pwm-meson.c | 386 | ||||
-rw-r--r-- | drivers/pwm/pwm-rcar.c | 39 | ||||
-rw-r--r-- | drivers/pwm/pwm-sifive.c | 339 | ||||
-rw-r--r-- | drivers/pwm/pwm-stm32-lp.c | 25 | ||||
-rw-r--r-- | drivers/pwm/pwm-stm32.c | 2 | ||||
-rw-r--r-- | drivers/pwm/sysfs.c | 102 |
14 files changed, 1090 insertions, 473 deletions
diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c index 9328193189ba..48d068f80f11 100644 --- a/drivers/leds/leds-pwm.c +++ b/drivers/leds/leds-pwm.c @@ -72,7 +72,7 @@ static inline size_t sizeof_pwm_leds_priv(int num_leds) } static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv, - struct led_pwm *led, struct device_node *child) + struct led_pwm *led, struct fwnode_handle *fwnode) { struct led_pwm_data *led_data = &priv->leds[priv->num_leds]; struct pwm_args pargs; @@ -85,8 +85,8 @@ static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv, led_data->cdev.max_brightness = led->max_brightness; led_data->cdev.flags = LED_CORE_SUSPENDRESUME; - if (child) - led_data->pwm = devm_of_pwm_get(dev, child, NULL); + if (fwnode) + led_data->pwm = devm_fwnode_pwm_get(dev, fwnode, NULL); else led_data->pwm = devm_pwm_get(dev, led->name); if (IS_ERR(led_data->pwm)) { @@ -111,7 +111,8 @@ static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv, if (!led_data->period && (led->pwm_period_ns > 0)) led_data->period = led->pwm_period_ns; - ret = devm_of_led_classdev_register(dev, child, &led_data->cdev); + ret = devm_of_led_classdev_register(dev, to_of_node(fwnode), + &led_data->cdev); if (ret == 0) { priv->num_leds++; led_pwm_set(&led_data->cdev, led_data->cdev.brightness); @@ -123,27 +124,35 @@ static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv, return ret; } -static int led_pwm_create_of(struct device *dev, struct led_pwm_priv *priv) +static int led_pwm_create_fwnode(struct device *dev, struct led_pwm_priv *priv) { - struct device_node *child; + struct fwnode_handle *fwnode; struct led_pwm led; int ret = 0; memset(&led, 0, sizeof(led)); - for_each_child_of_node(dev->of_node, child) { - led.name = of_get_property(child, "label", NULL) ? : - child->name; + device_for_each_child_node(dev, fwnode) { + ret = fwnode_property_read_string(fwnode, "label", &led.name); + if (ret && is_of_node(fwnode)) + led.name = to_of_node(fwnode)->name; - led.default_trigger = of_get_property(child, - "linux,default-trigger", NULL); - led.active_low = of_property_read_bool(child, "active-low"); - of_property_read_u32(child, "max-brightness", - &led.max_brightness); + if (!led.name) { + fwnode_handle_put(fwnode); + return -EINVAL; + } + + fwnode_property_read_string(fwnode, "linux,default-trigger", + &led.default_trigger); + + led.active_low = fwnode_property_read_bool(fwnode, + "active-low"); + fwnode_property_read_u32(fwnode, "max-brightness", + &led.max_brightness); - ret = led_pwm_add(dev, priv, &led, child); + ret = led_pwm_add(dev, priv, &led, fwnode); if (ret) { - of_node_put(child); + fwnode_handle_put(fwnode); break; } } @@ -161,7 +170,7 @@ static int led_pwm_probe(struct platform_device *pdev) if (pdata) count = pdata->num_leds; else - count = of_get_child_count(pdev->dev.of_node); + count = device_get_child_node_count(&pdev->dev); if (!count) return -EINVAL; @@ -179,7 +188,7 @@ static int led_pwm_probe(struct platform_device *pdev) break; } } else { - ret = led_pwm_create_of(&pdev->dev, priv); + ret = led_pwm_create_fwnode(&pdev->dev, priv); } if (ret) diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index dff5a93f7daa..a7e57516959e 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -401,6 +401,17 @@ config PWM_SAMSUNG To compile this driver as a module, choose M here: the module will be called pwm-samsung. +config PWM_SIFIVE + tristate "SiFive PWM support" + depends on OF + depends on COMMON_CLK + depends on RISCV || COMPILE_TEST + help + Generic PWM framework driver for SiFive SoCs. + + To compile this driver as a module, choose M here: the module + will be called pwm-sifive. + config PWM_SPEAR tristate "STMicroelectronics SPEAr PWM support" depends on PLAT_SPEAR diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index c368599d36c0..76b555b51887 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -39,6 +39,7 @@ obj-$(CONFIG_PWM_RCAR) += pwm-rcar.o obj-$(CONFIG_PWM_RENESAS_TPU) += pwm-renesas-tpu.o obj-$(CONFIG_PWM_ROCKCHIP) += pwm-rockchip.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o +obj-$(CONFIG_PWM_SIFIVE) += pwm-sifive.o obj-$(CONFIG_PWM_SPEAR) += pwm-spear.o obj-$(CONFIG_PWM_STI) += pwm-sti.o obj-$(CONFIG_PWM_STM32) += pwm-stm32.o diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 275b5f399a1a..c3ab07ab31a9 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -6,6 +6,7 @@ * Copyright (C) 2011-2012 Avionic Design GmbH */ +#include <linux/acpi.h> #include <linux/module.h> #include <linux/pwm.h> #include <linux/radix-tree.h> @@ -626,8 +627,35 @@ static struct pwm_chip *of_node_to_pwmchip(struct device_node *np) return ERR_PTR(-EPROBE_DEFER); } +static struct device_link *pwm_device_link_add(struct device *dev, + struct pwm_device *pwm) +{ + struct device_link *dl; + + if (!dev) { + /* + * No device for the PWM consumer has been provided. It may + * impact the PM sequence ordering: the PWM supplier may get + * suspended before the consumer. + */ + dev_warn(pwm->chip->dev, + "No consumer device specified to create a link to\n"); + return NULL; + } + + dl = device_link_add(dev, pwm->chip->dev, DL_FLAG_AUTOREMOVE_CONSUMER); + if (!dl) { + dev_err(dev, "failed to create device link to %s\n", + dev_name(pwm->chip->dev)); + return ERR_PTR(-EINVAL); + } + + return dl; +} + /** * of_pwm_get() - request a PWM via the PWM framework + * @dev: device for PWM consumer * @np: device node to get the PWM from * @con_id: consumer name * @@ -645,10 +673,12 @@ static struct pwm_chip *of_node_to_pwmchip(struct device_node *np) * Returns: A pointer to the requested PWM device or an ERR_PTR()-encoded * error code on failure. */ -struct pwm_device *of_pwm_get(struct device_node *np, const char *con_id) +struct pwm_device *of_pwm_get(struct device *dev, struct device_node *np, + const char *con_id) { struct pwm_device *pwm = NULL; struct of_phandle_args args; + struct device_link *dl; struct pwm_chip *pc; int index = 0; int err; @@ -679,6 +709,14 @@ struct pwm_device *of_pwm_get(struct device_node *np, const char *con_id) if (IS_ERR(pwm)) goto put; + dl = pwm_device_link_add(dev, pwm); + if (IS_ERR(dl)) { + /* of_xlate ended up calling pwm_request_from_chip() */ + pwm_free(pwm); + pwm = ERR_CAST(dl); + goto put; + } + /* * If a consumer name was not given, try to look it up from the * "pwm-names" property if it exists. Otherwise use the name of @@ -700,6 +738,85 @@ put: } EXPORT_SYMBOL_GPL(of_pwm_get); +#if IS_ENABLED(CONFIG_ACPI) +static struct pwm_chip *device_to_pwmchip(struct device *dev) +{ + struct pwm_chip *chip; + + mutex_lock(&pwm_lock); + + list_for_each_entry(chip, &pwm_chips, list) { + struct acpi_device *adev = ACPI_COMPANION(chip->dev); + + if ((chip->dev == dev) || (adev && &adev->dev == dev)) { + mutex_unlock(&pwm_lock); + return chip; + } + } + + mutex_unlock(&pwm_lock); + + return ERR_PTR(-EPROBE_DEFER); +} +#endif + +/** + * acpi_pwm_get() - request a PWM via parsing "pwms" property in ACPI + * @fwnode: firmware node to get the "pwm" property from + * + * Returns the PWM device parsed from the fwnode and index specified in the + * "pwms" property or a negative error-code on failure. + * Values parsed from the device tree are stored in the returned PWM device + * object. + * + * This is analogous to of_pwm_get() except con_id is not yet supported. + * ACPI entries must look like + * Package () {"pwms", Package () + * { <PWM device reference>, <PWM index>, <PWM period> [, <PWM flags>]}} + * + * Returns: A pointer to the requested PWM device or an ERR_PTR()-encoded + * error code on failure. + */ +static struct pwm_device *acpi_pwm_get(struct fwnode_handle *fwnode) +{ + struct pwm_device *pwm = ERR_PTR(-ENODEV); +#if IS_ENABLED(CONFIG_ACPI) + struct fwnode_reference_args args; + struct acpi_device *acpi; + struct pwm_chip *chip; + int ret; + + memset(&args, 0, sizeof(args)); + + ret = __acpi_node_get_property_reference(fwnode, "pwms", 0, 3, &args); + if (ret < 0) + return ERR_PTR(ret); + + acpi = to_acpi_device_node(args.fwnode); + if (!acpi) + return ERR_PTR(-EINVAL); + + if (args.nargs < 2) + return ERR_PTR(-EPROTO); + + chip = device_to_pwmchip(&acpi->dev); + if (IS_ERR(chip)) + return ERR_CAST(chip); + + pwm = pwm_request_from_chip(chip, args.args[0], NULL); + if (IS_ERR(pwm)) + return pwm; + + pwm->args.period = args.args[1]; + pwm->args.polarity = PWM_POLARITY_NORMAL; + + if (args.nargs > 2 && args.args[2] & PWM_POLARITY_INVERTED) + pwm->args.polarity = PWM_POLARITY_INVERSED; +#endif + + return pwm; +} + /** * pwm_add_table() - register PWM device consumers * @table: array of consumers to register @@ -754,6 +871,7 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id) const char *dev_id = dev ? dev_name(dev) : NULL; struct pwm_device *pwm; struct pwm_chip *chip; + struct device_link *dl; unsigned int best = 0; struct pwm_lookup *p, *chosen = NULL; unsigned int match; @@ -761,7 +879,11 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id) /* look up via DT first */ if (IS_ENABLED(CONFIG_OF) && dev && dev->of_node) - return of_pwm_get(dev->of_node, con_id); + return of_pwm_get(dev, dev->of_node, con_id); + + /* then lookup via ACPI */ + if (dev && is_acpi_node(dev->fwnode)) + return acpi_pwm_get(dev->fwnode); /* * We look up the provider in the static table typically provided by @@ -838,6 +960,12 @@ struct pwm_device *pwm_get(struct device *dev, const char *con_id) if (IS_ERR(pwm)) return pwm; + dl = pwm_device_link_add(dev, pwm); + if (IS_ERR(dl)) { + pwm_free(pwm); + return ERR_CAST(dl); + } + pwm->args.period = chosen->period; pwm->args.polarity = chosen->polarity; @@ -930,7 +1058,7 @@ struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np, if (!ptr) return ERR_PTR(-ENOMEM); - pwm = of_pwm_get(np, con_id); + pwm = of_pwm_get(dev, np, con_id); if (!IS_ERR(pwm)) { *ptr = pwm; devres_add(dev, ptr); @@ -942,6 +1070,44 @@ struct pwm_device *devm_of_pwm_get(struct device *dev, struct device_node *np, } EXPORT_SYMBOL_GPL(devm_of_pwm_get); +/** + * devm_fwnode_pwm_get() - request a resource managed PWM from firmware node + * @dev: device for PWM consumer + * @fwnode: firmware node to get the PWM from + * @con_id: consumer name + * + * Returns the PWM device parsed from the firmware node. See of_pwm_get() and + * acpi_pwm_get() for a detailed description. + * + * Returns: A pointer to the requested PWM device or an ERR_PTR()-encoded + * error code on failure. + */ +struct pwm_device *devm_fwnode_pwm_get(struct device *dev, + struct fwnode_handle *fwnode, + const char *con_id) +{ + struct pwm_device **ptr, *pwm = ERR_PTR(-ENODEV); + + ptr = devres_alloc(devm_pwm_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + if (is_of_node(fwnode)) + pwm = of_pwm_get(dev, to_of_node(fwnode), con_id); + else if (is_acpi_node(fwnode)) + pwm = acpi_pwm_get(fwnode); + + if (!IS_ERR(pwm)) { + *ptr = pwm; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return pwm; +} +EXPORT_SYMBOL_GPL(devm_fwnode_pwm_get); + static int devm_pwm_match(struct device *dev, void *res, void *data) { struct pwm_device **p = res; diff --git a/drivers/pwm/pwm-atmel-hlcdc.c b/drivers/pwm/pwm-atmel-hlcdc.c index 7186db85b15f..d13a83f430ac 100644 --- a/drivers/pwm/pwm-atmel-hlcdc.c +++ b/drivers/pwm/pwm-atmel-hlcdc.c @@ -235,6 +235,7 @@ static const struct of_device_id atmel_hlcdc_dt_ids[] = { .compatible = "atmel,sama5d4-hlcdc", .data = &atmel_hlcdc_pwm_sama5d3_errata, }, + { .compatible = "microchip,sam9x60-hlcdc", }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, atmel_hlcdc_dt_ids); diff --git a/drivers/pwm/pwm-bcm2835.c b/drivers/pwm/pwm-bcm2835.c index 5652f461d994..f6fe0b922e1e 100644 --- a/drivers/pwm/pwm-bcm2835.c +++ b/drivers/pwm/pwm-bcm2835.c @@ -70,7 +70,7 @@ static int bcm2835_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, return -EINVAL; } - scaler = NSEC_PER_SEC / rate; + scaler = DIV_ROUND_CLOSEST(NSEC_PER_SEC, rate); if (period_ns <= MIN_PERIOD) { dev_err(pc->dev, "period %d not supported, minimum %d\n", @@ -78,8 +78,10 @@ static int bcm2835_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, return -EINVAL; } - writel(duty_ns / scaler, pc->base + DUTY(pwm->hwpwm)); - writel(period_ns / scaler, pc->base + PERIOD(pwm->hwpwm)); + writel(DIV_ROUND_CLOSEST(duty_ns, scaler), + pc->base + DUTY(pwm->hwpwm)); + writel(DIV_ROUND_CLOSEST(period_ns, scaler), + pc->base + PERIOD(pwm->hwpwm)); return 0; } diff --git a/drivers/pwm/pwm-fsl-ftm.c b/drivers/pwm/pwm-fsl-ftm.c index a39b48839df7..9d31a217111d 100644 --- a/drivers/pwm/pwm-fsl-ftm.c +++ b/drivers/pwm/pwm-fsl-ftm.c @@ -34,17 +34,19 @@ struct fsl_ftm_soc { bool has_enable_bits; }; +struct fsl_pwm_periodcfg { + enum fsl_pwm_clk clk_select; + unsigned int clk_ps; + unsigned int mod_period; +}; + struct fsl_pwm_chip { struct pwm_chip chip; - struct mutex lock; - - unsigned int cnt_select; - unsigned int clk_ps; - struct regmap *regmap; - int period_ns; + /* This value is valid iff a pwm is running */ + struct fsl_pwm_periodcfg period; struct clk *ipg_clk; struct clk *clk[FSL_PWM_CLK_MAX]; @@ -57,6 +59,33 @@ static inline struct fsl_pwm_chip *to_fsl_chip(struct pwm_chip *chip) return container_of(chip, struct fsl_pwm_chip, chip); } +static void ftm_clear_write_protection(struct fsl_pwm_chip *fpc) +{ + u32 val; + + regmap_read(fpc->regmap, FTM_FMS, &val); + if (val & FTM_FMS_WPEN) + regmap_update_bits(fpc->regmap, FTM_MODE, FTM_MODE_WPDIS, + FTM_MODE_WPDIS); +} + +static void ftm_set_write_protection(struct fsl_pwm_chip *fpc) +{ + regmap_update_bits(fpc->regmap, FTM_FMS, FTM_FMS_WPEN, FTM_FMS_WPEN); +} + +static bool fsl_pwm_periodcfg_are_equal(const struct fsl_pwm_periodcfg *a, + const struct fsl_pwm_periodcfg *b) +{ + if (a->clk_select != b->clk_select) + return false; + if (a->clk_ps != b->clk_ps) + return false; + if (a->mod_period != b->mod_period) + return false; + return true; +} + static int fsl_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) { int ret; @@ -87,89 +116,58 @@ static void fsl_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) clk_disable_unprepare(fpc->ipg_clk); } -static int fsl_pwm_calculate_default_ps(struct fsl_pwm_chip *fpc, - enum fsl_pwm_clk index) +static unsigned int fsl_pwm_ticks_to_ns(struct fsl_pwm_chip *fpc, + unsigned int ticks) { - unsigned long sys_rate, cnt_rate; - unsigned long long ratio; - - sys_rate = clk_get_rate(fpc->clk[FSL_PWM_CLK_SYS]); - if (!sys_rate) - return -EINVAL; - - cnt_rate = clk_get_rate(fpc->clk[fpc->cnt_select]); - if (!cnt_rate) - return -EINVAL; - - switch (index) { - case FSL_PWM_CLK_SYS: - fpc->clk_ps = 1; - break; - case FSL_PWM_CLK_FIX: - ratio = 2 * cnt_rate - 1; - do_div(ratio, sys_rate); - fpc->clk_ps = ratio; - break; - case FSL_PWM_CLK_EXT: - ratio = 4 * cnt_rate - 1; - do_div(ratio, sys_rate); - fpc->clk_ps = ratio; - break; - default: - return -EINVAL; - } - - return 0; + unsigned long rate; + unsigned long long exval; + + rate = clk_get_rate(fpc->clk[fpc->period.clk_select]); + exval = ticks; + exval *= 1000000000UL; + do_div(exval, rate >> fpc->period.clk_ps); + return exval; } -static unsigned long fsl_pwm_calculate_cycles(struct fsl_pwm_chip *fpc, - unsigned long period_ns) +static bool fsl_pwm_calculate_period_clk(struct fsl_pwm_chip *fpc, + unsigned int period_ns, + enum fsl_pwm_clk index, + struct fsl_pwm_periodcfg *periodcfg + ) { - unsigned long long c, c0; + unsigned long long c; + unsigned int ps; - c = clk_get_rate(fpc->clk[fpc->cnt_select]); + c = clk_get_rate(fpc->clk[index]); c = c * period_ns; do_div(c, 1000000000UL); - do { - c0 = c; - do_div(c0, (1 << fpc->clk_ps)); - if (c0 <= 0xFFFF) - return (unsigned long)c0; - } while (++fpc->clk_ps < 8); - - return 0; -} - -static unsigned long fsl_pwm_calculate_period_cycles(struct fsl_pwm_chip *fpc, - unsigned long period_ns, - enum fsl_pwm_clk index) -{ - int ret; + if (c == 0) + return false; - ret = fsl_pwm_calculate_default_ps(fpc, index); - if (ret) { - dev_err(fpc->chip.dev, - "failed to calculate default prescaler: %d\n", - ret); - return 0; + for (ps = 0; ps < 8 ; ++ps, c >>= 1) { + if (c <= 0x10000) { + periodcfg->clk_select = index; + periodcfg->clk_ps = ps; + periodcfg->mod_period = c - 1; + return true; + } } - - return fsl_pwm_calculate_cycles(fpc, period_ns); + return false; } -static unsigned long fsl_pwm_calculate_period(struct fsl_pwm_chip *fpc, - unsigned long period_ns) +static bool fsl_pwm_calculate_period(struct fsl_pwm_chip *fpc, + unsigned int period_ns, + struct fsl_pwm_periodcfg *periodcfg) { enum fsl_pwm_clk m0, m1; - unsigned long fix_rate, ext_rate, cycles; + unsigned long fix_rate, ext_rate; + bool ret; - cycles = fsl_pwm_calculate_period_cycles(fpc, period_ns, - FSL_PWM_CLK_SYS); - if (cycles) { - fpc->cnt_select = FSL_PWM_CLK_SYS; - return cycles; - } + ret = fsl_pwm_calculate_period_clk(fpc, period_ns, FSL_PWM_CLK_SYS, + periodcfg); + if (ret) + return true; fix_rate = clk_get_rate(fpc->clk[FSL_PWM_CLK_FIX]); ext_rate = clk_get_rate(fpc->clk[FSL_PWM_CLK_EXT]); @@ -182,158 +180,185 @@ static unsigned long fsl_pwm_calculate_period(struct fsl_pwm_chip *fpc, m1 = FSL_PWM_CLK_FIX; } - cycles = fsl_pwm_calculate_period_cycles(fpc, period_ns, m0); - if (cycles) { - fpc->cnt_select = m0; - return cycles; - } - - fpc->cnt_select = m1; + ret = fsl_pwm_calculate_period_clk(fpc, period_ns, m0, periodcfg); + if (ret) + return true; - return fsl_pwm_calculate_period_cycles(fpc, period_ns, m1); + return fsl_pwm_calculate_period_clk(fpc, period_ns, m1, periodcfg); } -static unsigned long fsl_pwm_calculate_duty(struct fsl_pwm_chip *fpc, - unsigned long period_ns, - unsigned long duty_ns) +static unsigned int fsl_pwm_calculate_duty(struct fsl_pwm_chip *fpc, + unsigned int duty_ns) { unsigned long long duty; - u32 val; - regmap_read(fpc->regmap, FTM_MOD, &val); - duty = (unsigned long long)duty_ns * (val + 1); + unsigned int period = fpc->period.mod_period + 1; + unsigned int period_ns = fsl_pwm_ticks_to_ns(fpc, period); + + duty = (unsigned long long)duty_ns * period; do_div(duty, period_ns); - return (unsigned long)duty; + return (unsigned int)duty; } -static int fsl_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, - int duty_ns, int period_ns) +static bool fsl_pwm_is_any_pwm_enabled(struct fsl_pwm_chip *fpc, + struct pwm_device *pwm) { - struct fsl_pwm_chip *fpc = to_fsl_chip(chip); - u32 period, duty; + u32 val; - mutex_lock(&fpc->lock); + regmap_read(fpc->regmap, FTM_OUTMASK, &val); + if (~val & 0xFF) + return true; + else + return false; +} + +static bool fsl_pwm_is_other_pwm_enabled(struct fsl_pwm_chip *fpc, + struct pwm_device *pwm) +{ + u32 val; + regmap_read(fpc->regmap, FTM_OUTMASK, &val); + if (~(val | BIT(pwm->hwpwm)) & 0xFF) + return true; + else + return false; +} + +static int fsl_pwm_apply_config(struct fsl_pwm_chip *fpc, + struct pwm_device *pwm, + struct pwm_state *newstate) +{ + unsigned int duty; + u32 reg_polarity; + + struct fsl_pwm_periodcfg periodcfg; + bool do_write_period = false; + + if (!fsl_pwm_calculate_period(fpc, newstate->period, &periodcfg)) { + dev_err(fpc->chip.dev, "failed to calculate new period\n"); + return -EINVAL; + } + + if (!fsl_pwm_is_any_pwm_enabled(fpc, pwm)) + do_write_period = true; /* * The Freescale FTM controller supports only a single period for - * all PWM channels, therefore incompatible changes need to be - * refused. + * all PWM channels, therefore verify if the newly computed period + * is different than the current period being used. In such case + * we allow to change the period only if no other pwm is running. */ - if (fpc->period_ns && fpc->period_ns != period_ns) { - dev_err(fpc->chip.dev, - "conflicting period requested for PWM %u\n", - pwm->hwpwm); - mutex_unlock(&fpc->lock); - return -EBUSY; + else if (!fsl_pwm_periodcfg_are_equal(&fpc->period, &periodcfg)) { + if (fsl_pwm_is_other_pwm_enabled(fpc, pwm)) { + dev_err(fpc->chip.dev, + "Cannot change period for PWM %u, disable other PWMs first\n", + pwm->hwpwm); + return -EBUSY; + } + if (fpc->period.clk_select != periodcfg.clk_select) { + int ret; + enum fsl_pwm_clk oldclk = fpc->period.clk_select; + enum fsl_pwm_clk newclk = periodcfg.clk_select; + + ret = clk_prepare_enable(fpc->clk[newclk]); + if (ret) + return ret; + clk_disable_unprepare(fpc->clk[oldclk]); + } + do_write_period = true; } - if (!fpc->period_ns && duty_ns) { - period = fsl_pwm_calculate_period(fpc, period_ns); - if (!period) { - dev_err(fpc->chip.dev, "failed to calculate period\n"); - mutex_unlock(&fpc->lock); - return -EINVAL; - } + ftm_clear_write_protection(fpc); + if (do_write_period) { + regmap_update_bits(fpc->regmap, FTM_SC, FTM_SC_CLK_MASK, + FTM_SC_CLK(periodcfg.clk_select)); regmap_update_bits(fpc->regmap, FTM_SC, FTM_SC_PS_MASK, - fpc->clk_ps); - regmap_write(fpc->regmap, FTM_MOD, period - 1); + periodcfg.clk_ps); + regmap_write(fpc->regmap, FTM_MOD, periodcfg.mod_period); - fpc->period_ns = period_ns; + fpc->period = periodcfg; } - mutex_unlock(&fpc->lock); - - duty = fsl_pwm_calculate_duty(fpc, period_ns, duty_ns); + duty = fsl_pwm_calculate_duty(fpc, newstate->duty_cycle); regmap_write(fpc->regmap, FTM_CSC(pwm->hwpwm), FTM_CSC_MSB | FTM_CSC_ELSB); regmap_write(fpc->regmap, FTM_CV(pwm->hwpwm), duty); - return 0; -} - -static int fsl_pwm_set_polarity(struct pwm_chip *chip, - struct pwm_device *pwm, - enum pwm_polarity polarity) -{ - struct fsl_pwm_chip *fpc = to_fsl_chip(chip); - u32 val; + reg_polarity = 0; + if (newstate->polarity == PWM_POLARITY_INVERSED) + reg_polarity = BIT(pwm->hwpwm); - regmap_read(fpc->regmap, FTM_POL, &val); + regmap_update_bits(fpc->regmap, FTM_POL, BIT(pwm->hwpwm), reg_polarity); - if (polarity == PWM_POLARITY_INVERSED) - val |= BIT(pwm->hwpwm); - else - val &= ~BIT(pwm->hwpwm); + newstate->period = fsl_pwm_ticks_to_ns(fpc, + fpc->period.mod_period + 1); + newstate->duty_cycle = fsl_pwm_ticks_to_ns(fpc, duty); - regmap_write(fpc->regmap, FTM_POL, val); + ftm_set_write_protection(fpc); return 0; } -static int fsl_counter_clock_enable(struct fsl_pwm_chip *fpc) -{ - int ret; - - /* select counter clock source */ - regmap_update_bits(fpc->regmap, FTM_SC, FTM_SC_CLK_MASK, - FTM_SC_CLK(fpc->cnt_select)); - - ret = clk_prepare_enable(fpc->clk[fpc->cnt_select]); - if (ret) - return ret; - - ret = clk_prepare_enable(fpc->clk[FSL_PWM_CLK_CNTEN]); - if (ret) { - clk_disable_unprepare(fpc->clk[fpc->cnt_select]); - return ret; - } - - return 0; -} - -static int fsl_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +static int fsl_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *newstate) { struct fsl_pwm_chip *fpc = to_fsl_chip(chip); - int ret; - - mutex_lock(&fpc->lock); - regmap_update_bits(fpc->regmap, FTM_OUTMASK, BIT(pwm->hwpwm), 0); + struct pwm_state *oldstate = &pwm->state; + int ret = 0; - ret = fsl_counter_clock_enable(fpc); - mutex_unlock(&fpc->lock); + /* + * oldstate to newstate : action + * + * disabled to disabled : ignore + * enabled to disabled : disable + * enabled to enabled : update settings + * disabled to enabled : update settings + enable + */ - return ret; -} + mutex_lock(&fpc->lock); -static void fsl_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) -{ - struct fsl_pwm_chip *fpc = to_fsl_chip(chip); - u32 val; + if (!newstate->enabled) { + if (oldstate->enabled) { + regmap_update_bits(fpc->regmap, FTM_OUTMASK, + BIT(pwm->hwpwm), BIT(pwm->hwpwm)); + clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_CNTEN]); + clk_disable_unprepare(fpc->clk[fpc->period.clk_select]); + } - mutex_lock(&fpc->lock); - regmap_update_bits(fpc->regmap, FTM_OUTMASK, BIT(pwm->hwpwm), - BIT(pwm->hwpwm)); + goto end_mutex; + } - clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_CNTEN]); - clk_disable_unprepare(fpc->clk[fpc->cnt_select]); + ret = fsl_pwm_apply_config(fpc, pwm, newstate); + if (ret) + goto end_mutex; + + /* check if need to enable */ + if (!oldstate->enabled) { + ret = clk_prepare_enable(fpc->clk[fpc->period.clk_select]); + if (ret) + goto end_mutex; + + ret = clk_prepare_enable(fpc->clk[FSL_PWM_CLK_CNTEN]); + if (ret) { + clk_disable_unprepare(fpc->clk[fpc->period.clk_select]); + goto end_mutex; + } - regmap_read(fpc->regmap, FTM_OUTMASK, &val); - if ((val & 0xFF) == 0xFF) - fpc->period_ns = 0; + regmap_update_bits(fpc->regmap, FTM_OUTMASK, BIT(pwm->hwpwm), + 0); + } +end_mutex: mutex_unlock(&fpc->lock); + return ret; } static const struct pwm_ops fsl_pwm_ops = { .request = fsl_pwm_request, .free = fsl_pwm_free, - .config = fsl_pwm_config, - .set_polarity = fsl_pwm_set_polarity, - .enable = fsl_pwm_enable, - .disable = fsl_pwm_disable, + .apply = fsl_pwm_apply, .owner = THIS_MODULE, }; @@ -357,6 +382,8 @@ static int fsl_pwm_init(struct fsl_pwm_chip *fpc) static bool fsl_pwm_volatile_reg(struct device *dev, unsigned int reg) { switch (reg) { + case FTM_FMS: + case FTM_MODE: case FTM_CNT: return true; } @@ -474,7 +501,7 @@ static int fsl_pwm_suspend(struct device *dev) continue; clk_disable_unprepare(fpc->clk[FSL_PWM_CLK_CNTEN]); - clk_disable_unprepare(fpc->clk[fpc->cnt_select]); + clk_disable_unprepare(fpc->clk[fpc->period.clk_select]); } return 0; @@ -496,7 +523,7 @@ static int fsl_pwm_resume(struct device *dev) if (!pwm_is_enabled(pwm)) continue; - clk_prepare_enable(fpc->clk[fpc->cnt_select]); + clk_prepare_enable(fpc->clk[fpc->period.clk_select]); clk_prepare_enable(fpc->clk[FSL_PWM_CLK_CNTEN]); } diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c index 88a51a40e695..f901e8a0d33d 100644 --- a/drivers/pwm/pwm-jz4740.c +++ b/drivers/pwm/pwm-jz4740.c @@ -63,7 +63,15 @@ static void jz4740_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) { uint32_t ctrl = jz4740_timer_get_ctrl(pwm->hwpwm); - /* Disable PWM output. + /* + * Set duty > period. This trick allows the TCU channels in TCU2 mode to + * properly return to their init level. + */ + jz4740_timer_set_duty(pwm->hwpwm, 0xffff); + jz4740_timer_set_period(pwm->hwpwm, 0x0); + + /* + * Disable PWM output. * In TCU2 mode (channel 1/2 on JZ4750+), this must be done before the * counter is stopped, while in TCU1 mode the order does not matter. */ @@ -74,17 +82,16 @@ static void jz4740_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) jz4740_timer_disable(pwm->hwpwm); } -static int jz4740_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, - int duty_ns, int period_ns) +static int jz4740_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) { struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip); unsigned long long tmp; unsigned long period, duty; unsigned int prescaler = 0; uint16_t ctrl; - bool is_enabled; - tmp = (unsigned long long)clk_get_rate(jz4740->clk) * period_ns; + tmp = (unsigned long long)clk_get_rate(jz4740->clk) * state->period; do_div(tmp, 1000000000); period = tmp; @@ -96,16 +103,14 @@ static int jz4740_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, if (prescaler == 6) return -EINVAL; - tmp = (unsigned long long)period * duty_ns; - do_div(tmp, period_ns); + tmp = (unsigned long long)period * state->duty_cycle; + do_div(tmp, state->period); duty = period - tmp; if (duty >= period) duty = period - 1; - is_enabled = jz4740_timer_is_enabled(pwm->hwpwm); - if (is_enabled) - jz4740_pwm_disable(chip, pwm); + jz4740_pwm_disable(chip, pwm); jz4740_timer_set_count(pwm->hwpwm, 0); jz4740_timer_set_duty(pwm->hwpwm, duty); @@ -116,18 +121,7 @@ static int jz4740_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, jz4740_timer_set_ctrl(pwm->hwpwm, ctrl); - if (is_enabled) - jz4740_pwm_enable(chip, pwm); - - return 0; -} - -static int jz4740_pwm_set_polarity(struct pwm_chip *chip, - struct pwm_device *pwm, enum pwm_polarity polarity) -{ - uint32_t ctrl = jz4740_timer_get_ctrl(pwm->pwm); - - switch (polarity) { + switch (state->polarity) { case PWM_POLARITY_NORMAL: ctrl &= ~JZ_TIMER_CTRL_PWM_ACTIVE_LOW; break; @@ -137,16 +131,17 @@ static int jz4740_pwm_set_polarity(struct pwm_chip *chip, } jz4740_timer_set_ctrl(pwm->hwpwm, ctrl); + + if (state->enabled) + jz4740_pwm_enable(chip, pwm); + return 0; } static const struct pwm_ops jz4740_pwm_ops = { .request = jz4740_pwm_request, .free = jz4740_pwm_free, - .config = jz4740_pwm_config, - .set_polarity = jz4740_pwm_set_polarity, - .enable = jz4740_pwm_enable, - .disable = jz4740_pwm_disable, + .apply = jz4740_pwm_apply, .owner = THIS_MODULE, }; @@ -184,8 +179,6 @@ static int jz4740_pwm_remove(struct platform_device *pdev) #ifdef CONFIG_OF static const struct of_device_id jz4740_pwm_dt_ids[] = { { .compatible = "ingenic,jz4740-pwm", }, - { .compatible = "ingenic,jz4770-pwm", }, - { .compatible = "ingenic,jz4780-pwm", }, {}, }; MODULE_DEVICE_TABLE(of, jz4740_pwm_dt_ids); diff --git a/drivers/pwm/pwm-meson.c b/drivers/pwm/pwm-meson.c index fb5a369b1a8d..3cbff5cbb789 100644 --- a/drivers/pwm/pwm-meson.c +++ b/drivers/pwm/pwm-meson.c @@ -1,65 +1,40 @@ +// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause /* - * This file is provided under a dual BSD/GPLv2 license. When using or - * redistributing this file, you may do so under either license. + * PWM controller driver for Amlogic Meson SoCs. * - * GPL LICENSE SUMMARY + * This PWM is only a set of Gates, Dividers and Counters: + * PWM output is achieved by calculating a clock that permits calculating + * two periods (low and high). The counter then has to be set to switch after + * N cycles for the first half period. + * The hardware has no "polarity" setting. This driver reverses the period + * cycles (the low length is inverted with the high length) for + * PWM_POLARITY_INVERSED. This means that .get_state cannot read the polarity + * from the hardware. + * Setting the duty cycle will disable and re-enable the PWM output. + * Disabling the PWM stops the output immediately (without waiting for the + * current period to complete first). * - * Copyright (c) 2016 BayLibre, SAS. - * Author: Neil Armstrong <narmstrong@baylibre.com> - * Copyright (C) 2014 Amlogic, Inc. - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of version 2 of the GNU General Public License as - * published by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see <http://www.gnu.org/licenses/>. - * The full GNU General Public License is included in this distribution - * in the file called COPYING. - * - * BSD LICENSE + * The public S912 (GXM) datasheet contains some documentation for this PWM + * controller starting on page 543: + * https://dl.khadas.com/Hardware/VIM2/Datasheet/S912_Datasheet_V0.220170314publicversion-Wesion.pdf + * An updated version of this IP block is found in S922X (G12B) SoCs. The + * datasheet contains the description for this IP block revision starting at + * page 1084: + * https://dn.odroid.com/S922X/ODROID-N2/Datasheet/S922X_Public_Datasheet_V0.2.pdf * * Copyright (c) 2016 BayLibre, SAS. * Author: Neil Armstrong <narmstrong@baylibre.com> * Copyright (C) 2014 Amlogic, Inc. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Intel Corporation nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ +#include <linux/bitfield.h> +#include <linux/bits.h> #include <linux/clk.h> #include <linux/clk-provider.h> #include <linux/err.h> #include <linux/io.h> #include <linux/kernel.h> +#include <linux/math64.h> #include <linux/module.h> #include <linux/of.h> #include <linux/of_device.h> @@ -70,7 +45,8 @@ #define REG_PWM_A 0x0 #define REG_PWM_B 0x4 -#define PWM_HIGH_SHIFT 16 +#define PWM_LOW_MASK GENMASK(15, 0) +#define PWM_HIGH_MASK GENMASK(31, 16) #define REG_MISC_AB 0x8 #define MISC_B_CLK_EN BIT(23) @@ -80,13 +56,33 @@ #define MISC_A_CLK_DIV_SHIFT 8 #define MISC_B_CLK_SEL_SHIFT 6 #define MISC_A_CLK_SEL_SHIFT 4 -#define MISC_CLK_SEL_WIDTH 2 +#define MISC_CLK_SEL_MASK 0x3 #define MISC_B_EN BIT(1) #define MISC_A_EN BIT(0) -static const unsigned int mux_reg_shifts[] = { - MISC_A_CLK_SEL_SHIFT, - MISC_B_CLK_SEL_SHIFT +#define MESON_NUM_PWMS 2 + +static struct meson_pwm_channel_data { + u8 reg_offset; + u8 clk_sel_shift; + u8 clk_div_shift; + u32 clk_en_mask; + u32 pwm_en_mask; +} meson_pwm_per_channel_data[MESON_NUM_PWMS] = { + { + .reg_offset = REG_PWM_A, + .clk_sel_shift = MISC_A_CLK_SEL_SHIFT, + .clk_div_shift = MISC_A_CLK_DIV_SHIFT, + .clk_en_mask = MISC_A_CLK_EN, + .pwm_en_mask = MISC_A_EN, + }, + { + .reg_offset = REG_PWM_B, + .clk_sel_shift = MISC_B_CLK_SEL_SHIFT, + .clk_div_shift = MISC_B_CLK_DIV_SHIFT, + .clk_en_mask = MISC_B_CLK_EN, + .pwm_en_mask = MISC_B_EN, + } }; struct meson_pwm_channel { @@ -94,8 +90,6 @@ struct meson_pwm_channel { unsigned int lo; u8 pre_div; - struct pwm_state state; - struct clk *clk_parent; struct clk_mux mux; struct clk *clk; @@ -109,8 +103,8 @@ struct meson_pwm_data { struct meson_pwm { struct pwm_chip chip; const struct meson_pwm_data *data; + struct meson_pwm_channel channels[MESON_NUM_PWMS]; void __iomem *base; - u8 inverter_mask; /* * Protects register (write) access to the REG_MISC_AB register * that is shared between the two PWMs. @@ -125,12 +119,16 @@ static inline struct meson_pwm *to_meson_pwm(struct pwm_chip *chip) static int meson_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) { - struct meson_pwm_channel *channel = pwm_get_chip_data(pwm); + struct meson_pwm *meson = to_meson_pwm(chip); + struct meson_pwm_channel *channel; struct device *dev = chip->dev; int err; - if (!channel) - return -ENODEV; + channel = pwm_get_chip_data(pwm); + if (channel) + return 0; + + channel = &meson->channels[pwm->hwpwm]; if (channel->clk_parent) { err = clk_set_parent(channel->clk, channel->clk_parent); @@ -149,9 +147,7 @@ static int meson_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) return err; } - chip->ops->get_state(chip, pwm, &channel->state); - - return 0; + return pwm_set_chip_data(pwm, channel); } static void meson_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) @@ -162,20 +158,18 @@ static void meson_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) clk_disable_unprepare(channel->clk); } -static int meson_pwm_calc(struct meson_pwm *meson, - struct meson_pwm_channel *channel, unsigned int id, - unsigned int duty, unsigned int period) +static int meson_pwm_calc(struct meson_pwm *meson, struct pwm_device *pwm, + struct pwm_state *state) { - unsigned int pre_div, cnt, duty_cnt; + struct meson_pwm_channel *channel = pwm_get_chip_data(pwm); + unsigned int duty, period, pre_div, cnt, duty_cnt; unsigned long fin_freq = -1; - u64 fin_ps; - if (~(meson->inverter_mask >> id) & 0x1) - duty = period - duty; + duty = state->duty_cycle; + period = state->period; - if (period == channel->state.period && - duty == channel->state.duty_cycle) - return 0; + if (state->polarity == PWM_POLARITY_INVERSED) + duty = period - duty; fin_freq = clk_get_rate(channel->clk); if (fin_freq == 0) { @@ -184,24 +178,19 @@ static int meson_pwm_calc(struct meson_pwm *meson, } dev_dbg(meson->chip.dev, "fin_freq: %lu Hz\n", fin_freq); - fin_ps = (u64)NSEC_PER_SEC * 1000; - do_div(fin_ps, fin_freq); - - /* Calc pre_div with the period */ - for (pre_div = 0; pre_div <= MISC_CLK_DIV_MASK; pre_div++) { - cnt = DIV_ROUND_CLOSEST_ULL((u64)period * 1000, - fin_ps * (pre_div + 1)); - dev_dbg(meson->chip.dev, "fin_ps=%llu pre_div=%u cnt=%u\n", - fin_ps, pre_div, cnt); - if (cnt <= 0xffff) - break; - } + pre_div = div64_u64(fin_freq * (u64)period, NSEC_PER_SEC * 0xffffLL); if (pre_div > MISC_CLK_DIV_MASK) { dev_err(meson->chip.dev, "unable to get period pre_div\n"); return -EINVAL; } + cnt = div64_u64(fin_freq * (u64)period, NSEC_PER_SEC * (pre_div + 1)); + if (cnt > 0xffff) { + dev_err(meson->chip.dev, "unable to get period cnt\n"); + return -EINVAL; + } + dev_dbg(meson->chip.dev, "period=%u pre_div=%u cnt=%u\n", period, pre_div, cnt); @@ -215,8 +204,8 @@ static int meson_pwm_calc(struct meson_pwm *meson, channel->lo = cnt; } else { /* Then check is we can have the duty with the same pre_div */ - duty_cnt = DIV_ROUND_CLOSEST_ULL((u64)duty * 1000, - fin_ps * (pre_div + 1)); + duty_cnt = div64_u64(fin_freq * (u64)duty, + NSEC_PER_SEC * (pre_div + 1)); if (duty_cnt > 0xffff) { dev_err(meson->chip.dev, "unable to get duty cycle\n"); return -EINVAL; @@ -233,73 +222,43 @@ static int meson_pwm_calc(struct meson_pwm *meson, return 0; } -static void meson_pwm_enable(struct meson_pwm *meson, - struct meson_pwm_channel *channel, - unsigned int id) +static void meson_pwm_enable(struct meson_pwm *meson, struct pwm_device *pwm) { - u32 value, clk_shift, clk_enable, enable; - unsigned int offset; + struct meson_pwm_channel *channel = pwm_get_chip_data(pwm); + struct meson_pwm_channel_data *channel_data; unsigned long flags; + u32 value; - switch (id) { - case 0: - clk_shift = MISC_A_CLK_DIV_SHIFT; - clk_enable = MISC_A_CLK_EN; - enable = MISC_A_EN; - offset = REG_PWM_A; - break; - - case 1: - clk_shift = MISC_B_CLK_DIV_SHIFT; - clk_enable = MISC_B_CLK_EN; - enable = MISC_B_EN; - offset = REG_PWM_B; - break; - - default: - return; - } + channel_data = &meson_pwm_per_channel_data[pwm->hwpwm]; spin_lock_irqsave(&meson->lock, flags); value = readl(meson->base + REG_MISC_AB); - value &= ~(MISC_CLK_DIV_MASK << clk_shift); - value |= channel->pre_div << clk_shift; - value |= clk_enable; + value &= ~(MISC_CLK_DIV_MASK << channel_data->clk_div_shift); + value |= channel->pre_div << channel_data->clk_div_shift; + value |= channel_data->clk_en_mask; writel(value, meson->base + REG_MISC_AB); - value = (channel->hi << PWM_HIGH_SHIFT) | channel->lo; - writel(value, meson->base + offset); + value = FIELD_PREP(PWM_HIGH_MASK, channel->hi) | + FIELD_PREP(PWM_LOW_MASK, channel->lo); + writel(value, meson->base + channel_data->reg_offset); value = readl(meson->base + REG_MISC_AB); - value |= enable; + value |= channel_data->pwm_en_mask; writel(value, meson->base + REG_MISC_AB); spin_unlock_irqrestore(&meson->lock, flags); } -static void meson_pwm_disable(struct meson_pwm *meson, unsigned int id) +static void meson_pwm_disable(struct meson_pwm *meson, struct pwm_device *pwm) { - u32 value, enable; unsigned long flags; - - switch (id) { - case 0: - enable = MISC_A_EN; - break; - - case 1: - enable = MISC_B_EN; - break; - - default: - return; - } + u32 value; spin_lock_irqsave(&meson->lock, flags); value = readl(meson->base + REG_MISC_AB); - value &= ~enable; + value &= ~meson_pwm_per_channel_data[pwm->hwpwm].pwm_en_mask; writel(value, meson->base + REG_MISC_AB); spin_unlock_irqrestore(&meson->lock, flags); @@ -316,64 +275,97 @@ static int meson_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, return -EINVAL; if (!state->enabled) { - meson_pwm_disable(meson, pwm->hwpwm); - channel->state.enabled = false; - - return 0; - } - - if (state->period != channel->state.period || - state->duty_cycle != channel->state.duty_cycle || - state->polarity != channel->state.polarity) { - if (state->polarity != channel->state.polarity) { - if (state->polarity == PWM_POLARITY_NORMAL) - meson->inverter_mask |= BIT(pwm->hwpwm); - else - meson->inverter_mask &= ~BIT(pwm->hwpwm); + if (state->polarity == PWM_POLARITY_INVERSED) { + /* + * This IP block revision doesn't have an "always high" + * setting which we can use for "inverted disabled". + * Instead we achieve this using the same settings + * that we use a pre_div of 0 (to get the shortest + * possible duration for one "count") and + * "period == duty_cycle". This results in a signal + * which is LOW for one "count", while being HIGH for + * the rest of the (so the signal is HIGH for slightly + * less than 100% of the period, but this is the best + * we can achieve). + */ + channel->pre_div = 0; + channel->hi = ~0; + channel->lo = 0; + + meson_pwm_enable(meson, pwm); + } else { + meson_pwm_disable(meson, pwm); } - - err = meson_pwm_calc(meson, channel, pwm->hwpwm, - state->duty_cycle, state->period); + } else { + err = meson_pwm_calc(meson, pwm, state); if (err < 0) return err; - channel->state.polarity = state->polarity; - channel->state.period = state->period; - channel->state.duty_cycle = state->duty_cycle; - } - - if (state->enabled && !channel->state.enabled) { - meson_pwm_enable(meson, channel, pwm->hwpwm); - channel->state.enabled = true; + meson_pwm_enable(meson, pwm); } return 0; } +static unsigned int meson_pwm_cnt_to_ns(struct pwm_chip *chip, + struct pwm_device *pwm, u32 cnt) +{ + struct meson_pwm *meson = to_meson_pwm(chip); + struct meson_pwm_channel *channel; + unsigned long fin_freq; + u32 fin_ns; + + /* to_meson_pwm() can only be used after .get_state() is called */ + channel = &meson->channels[pwm->hwpwm]; + + fin_freq = clk_get_rate(channel->clk); + if (fin_freq == 0) + return 0; + + fin_ns = div_u64(NSEC_PER_SEC, fin_freq); + + return cnt * fin_ns * (channel->pre_div + 1); +} + static void meson_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { struct meson_pwm *meson = to_meson_pwm(chip); - u32 value, mask; + struct meson_pwm_channel_data *channel_data; + struct meson_pwm_channel *channel; + u32 value, tmp; if (!state) return; - switch (pwm->hwpwm) { - case 0: - mask = MISC_A_EN; - break; + channel = &meson->channels[pwm->hwpwm]; + channel_data = &meson_pwm_per_channel_data[pwm->hwpwm]; - case 1: - mask = MISC_B_EN; - break; + value = readl(meson->base + REG_MISC_AB); - default: - return; - } + tmp = channel_data->pwm_en_mask | channel_data->clk_en_mask; + state->enabled = (value & tmp) == tmp; - value = readl(meson->base + REG_MISC_AB); - state->enabled = (value & mask) != 0; + tmp = value >> channel_data->clk_div_shift; + channel->pre_div = FIELD_GET(MISC_CLK_DIV_MASK, tmp); + + value = readl(meson->base + channel_data->reg_offset); + + channel->lo = FIELD_GET(PWM_LOW_MASK, value); + channel->hi = FIELD_GET(PWM_HIGH_MASK, value); + + if (channel->lo == 0) { + state->period = meson_pwm_cnt_to_ns(chip, pwm, channel->hi); + state->duty_cycle = state->period; + } else if (channel->lo >= channel->hi) { + state->period = meson_pwm_cnt_to_ns(chip, pwm, + channel->lo + channel->hi); + state->duty_cycle = meson_pwm_cnt_to_ns(chip, pwm, + channel->hi); + } else { + state->period = 0; + state->duty_cycle = 0; + } } static const struct pwm_ops meson_pwm_ops = { @@ -433,8 +425,17 @@ static const struct meson_pwm_data pwm_axg_ao_data = { .num_parents = ARRAY_SIZE(pwm_axg_ao_parent_names), }; +static const char * const pwm_g12a_ao_ab_parent_names[] = { + "xtal", "aoclk81", "fclk_div4", "fclk_div5" +}; + +static const struct meson_pwm_data pwm_g12a_ao_ab_data = { + .parent_names = pwm_g12a_ao_ab_parent_names, + .num_parents = ARRAY_SIZE(pwm_g12a_ao_ab_parent_names), +}; + static const char * const pwm_g12a_ao_cd_parent_names[] = { - "aoclk81", "xtal", + "xtal", "aoclk81", }; static const struct meson_pwm_data pwm_g12a_ao_cd_data = { @@ -478,7 +479,7 @@ static const struct of_device_id meson_pwm_matches[] = { }, { .compatible = "amlogic,meson-g12a-ao-pwm-ab", - .data = &pwm_axg_ao_data + .data = &pwm_g12a_ao_ab_data }, { .compatible = "amlogic,meson-g12a-ao-pwm-cd", @@ -488,8 +489,7 @@ static const struct of_device_id meson_pwm_matches[] = { }; MODULE_DEVICE_TABLE(of, meson_pwm_matches); -static int meson_pwm_init_channels(struct meson_pwm *meson, - struct meson_pwm_channel *channels) +static int meson_pwm_init_channels(struct meson_pwm *meson) { struct device *dev = meson->chip.dev; struct clk_init_data init; @@ -498,7 +498,7 @@ static int meson_pwm_init_channels(struct meson_pwm *meson, int err; for (i = 0; i < meson->chip.npwm; i++) { - struct meson_pwm_channel *channel = &channels[i]; + struct meson_pwm_channel *channel = &meson->channels[i]; snprintf(name, sizeof(name), "%s#mux%u", dev_name(dev), i); @@ -509,8 +509,9 @@ static int meson_pwm_init_channels(struct meson_pwm *meson, init.num_parents = meson->data->num_parents; channel->mux.reg = meson->base + REG_MISC_AB; - channel->mux.shift = mux_reg_shifts[i]; - channel->mux.mask = BIT(MISC_CLK_SEL_WIDTH) - 1; + channel->mux.shift = + meson_pwm_per_channel_data[i].clk_sel_shift; + channel->mux.mask = MISC_CLK_SEL_MASK; channel->mux.flags = 0; channel->mux.lock = &meson->lock; channel->mux.table = NULL; @@ -525,31 +526,16 @@ static int meson_pwm_init_channels(struct meson_pwm *meson, snprintf(name, sizeof(name), "clkin%u", i); - channel->clk_parent = devm_clk_get(dev, name); - if (IS_ERR(channel->clk_parent)) { - err = PTR_ERR(channel->clk_parent); - if (err == -EPROBE_DEFER) - return err; - - channel->clk_parent = NULL; - } + channel->clk_parent = devm_clk_get_optional(dev, name); + if (IS_ERR(channel->clk_parent)) + return PTR_ERR(channel->clk_parent); } return 0; } -static void meson_pwm_add_channels(struct meson_pwm *meson, - struct meson_pwm_channel *channels) -{ - unsigned int i; - - for (i = 0; i < meson->chip.npwm; i++) - pwm_set_chip_data(&meson->chip.pwms[i], &channels[i]); -} - static int meson_pwm_probe(struct platform_device *pdev) { - struct meson_pwm_channel *channels; struct meson_pwm *meson; struct resource *regs; int err; @@ -567,19 +553,13 @@ static int meson_pwm_probe(struct platform_device *pdev) meson->chip.dev = &pdev->dev; meson->chip.ops = &meson_pwm_ops; meson->chip.base = -1; - meson->chip.npwm = 2; + meson->chip.npwm = MESON_NUM_PWMS; meson->chip.of_xlate = of_pwm_xlate_with_flags; meson->chip.of_pwm_n_cells = 3; meson->data = of_device_get_match_data(&pdev->dev); - meson->inverter_mask = BIT(meson->chip.npwm) - 1; - channels = devm_kcalloc(&pdev->dev, meson->chip.npwm, - sizeof(*channels), GFP_KERNEL); - if (!channels) - return -ENOMEM; - - err = meson_pwm_init_channels(meson, channels); + err = meson_pwm_init_channels(meson); if (err < 0) return err; @@ -589,8 +569,6 @@ static int meson_pwm_probe(struct platform_device *pdev) return err; } - meson_pwm_add_channels(meson, channels); - platform_set_drvdata(pdev, meson); return 0; diff --git a/drivers/pwm/pwm-rcar.c b/drivers/pwm/pwm-rcar.c index cfe7dd1b448e..5b2b8ecc354c 100644 --- a/drivers/pwm/pwm-rcar.c +++ b/drivers/pwm/pwm-rcar.c @@ -254,50 +254,11 @@ static const struct of_device_id rcar_pwm_of_table[] = { }; MODULE_DEVICE_TABLE(of, rcar_pwm_of_table); -#ifdef CONFIG_PM_SLEEP -static struct pwm_device *rcar_pwm_dev_to_pwm_dev(struct device *dev) -{ - struct rcar_pwm_chip *rcar_pwm = dev_get_drvdata(dev); - struct pwm_chip *chip = &rcar_pwm->chip; - - return &chip->pwms[0]; -} - -static int rcar_pwm_suspend(struct device *dev) -{ - struct pwm_device *pwm = rcar_pwm_dev_to_pwm_dev(dev); - - if (!test_bit(PWMF_REQUESTED, &pwm->flags)) - return 0; - - pm_runtime_put(dev); - - return 0; -} - -static int rcar_pwm_resume(struct device *dev) -{ - struct pwm_device *pwm = rcar_pwm_dev_to_pwm_dev(dev); - struct pwm_state state; - - if (!test_bit(PWMF_REQUESTED, &pwm->flags)) - return 0; - - pm_runtime_get_sync(dev); - - pwm_get_state(pwm, &state); - - return rcar_pwm_apply(pwm->chip, pwm, &state); -} -#endif /* CONFIG_PM_SLEEP */ -static SIMPLE_DEV_PM_OPS(rcar_pwm_pm_ops, rcar_pwm_suspend, rcar_pwm_resume); - static struct platform_driver rcar_pwm_driver = { .probe = rcar_pwm_probe, .remove = rcar_pwm_remove, .driver = { .name = "pwm-rcar", - .pm = &rcar_pwm_pm_ops, .of_match_table = of_match_ptr(rcar_pwm_of_table), } }; diff --git a/drivers/pwm/pwm-sifive.c b/drivers/pwm/pwm-sifive.c new file mode 100644 index 000000000000..a7c107f19e66 --- /dev/null +++ b/drivers/pwm/pwm-sifive.c @@ -0,0 +1,339 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2017-2018 SiFive + * For SiFive's PWM IP block documentation please refer Chapter 14 of + * Reference Manual : https://static.dev.sifive.com/FU540-C000-v1.0.pdf + * + * Limitations: + * - When changing both duty cycle and period, we cannot prevent in + * software that the output might produce a period with mixed + * settings (new period length and old duty cycle). + * - The hardware cannot generate a 100% duty cycle. + * - The hardware generates only inverted output. + */ +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pwm.h> +#include <linux/slab.h> +#include <linux/bitfield.h> + +/* Register offsets */ +#define PWM_SIFIVE_PWMCFG 0x0 +#define PWM_SIFIVE_PWMCOUNT 0x8 +#define PWM_SIFIVE_PWMS 0x10 +#define PWM_SIFIVE_PWMCMP0 0x20 + +/* PWMCFG fields */ +#define PWM_SIFIVE_PWMCFG_SCALE GENMASK(3, 0) +#define PWM_SIFIVE_PWMCFG_STICKY BIT(8) +#define PWM_SIFIVE_PWMCFG_ZERO_CMP BIT(9) +#define PWM_SIFIVE_PWMCFG_DEGLITCH BIT(10) +#define PWM_SIFIVE_PWMCFG_EN_ALWAYS BIT(12) +#define PWM_SIFIVE_PWMCFG_EN_ONCE BIT(13) +#define PWM_SIFIVE_PWMCFG_CENTER BIT(16) +#define PWM_SIFIVE_PWMCFG_GANG BIT(24) +#define PWM_SIFIVE_PWMCFG_IP BIT(28) + +/* PWM_SIFIVE_SIZE_PWMCMP is used to calculate offset for pwmcmpX registers */ +#define PWM_SIFIVE_SIZE_PWMCMP 4 +#define PWM_SIFIVE_CMPWIDTH 16 +#define PWM_SIFIVE_DEFAULT_PERIOD 10000000 + +struct pwm_sifive_ddata { + struct pwm_chip chip; + struct mutex lock; /* lock to protect user_count */ + struct notifier_block notifier; + struct clk *clk; + void __iomem *regs; + unsigned int real_period; + unsigned int approx_period; + int user_count; +}; + +static inline +struct pwm_sifive_ddata *pwm_sifive_chip_to_ddata(struct pwm_chip *c) +{ + return container_of(c, struct pwm_sifive_ddata, chip); +} + +static int pwm_sifive_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip); + + mutex_lock(&ddata->lock); + ddata->user_count++; + mutex_unlock(&ddata->lock); + + return 0; +} + +static void pwm_sifive_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip); + + mutex_lock(&ddata->lock); + ddata->user_count--; + mutex_unlock(&ddata->lock); +} + +static void pwm_sifive_update_clock(struct pwm_sifive_ddata *ddata, + unsigned long rate) +{ + unsigned long long num; + unsigned long scale_pow; + int scale; + u32 val; + /* + * The PWM unit is used with pwmzerocmp=0, so the only way to modify the + * period length is using pwmscale which provides the number of bits the + * counter is shifted before being feed to the comparators. A period + * lasts (1 << (PWM_SIFIVE_CMPWIDTH + pwmscale)) clock ticks. + * (1 << (PWM_SIFIVE_CMPWIDTH + scale)) * 10^9/rate = period + */ + scale_pow = div64_ul(ddata->approx_period * (u64)rate, NSEC_PER_SEC); + scale = clamp(ilog2(scale_pow) - PWM_SIFIVE_CMPWIDTH, 0, 0xf); + + val = PWM_SIFIVE_PWMCFG_EN_ALWAYS | + FIELD_PREP(PWM_SIFIVE_PWMCFG_SCALE, scale); + writel(val, ddata->regs + PWM_SIFIVE_PWMCFG); + + /* As scale <= 15 the shift operation cannot overflow. */ + num = (unsigned long long)NSEC_PER_SEC << (PWM_SIFIVE_CMPWIDTH + scale); + ddata->real_period = div64_ul(num, rate); + dev_dbg(ddata->chip.dev, + "New real_period = %u ns\n", ddata->real_period); +} + +static void pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip); + u32 duty, val; + + duty = readl(ddata->regs + PWM_SIFIVE_PWMCMP0 + + pwm->hwpwm * PWM_SIFIVE_SIZE_PWMCMP); + + state->enabled = duty > 0; + + val = readl(ddata->regs + PWM_SIFIVE_PWMCFG); + if (!(val & PWM_SIFIVE_PWMCFG_EN_ALWAYS)) + state->enabled = false; + + state->period = ddata->real_period; + state->duty_cycle = + (u64)duty * ddata->real_period >> PWM_SIFIVE_CMPWIDTH; + state->polarity = PWM_POLARITY_INVERSED; +} + +static int pwm_sifive_enable(struct pwm_chip *chip, bool enable) +{ + struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip); + int ret; + + if (enable) { + ret = clk_enable(ddata->clk); + if (ret) { + dev_err(ddata->chip.dev, "Enable clk failed\n"); + return ret; + } + } + + if (!enable) + clk_disable(ddata->clk); + + return 0; +} + +static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm, + struct pwm_state *state) +{ + struct pwm_sifive_ddata *ddata = pwm_sifive_chip_to_ddata(chip); + struct pwm_state cur_state; + unsigned int duty_cycle; + unsigned long long num; + bool enabled; + int ret = 0; + u32 frac; + + if (state->polarity != PWM_POLARITY_INVERSED) + return -EINVAL; + + ret = clk_enable(ddata->clk); + if (ret) { + dev_err(ddata->chip.dev, "Enable clk failed\n"); + return ret; + } + + mutex_lock(&ddata->lock); + cur_state = pwm->state; + enabled = cur_state.enabled; + + duty_cycle = state->duty_cycle; + if (!state->enabled) + duty_cycle = 0; + + /* + * The problem of output producing mixed setting as mentioned at top, + * occurs here. To minimize the window for this problem, we are + * calculating the register values first and then writing them + * consecutively + */ + num = (u64)duty_cycle * (1U << PWM_SIFIVE_CMPWIDTH); + frac = DIV_ROUND_CLOSEST_ULL(num, state->period); + /* The hardware cannot generate a 100% duty cycle */ + frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1); + + if (state->period != ddata->approx_period) { + if (ddata->user_count != 1) { + ret = -EBUSY; + goto exit; + } + ddata->approx_period = state->period; + pwm_sifive_update_clock(ddata, clk_get_rate(ddata->clk)); + } + + writel(frac, ddata->regs + PWM_SIFIVE_PWMCMP0 + + pwm->hwpwm * PWM_SIFIVE_SIZE_PWMCMP); + + if (state->enabled != enabled) + pwm_sifive_enable(chip, state->enabled); + +exit: + clk_disable(ddata->clk); + mutex_unlock(&ddata->lock); + return ret; +} + +static const struct pwm_ops pwm_sifive_ops = { + .request = pwm_sifive_request, + .free = pwm_sifive_free, + .get_state = pwm_sifive_get_state, + .apply = pwm_sifive_apply, + .owner = THIS_MODULE, +}; + +static int pwm_sifive_clock_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct clk_notifier_data *ndata = data; + struct pwm_sifive_ddata *ddata = + container_of(nb, struct pwm_sifive_ddata, notifier); + + if (event == POST_RATE_CHANGE) + pwm_sifive_update_clock(ddata, ndata->new_rate); + + return NOTIFY_OK; +} + +static int pwm_sifive_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct pwm_sifive_ddata *ddata; + struct pwm_chip *chip; + struct resource *res; + int ret; + + ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + mutex_init(&ddata->lock); + chip = &ddata->chip; + chip->dev = dev; + chip->ops = &pwm_sifive_ops; + chip->of_xlate = of_pwm_xlate_with_flags; + chip->of_pwm_n_cells = 3; + chip->base = -1; + chip->npwm = 4; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ddata->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(ddata->regs)) { + dev_err(dev, "Unable to map IO resources\n"); + return PTR_ERR(ddata->regs); + } + + ddata->clk = devm_clk_get(dev, NULL); + if (IS_ERR(ddata->clk)) { + if (PTR_ERR(ddata->clk) != -EPROBE_DEFER) + dev_err(dev, "Unable to find controller clock\n"); + return PTR_ERR(ddata->clk); + } + + ret = clk_prepare_enable(ddata->clk); + if (ret) { + dev_err(dev, "failed to enable clock for pwm: %d\n", ret); + return ret; + } + + /* Watch for changes to underlying clock frequency */ + ddata->notifier.notifier_call = pwm_sifive_clock_notifier; + ret = clk_notifier_register(ddata->clk, &ddata->notifier); + if (ret) { + dev_err(dev, "failed to register clock notifier: %d\n", ret); + goto disable_clk; + } + + ret = pwmchip_add(chip); + if (ret < 0) { + dev_err(dev, "cannot register PWM: %d\n", ret); + goto unregister_clk; + } + + platform_set_drvdata(pdev, ddata); + dev_dbg(dev, "SiFive PWM chip registered %d PWMs\n", chip->npwm); + + return 0; + +unregister_clk: + clk_notifier_unregister(ddata->clk, &ddata->notifier); +disable_clk: + clk_disable_unprepare(ddata->clk); + + return ret; +} + +static int pwm_sifive_remove(struct platform_device *dev) +{ + struct pwm_sifive_ddata *ddata = platform_get_drvdata(dev); + bool is_enabled = false; + struct pwm_device *pwm; + int ret, ch; + + for (ch = 0; ch < ddata->chip.npwm; ch++) { + pwm = &ddata->chip.pwms[ch]; + if (pwm->state.enabled) { + is_enabled = true; + break; + } + } + if (is_enabled) + clk_disable(ddata->clk); + + clk_disable_unprepare(ddata->clk); + ret = pwmchip_remove(&ddata->chip); + clk_notifier_unregister(ddata->clk, &ddata->notifier); + + return ret; +} + +static const struct of_device_id pwm_sifive_of_match[] = { + { .compatible = "sifive,pwm0" }, + {}, +}; +MODULE_DEVICE_TABLE(of, pwm_sifive_of_match); + +static struct platform_driver pwm_sifive_driver = { + .probe = pwm_sifive_probe, + .remove = pwm_sifive_remove, + .driver = { + .name = "pwm-sifive", + .of_match_table = pwm_sifive_of_match, + }, +}; +module_platform_driver(pwm_sifive_driver); + +MODULE_DESCRIPTION("SiFive PWM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/pwm-stm32-lp.c b/drivers/pwm/pwm-stm32-lp.c index 0059b24cfdc3..2211a642066d 100644 --- a/drivers/pwm/pwm-stm32-lp.c +++ b/drivers/pwm/pwm-stm32-lp.c @@ -13,6 +13,7 @@ #include <linux/mfd/stm32-lptimer.h> #include <linux/module.h> #include <linux/of.h> +#include <linux/pinctrl/consumer.h> #include <linux/platform_device.h> #include <linux/pwm.h> @@ -223,6 +224,29 @@ static int stm32_pwm_lp_remove(struct platform_device *pdev) return pwmchip_remove(&priv->chip); } +static int __maybe_unused stm32_pwm_lp_suspend(struct device *dev) +{ + struct stm32_pwm_lp *priv = dev_get_drvdata(dev); + struct pwm_state state; + + pwm_get_state(&priv->chip.pwms[0], &state); + if (state.enabled) { + dev_err(dev, "The consumer didn't stop us (%s)\n", + priv->chip.pwms[0].label); + return -EBUSY; + } + + return pinctrl_pm_select_sleep_state(dev); +} + +static int __maybe_unused stm32_pwm_lp_resume(struct device *dev) +{ + return pinctrl_pm_select_default_state(dev); +} + +static SIMPLE_DEV_PM_OPS(stm32_pwm_lp_pm_ops, stm32_pwm_lp_suspend, + stm32_pwm_lp_resume); + static const struct of_device_id stm32_pwm_lp_of_match[] = { { .compatible = "st,stm32-pwm-lp", }, {}, @@ -235,6 +259,7 @@ static struct platform_driver stm32_pwm_lp_driver = { .driver = { .name = "stm32-pwm-lp", .of_match_table = of_match_ptr(stm32_pwm_lp_of_match), + .pm = &stm32_pwm_lp_pm_ops, }, }; module_platform_driver(stm32_pwm_lp_driver); diff --git a/drivers/pwm/pwm-stm32.c b/drivers/pwm/pwm-stm32.c index 4f842550fbd1..740e2dec8313 100644 --- a/drivers/pwm/pwm-stm32.c +++ b/drivers/pwm/pwm-stm32.c @@ -608,6 +608,8 @@ static int stm32_pwm_probe(struct platform_device *pdev) priv->regmap = ddata->regmap; priv->clk = ddata->clk; priv->max_arr = ddata->max_arr; + priv->chip.of_xlate = of_pwm_xlate_with_flags; + priv->chip.of_pwm_n_cells = 3; if (!priv->regmap || !priv->clk) return -EINVAL; diff --git a/drivers/pwm/sysfs.c b/drivers/pwm/sysfs.c index bf6823fe0812..2389b8669846 100644 --- a/drivers/pwm/sysfs.c +++ b/drivers/pwm/sysfs.c @@ -18,6 +18,7 @@ struct pwm_export { struct device child; struct pwm_device *pwm; struct mutex lock; + struct pwm_state suspend; }; static struct pwm_export *child_to_pwm_export(struct device *child) @@ -372,10 +373,111 @@ static struct attribute *pwm_chip_attrs[] = { }; ATTRIBUTE_GROUPS(pwm_chip); +/* takes export->lock on success */ +static struct pwm_export *pwm_class_get_state(struct device *parent, + struct pwm_device *pwm, + struct pwm_state *state) +{ + struct device *child; + struct pwm_export *export; + + if (!test_bit(PWMF_EXPORTED, &pwm->flags)) + return NULL; + + child = device_find_child(parent, pwm, pwm_unexport_match); + if (!child) + return NULL; + + export = child_to_pwm_export(child); + put_device(child); /* for device_find_child() */ + + mutex_lock(&export->lock); + pwm_get_state(pwm, state); + + return export; +} + +static int pwm_class_apply_state(struct pwm_export *export, + struct pwm_device *pwm, + struct pwm_state *state) +{ + int ret = pwm_apply_state(pwm, state); + + /* release lock taken in pwm_class_get_state */ + mutex_unlock(&export->lock); + + return ret; +} + +static int pwm_class_resume_npwm(struct device *parent, unsigned int npwm) +{ + struct pwm_chip *chip = dev_get_drvdata(parent); + unsigned int i; + int ret = 0; + + for (i = 0; i < npwm; i++) { + struct pwm_device *pwm = &chip->pwms[i]; + struct pwm_state state; + struct pwm_export *export; + + export = pwm_class_get_state(parent, pwm, &state); + if (!export) + continue; + + state.enabled = export->suspend.enabled; + ret = pwm_class_apply_state(export, pwm, &state); + if (ret < 0) + break; + } + + return ret; +} + +static int __maybe_unused pwm_class_suspend(struct device *parent) +{ + struct pwm_chip *chip = dev_get_drvdata(parent); + unsigned int i; + int ret = 0; + + for (i = 0; i < chip->npwm; i++) { + struct pwm_device *pwm = &chip->pwms[i]; + struct pwm_state state; + struct pwm_export *export; + + export = pwm_class_get_state(parent, pwm, &state); + if (!export) + continue; + + export->suspend = state; + state.enabled = false; + ret = pwm_class_apply_state(export, pwm, &state); + if (ret < 0) { + /* + * roll back the PWM devices that were disabled by + * this suspend function. + */ + pwm_class_resume_npwm(parent, i); + break; + } + } + + return ret; +} + +static int __maybe_unused pwm_class_resume(struct device *parent) +{ + struct pwm_chip *chip = dev_get_drvdata(parent); + + return pwm_class_resume_npwm(parent, chip->npwm); +} + +static SIMPLE_DEV_PM_OPS(pwm_class_pm_ops, pwm_class_suspend, pwm_class_resume); + static struct class pwm_class = { .name = "pwm", .owner = THIS_MODULE, .dev_groups = pwm_chip_groups, + .pm = &pwm_class_pm_ops, }; static int pwmchip_sysfs_match(struct device *parent, const void *data) |