mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-27 21:14:44 +08:00
d5806ac636
The .remove() callback for a platform driver returns an int which makes many driver authors wrongly assume it's possible to do error handling by returning an error code. However the value returned is (mostly) ignored and this typically results in resource leaks. To improve here there is a quest to make the remove callback return void. In the first step of this quest all drivers are converted to .remove_new() which already returns void. Trivially convert this driver from always returning zero in the remove callback to the void returning variant. Signed-off-by: Uwe Kleine-König <u.kleine-koenig@pengutronix.de> Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
147 lines
3.7 KiB
C
147 lines
3.7 KiB
C
// 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 void 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);
|
|
}
|
|
|
|
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_new = 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");
|