Merge 530c28df03 ("Merge tag 'pwm/for-5.20-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/thierry.reding/linux-pwm") into android-mainline

Steps on the way to 6.0-rc1

Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
Change-Id: I42b5b68e2e53ea06316c320d54fb9e3a869d2712
This commit is contained in:
Greg Kroah-Hartman
2022-08-10 13:55:38 +02:00
13 changed files with 353 additions and 191 deletions

View File

@@ -0,0 +1,46 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/pwm/clk-pwm.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Clock based PWM controller
maintainers:
- Nikita Travkin <nikita@trvn.ru>
description: |
Some systems have clocks that can be exposed to external devices.
(e.g. by muxing them to GPIO pins)
It's often possible to control duty-cycle of such clocks which makes them
suitable for generating PWM signal.
allOf:
- $ref: pwm.yaml#
properties:
compatible:
const: clk-pwm
clocks:
description: Clock used to generate the signal.
maxItems: 1
"#pwm-cells":
const: 2
unevaluatedProperties: false
required:
- compatible
- clocks
examples:
- |
pwm {
compatible = "clk-pwm";
#pwm-cells = <2>;
clocks = <&gcc 0>;
pinctrl-names = "default";
pinctrl-0 = <&pwm_clk_flash_default>;
};

View File

@@ -9,6 +9,8 @@ Required properties:
- "mediatek,mt7628-pwm": found on mt7628 SoC.
- "mediatek,mt7629-pwm": found on mt7629 SoC.
- "mediatek,mt8183-pwm": found on mt8183 SoC.
- "mediatek,mt8195-pwm", "mediatek,mt8183-pwm": found on mt8195 SoC.
- "mediatek,mt8365-pwm": found on mt8365 SoC.
- "mediatek,mt8516-pwm": found on mt8516 SoC.
- reg: physical base address and length of the controller's registers.
- #pwm-cells: must be 2. See pwm.yaml in this directory for a description of
@@ -18,6 +20,7 @@ Required properties:
has no clocks
- "top": the top clock generator
- "main": clock used by the PWM core
- "pwm1-3": the three per PWM clocks for mt8365
- "pwm1-8": the eight per PWM clocks for mt2712
- "pwm1-6": the six per PWM clocks for mt7622
- "pwm1-5": the five per PWM clocks for mt7623

View File

@@ -16384,7 +16384,6 @@ F: drivers/media/rc/pwm-ir-tx.c
PWM SUBSYSTEM
M: Thierry Reding <thierry.reding@gmail.com>
R: Uwe Kleine-König <u.kleine-koenig@pengutronix.de>
M: Lee Jones <lee.jones@linaro.org>
L: linux-pwm@vger.kernel.org
S: Maintained
Q: https://patchwork.ozlabs.org/project/linux-pwm/list/
@@ -16395,6 +16394,7 @@ F: Documentation/driver-api/pwm.rst
F: drivers/gpio/gpio-mvebu.c
F: drivers/pwm/
F: drivers/video/backlight/pwm_bl.c
F: include/dt-bindings/pwm/
F: include/linux/pwm.h
F: include/linux/pwm_backlight.h
K: pwm_(config|apply_state|ops)

View File

@@ -140,6 +140,16 @@ config PWM_BRCMSTB
To compile this driver as a module, choose M Here: the module
will be called pwm-brcmstb.c.
config PWM_CLK
tristate "Clock based PWM support"
depends on HAVE_CLK || COMPILE_TEST
help
Generic PWM framework driver for outputs that can be
muxed to clocks.
To compile this driver as a module, choose M here: the module
will be called pwm-clk.
config PWM_CLPS711X
tristate "CLPS711X PWM support"
depends on ARCH_CLPS711X || COMPILE_TEST

View File

@@ -10,6 +10,7 @@ obj-$(CONFIG_PWM_BCM_KONA) += pwm-bcm-kona.o
obj-$(CONFIG_PWM_BCM2835) += pwm-bcm2835.o
obj-$(CONFIG_PWM_BERLIN) += pwm-berlin.o
obj-$(CONFIG_PWM_BRCMSTB) += pwm-brcmstb.o
obj-$(CONFIG_PWM_CLK) += pwm-clk.o
obj-$(CONFIG_PWM_CLPS711X) += pwm-clps711x.o
obj-$(CONFIG_PWM_CRC) += pwm-crc.o
obj-$(CONFIG_PWM_CROS_EC) += pwm-cros-ec.o

View File

@@ -235,18 +235,8 @@ EXPORT_SYMBOL_GPL(pwm_get_chip_data);
static bool pwm_ops_check(const struct pwm_chip *chip)
{
const struct pwm_ops *ops = chip->ops;
/* driver supports legacy, non-atomic operation */
if (ops->config && ops->enable && ops->disable) {
if (IS_ENABLED(CONFIG_PWM_DEBUG))
dev_warn(chip->dev,
"Driver needs updating to atomic API\n");
return true;
}
if (!ops->apply)
return false;
@@ -548,73 +538,6 @@ static void pwm_apply_state_debug(struct pwm_device *pwm,
}
}
static int pwm_apply_legacy(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
int err;
struct pwm_state initial_state = pwm->state;
if (state->polarity != pwm->state.polarity) {
if (!chip->ops->set_polarity)
return -EINVAL;
/*
* Changing the polarity of a running PWM is only allowed when
* the PWM driver implements ->apply().
*/
if (pwm->state.enabled) {
chip->ops->disable(chip, pwm);
/*
* Update pwm->state already here in case
* .set_polarity() or another callback depend on that.
*/
pwm->state.enabled = false;
}
err = chip->ops->set_polarity(chip, pwm, state->polarity);
if (err)
goto rollback;
pwm->state.polarity = state->polarity;
}
if (!state->enabled) {
if (pwm->state.enabled)
chip->ops->disable(chip, pwm);
return 0;
}
/*
* We cannot skip calling ->config even if state->period ==
* pwm->state.period && state->duty_cycle == pwm->state.duty_cycle
* because we might have exited early in the last call to
* pwm_apply_state because of !state->enabled and so the two values in
* pwm->state might not be configured in hardware.
*/
err = chip->ops->config(pwm->chip, pwm,
state->duty_cycle,
state->period);
if (err)
goto rollback;
pwm->state.period = state->period;
pwm->state.duty_cycle = state->duty_cycle;
if (!pwm->state.enabled) {
err = chip->ops->enable(chip, pwm);
if (err)
goto rollback;
}
return 0;
rollback:
pwm->state = initial_state;
return err;
}
/**
* pwm_apply_state() - atomically apply a new state to a PWM device
* @pwm: PWM device
@@ -647,10 +570,7 @@ int pwm_apply_state(struct pwm_device *pwm, const struct pwm_state *state)
state->usage_power == pwm->state.usage_power)
return 0;
if (chip->ops->apply)
err = chip->ops->apply(chip, pwm, state);
else
err = pwm_apply_legacy(chip, pwm, state);
err = chip->ops->apply(chip, pwm, state);
if (err)
return err;

View File

@@ -304,7 +304,7 @@ static int atmel_tcb_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
/*
* Find best clk divisor:
* the smallest divisor which can fulfill the period_ns requirements.
* If there is a gclk, the first divisor is actuallly the gclk selector
* If there is a gclk, the first divisor is actually the gclk selector
*/
if (tcbpwmc->gclk)
i = 1;

148
drivers/pwm/pwm-clk.c Normal file
View File

@@ -0,0 +1,148 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Clock based PWM controller
*
* Copyright (c) 2021 Nikita Travkin <nikita@trvn.ru>
*
* This is an "adapter" driver that allows PWM consumers to use
* system clocks with duty cycle control as PWM outputs.
*
* Limitations:
* - Due to the fact that exact behavior depends on the underlying
* clock driver, various limitations are possible.
* - Underlying clock may not be able to give 0% or 100% duty cycle
* (constant off or on), exact behavior will depend on the clock.
* - When the PWM is disabled, the clock will be disabled as well,
* line state will depend on the clock.
* - The clk API doesn't expose the necessary calls to implement
* .get_state().
*/
#include <linux/kernel.h>
#include <linux/math64.h>
#include <linux/err.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/pwm.h>
struct pwm_clk_chip {
struct pwm_chip chip;
struct clk *clk;
bool clk_enabled;
};
#define to_pwm_clk_chip(_chip) container_of(_chip, struct pwm_clk_chip, chip)
static int pwm_clk_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
struct pwm_clk_chip *pcchip = to_pwm_clk_chip(chip);
int ret;
u32 rate;
u64 period = state->period;
u64 duty_cycle = state->duty_cycle;
if (!state->enabled) {
if (pwm->state.enabled) {
clk_disable(pcchip->clk);
pcchip->clk_enabled = false;
}
return 0;
} else if (!pwm->state.enabled) {
ret = clk_enable(pcchip->clk);
if (ret)
return ret;
pcchip->clk_enabled = true;
}
/*
* We have to enable the clk before setting the rate and duty_cycle,
* that however results in a window where the clk is on with a
* (potentially) different setting. Also setting period and duty_cycle
* are two separate calls, so that probably isn't atomic either.
*/
rate = DIV64_U64_ROUND_UP(NSEC_PER_SEC, period);
ret = clk_set_rate(pcchip->clk, rate);
if (ret)
return ret;
if (state->polarity == PWM_POLARITY_INVERSED)
duty_cycle = period - duty_cycle;
return clk_set_duty_cycle(pcchip->clk, duty_cycle, period);
}
static const struct pwm_ops pwm_clk_ops = {
.apply = pwm_clk_apply,
.owner = THIS_MODULE,
};
static int pwm_clk_probe(struct platform_device *pdev)
{
struct pwm_clk_chip *pcchip;
int ret;
pcchip = devm_kzalloc(&pdev->dev, sizeof(*pcchip), GFP_KERNEL);
if (!pcchip)
return -ENOMEM;
pcchip->clk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(pcchip->clk))
return dev_err_probe(&pdev->dev, PTR_ERR(pcchip->clk),
"Failed to get clock\n");
pcchip->chip.dev = &pdev->dev;
pcchip->chip.ops = &pwm_clk_ops;
pcchip->chip.npwm = 1;
ret = clk_prepare(pcchip->clk);
if (ret < 0)
return dev_err_probe(&pdev->dev, ret, "Failed to prepare clock\n");
ret = pwmchip_add(&pcchip->chip);
if (ret < 0) {
clk_unprepare(pcchip->clk);
return dev_err_probe(&pdev->dev, ret, "Failed to add pwm chip\n");
}
platform_set_drvdata(pdev, pcchip);
return 0;
}
static int pwm_clk_remove(struct platform_device *pdev)
{
struct pwm_clk_chip *pcchip = platform_get_drvdata(pdev);
pwmchip_remove(&pcchip->chip);
if (pcchip->clk_enabled)
clk_disable(pcchip->clk);
clk_unprepare(pcchip->clk);
return 0;
}
static const struct of_device_id pwm_clk_dt_ids[] = {
{ .compatible = "clk-pwm", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, pwm_clk_dt_ids);
static struct platform_driver pwm_clk_driver = {
.driver = {
.name = "pwm-clk",
.of_match_table = pwm_clk_dt_ids,
},
.probe = pwm_clk_probe,
.remove = pwm_clk_remove,
};
module_platform_driver(pwm_clk_driver);
MODULE_ALIAS("platform:pwm-clk");
MODULE_AUTHOR("Nikita Travkin <nikita@trvn.ru>");
MODULE_DESCRIPTION("Clock based PWM driver");
MODULE_LICENSE("GPL");

View File

@@ -98,7 +98,7 @@ struct lpc18xx_pwm_chip {
unsigned long clk_rate;
unsigned int period_ns;
unsigned int min_period_ns;
unsigned int max_period_ns;
u64 max_period_ns;
unsigned int period_event;
unsigned long event_map;
struct mutex res_lock;
@@ -145,40 +145,48 @@ static void lpc18xx_pwm_set_conflict_res(struct lpc18xx_pwm_chip *lpc18xx_pwm,
mutex_unlock(&lpc18xx_pwm->res_lock);
}
static void lpc18xx_pwm_config_period(struct pwm_chip *chip, int period_ns)
static void lpc18xx_pwm_config_period(struct pwm_chip *chip, u64 period_ns)
{
struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip);
u64 val;
u32 val;
val = (u64)period_ns * lpc18xx_pwm->clk_rate;
do_div(val, NSEC_PER_SEC);
/*
* With clk_rate < NSEC_PER_SEC this cannot overflow.
* With period_ns < max_period_ns this also fits into an u32.
* As period_ns >= min_period_ns = DIV_ROUND_UP(NSEC_PER_SEC, lpc18xx_pwm->clk_rate);
* we have val >= 1.
*/
val = mul_u64_u64_div_u64(period_ns, lpc18xx_pwm->clk_rate, NSEC_PER_SEC);
lpc18xx_pwm_writel(lpc18xx_pwm,
LPC18XX_PWM_MATCH(lpc18xx_pwm->period_event),
(u32)val - 1);
val - 1);
lpc18xx_pwm_writel(lpc18xx_pwm,
LPC18XX_PWM_MATCHREL(lpc18xx_pwm->period_event),
(u32)val - 1);
val - 1);
}
static void lpc18xx_pwm_config_duty(struct pwm_chip *chip,
struct pwm_device *pwm, int duty_ns)
struct pwm_device *pwm, u64 duty_ns)
{
struct lpc18xx_pwm_chip *lpc18xx_pwm = to_lpc18xx_pwm_chip(chip);
struct lpc18xx_pwm_data *lpc18xx_data = &lpc18xx_pwm->channeldata[pwm->hwpwm];
u64 val;
u32 val;
val = (u64)duty_ns * lpc18xx_pwm->clk_rate;
do_div(val, NSEC_PER_SEC);
/*
* With clk_rate < NSEC_PER_SEC this cannot overflow.
* With duty_ns <= period_ns < max_period_ns this also fits into an u32.
*/
val = mul_u64_u64_div_u64(duty_ns, lpc18xx_pwm->clk_rate, NSEC_PER_SEC);
lpc18xx_pwm_writel(lpc18xx_pwm,
LPC18XX_PWM_MATCH(lpc18xx_data->duty_event),
(u32)val);
val);
lpc18xx_pwm_writel(lpc18xx_pwm,
LPC18XX_PWM_MATCHREL(lpc18xx_data->duty_event),
(u32)val);
val);
}
static int lpc18xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm,
@@ -359,30 +367,35 @@ static int lpc18xx_pwm_probe(struct platform_device *pdev)
return PTR_ERR(lpc18xx_pwm->base);
lpc18xx_pwm->pwm_clk = devm_clk_get(&pdev->dev, "pwm");
if (IS_ERR(lpc18xx_pwm->pwm_clk)) {
dev_err(&pdev->dev, "failed to get pwm clock\n");
return PTR_ERR(lpc18xx_pwm->pwm_clk);
}
if (IS_ERR(lpc18xx_pwm->pwm_clk))
return dev_err_probe(&pdev->dev, PTR_ERR(lpc18xx_pwm->pwm_clk),
"failed to get pwm clock\n");
ret = clk_prepare_enable(lpc18xx_pwm->pwm_clk);
if (ret < 0) {
dev_err(&pdev->dev, "could not prepare or enable pwm clock\n");
return ret;
}
if (ret < 0)
return dev_err_probe(&pdev->dev, ret,
"could not prepare or enable pwm clock\n");
lpc18xx_pwm->clk_rate = clk_get_rate(lpc18xx_pwm->pwm_clk);
if (!lpc18xx_pwm->clk_rate) {
dev_err(&pdev->dev, "pwm clock has no frequency\n");
ret = -EINVAL;
ret = dev_err_probe(&pdev->dev,
-EINVAL, "pwm clock has no frequency\n");
goto disable_pwmclk;
}
/*
* If clkrate is too fast, the calculations in .apply() might overflow.
*/
if (lpc18xx_pwm->clk_rate > NSEC_PER_SEC) {
ret = dev_err_probe(&pdev->dev, -EINVAL, "pwm clock to fast\n");
goto disable_pwmclk;
}
mutex_init(&lpc18xx_pwm->res_lock);
mutex_init(&lpc18xx_pwm->period_lock);
val = (u64)NSEC_PER_SEC * LPC18XX_PWM_TIMER_MAX;
do_div(val, lpc18xx_pwm->clk_rate);
lpc18xx_pwm->max_period_ns = val;
lpc18xx_pwm->max_period_ns =
mul_u64_u64_div_u64(NSEC_PER_SEC, LPC18XX_PWM_TIMER_MAX, lpc18xx_pwm->clk_rate);
lpc18xx_pwm->min_period_ns = DIV_ROUND_UP(NSEC_PER_SEC,
lpc18xx_pwm->clk_rate);
@@ -423,7 +436,7 @@ static int lpc18xx_pwm_probe(struct platform_device *pdev)
ret = pwmchip_add(&lpc18xx_pwm->chip);
if (ret < 0) {
dev_err(&pdev->dev, "pwmchip_add failed: %d\n", ret);
dev_err_probe(&pdev->dev, ret, "pwmchip_add failed\n");
goto disable_pwmclk;
}

View File

@@ -323,6 +323,12 @@ static const struct pwm_mediatek_of_data mt8183_pwm_data = {
.has_ck_26m_sel = true,
};
static const struct pwm_mediatek_of_data mt8365_pwm_data = {
.num_pwms = 3,
.pwm45_fixup = false,
.has_ck_26m_sel = true,
};
static const struct pwm_mediatek_of_data mt8516_pwm_data = {
.num_pwms = 5,
.pwm45_fixup = false,
@@ -337,6 +343,7 @@ static const struct of_device_id pwm_mediatek_of_match[] = {
{ .compatible = "mediatek,mt7628-pwm", .data = &mt7628_pwm_data },
{ .compatible = "mediatek,mt7629-pwm", .data = &mt7629_pwm_data },
{ .compatible = "mediatek,mt8183-pwm", .data = &mt8183_pwm_data },
{ .compatible = "mediatek,mt8365-pwm", .data = &mt8365_pwm_data },
{ .compatible = "mediatek,mt8516-pwm", .data = &mt8516_pwm_data },
{ },
};

View File

@@ -23,7 +23,7 @@
#define PWM_SIFIVE_PWMCFG 0x0
#define PWM_SIFIVE_PWMCOUNT 0x8
#define PWM_SIFIVE_PWMS 0x10
#define PWM_SIFIVE_PWMCMP0 0x20
#define PWM_SIFIVE_PWMCMP(i) (0x20 + 4 * (i))
/* PWMCFG fields */
#define PWM_SIFIVE_PWMCFG_SCALE GENMASK(3, 0)
@@ -36,14 +36,12 @@
#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 mutex lock; /* lock to protect user_count and approx_period */
struct notifier_block notifier;
struct clk *clk;
void __iomem *regs;
@@ -78,6 +76,7 @@ static void pwm_sifive_free(struct pwm_chip *chip, struct pwm_device *pwm)
mutex_unlock(&ddata->lock);
}
/* Called holding ddata->lock */
static void pwm_sifive_update_clock(struct pwm_sifive_ddata *ddata,
unsigned long rate)
{
@@ -112,8 +111,7 @@ static void pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
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);
duty = readl(ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));
state->enabled = duty > 0;
@@ -127,24 +125,6 @@ static void pwm_sifive_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
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;
}
} else {
clk_disable(ddata->clk);
}
return 0;
}
static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm,
const struct pwm_state *state)
{
@@ -159,13 +139,6 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm,
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;
@@ -184,25 +157,36 @@ static int pwm_sifive_apply(struct pwm_chip *chip, struct pwm_device *pwm,
/* The hardware cannot generate a 100% duty cycle */
frac = min(frac, (1U << PWM_SIFIVE_CMPWIDTH) - 1);
mutex_lock(&ddata->lock);
if (state->period != ddata->approx_period) {
if (ddata->user_count != 1) {
ret = -EBUSY;
goto exit;
mutex_unlock(&ddata->lock);
return -EBUSY;
}
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;
/*
* If the PWM is enabled the clk is already on. So only enable it
* conditionally to have it on exactly once afterwards independent of
* the PWM state.
*/
if (!enabled) {
ret = clk_enable(ddata->clk);
if (ret) {
dev_err(ddata->chip.dev, "Enable clk failed\n");
return ret;
}
}
writel(frac, ddata->regs + PWM_SIFIVE_PWMCMP(pwm->hwpwm));
if (!state->enabled)
clk_disable(ddata->clk);
return 0;
}
static const struct pwm_ops pwm_sifive_ops = {
@@ -232,6 +216,8 @@ static int pwm_sifive_probe(struct platform_device *pdev)
struct pwm_sifive_ddata *ddata;
struct pwm_chip *chip;
int ret;
u32 val;
unsigned int enabled_pwms = 0, enabled_clks = 1;
ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
if (!ddata)
@@ -258,6 +244,33 @@ static int pwm_sifive_probe(struct platform_device *pdev)
return ret;
}
val = readl(ddata->regs + PWM_SIFIVE_PWMCFG);
if (val & PWM_SIFIVE_PWMCFG_EN_ALWAYS) {
unsigned int i;
for (i = 0; i < chip->npwm; ++i) {
val = readl(ddata->regs + PWM_SIFIVE_PWMCMP(i));
if (val > 0)
++enabled_pwms;
}
}
/* The clk should be on once for each running PWM. */
if (enabled_pwms) {
while (enabled_clks < enabled_pwms) {
/* This is not expected to fail as the clk is already on */
ret = clk_enable(ddata->clk);
if (unlikely(ret)) {
dev_err_probe(dev, ret, "Failed to enable clk\n");
goto disable_clk;
}
++enabled_clks;
}
} else {
clk_disable(ddata->clk);
enabled_clks = 0;
}
/* Watch for changes to underlying clock frequency */
ddata->notifier.notifier_call = pwm_sifive_clock_notifier;
ret = clk_notifier_register(ddata->clk, &ddata->notifier);
@@ -280,7 +293,11 @@ static int pwm_sifive_probe(struct platform_device *pdev)
unregister_clk:
clk_notifier_unregister(ddata->clk, &ddata->notifier);
disable_clk:
clk_disable_unprepare(ddata->clk);
while (enabled_clks) {
clk_disable(ddata->clk);
--enabled_clks;
}
clk_unprepare(ddata->clk);
return ret;
}
@@ -288,24 +305,20 @@ disable_clk:
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 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);
pwmchip_remove(&ddata->chip);
clk_notifier_unregister(ddata->clk, &ddata->notifier);
for (ch = 0; ch < ddata->chip.npwm; ch++) {
pwm = &ddata->chip.pwms[ch];
if (pwm->state.enabled)
clk_disable(ddata->clk);
}
clk_unprepare(ddata->clk);
return 0;
}

View File

@@ -7,6 +7,22 @@
*
* This driver is a complete rewrite of the former pwm-twl6030.c authorded by:
* Hemanth V <hemanthv@ti.com>
*
* Reference manual for the twl6030 is available at:
* https://www.ti.com/lit/ds/symlink/twl6030.pdf
*
* Limitations:
* - The twl6030 hardware only supports two period lengths (128 clock ticks and
* 64 clock ticks), the driver only uses 128 ticks
* - The hardware doesn't support ON = 0, so the active part of a period doesn't
* start at its beginning.
* - The hardware could support inverted polarity (with a similar limitation as
* for normal: the last clock tick is always inactive).
* - The hardware emits a constant low output when disabled.
* - A request for .duty_cycle = 0 results in an output wave with one active
* clock tick per period. This should better use the disabled state.
* - The driver only implements setting the relative duty cycle.
* - The driver doesn't implement .get_state().
*/
#include <linux/module.h>

View File

@@ -6,9 +6,6 @@
#include <linux/mutex.h>
#include <linux/of.h>
struct pwm_capture;
struct seq_file;
struct pwm_chip;
/**
@@ -251,6 +248,16 @@ pwm_set_relative_duty_cycle(struct pwm_state *state, unsigned int duty_cycle,
return 0;
}
/**
* struct pwm_capture - PWM capture data
* @period: period of the PWM signal (in nanoseconds)
* @duty_cycle: duty cycle of the PWM signal (in nanoseconds)
*/
struct pwm_capture {
unsigned int period;
unsigned int duty_cycle;
};
/**
* struct pwm_ops - PWM controller operations
* @request: optional hook for requesting a PWM
@@ -261,10 +268,6 @@ pwm_set_relative_duty_cycle(struct pwm_state *state, unsigned int duty_cycle,
* called once per PWM device when the PWM chip is
* registered.
* @owner: helps prevent removal of modules exporting active PWMs
* @config: configure duty cycles and period length for this PWM
* @set_polarity: configure the polarity of this PWM
* @enable: enable PWM output toggling
* @disable: disable PWM output toggling
*/
struct pwm_ops {
int (*request)(struct pwm_chip *chip, struct pwm_device *pwm);
@@ -276,14 +279,6 @@ struct pwm_ops {
void (*get_state)(struct pwm_chip *chip, struct pwm_device *pwm,
struct pwm_state *state);
struct module *owner;
/* Only used by legacy drivers */
int (*config)(struct pwm_chip *chip, struct pwm_device *pwm,
int duty_ns, int period_ns);
int (*set_polarity)(struct pwm_chip *chip, struct pwm_device *pwm,
enum pwm_polarity polarity);
int (*enable)(struct pwm_chip *chip, struct pwm_device *pwm);
void (*disable)(struct pwm_chip *chip, struct pwm_device *pwm);
};
/**
@@ -312,16 +307,6 @@ struct pwm_chip {
struct pwm_device *pwms;
};
/**
* struct pwm_capture - PWM capture data
* @period: period of the PWM signal (in nanoseconds)
* @duty_cycle: duty cycle of the PWM signal (in nanoseconds)
*/
struct pwm_capture {
unsigned int period;
unsigned int duty_cycle;
};
#if IS_ENABLED(CONFIG_PWM)
/* PWM user APIs */
struct pwm_device *pwm_request(int pwm_id, const char *label);