Back to home page

OSCL-LXR

 
 

    


0001 // SPDX-License-Identifier: GPL-2.0
0002 /*
0003  * Clock based PWM controller
0004  *
0005  * Copyright (c) 2021 Nikita Travkin <nikita@trvn.ru>
0006  *
0007  * This is an "adapter" driver that allows PWM consumers to use
0008  * system clocks with duty cycle control as PWM outputs.
0009  *
0010  * Limitations:
0011  * - Due to the fact that exact behavior depends on the underlying
0012  *   clock driver, various limitations are possible.
0013  * - Underlying clock may not be able to give 0% or 100% duty cycle
0014  *   (constant off or on), exact behavior will depend on the clock.
0015  * - When the PWM is disabled, the clock will be disabled as well,
0016  *   line state will depend on the clock.
0017  * - The clk API doesn't expose the necessary calls to implement
0018  *   .get_state().
0019  */
0020 
0021 #include <linux/kernel.h>
0022 #include <linux/math64.h>
0023 #include <linux/err.h>
0024 #include <linux/module.h>
0025 #include <linux/of.h>
0026 #include <linux/platform_device.h>
0027 #include <linux/clk.h>
0028 #include <linux/pwm.h>
0029 
0030 struct pwm_clk_chip {
0031     struct pwm_chip chip;
0032     struct clk *clk;
0033     bool clk_enabled;
0034 };
0035 
0036 #define to_pwm_clk_chip(_chip) container_of(_chip, struct pwm_clk_chip, chip)
0037 
0038 static int pwm_clk_apply(struct pwm_chip *chip, struct pwm_device *pwm,
0039              const struct pwm_state *state)
0040 {
0041     struct pwm_clk_chip *pcchip = to_pwm_clk_chip(chip);
0042     int ret;
0043     u32 rate;
0044     u64 period = state->period;
0045     u64 duty_cycle = state->duty_cycle;
0046 
0047     if (!state->enabled) {
0048         if (pwm->state.enabled) {
0049             clk_disable(pcchip->clk);
0050             pcchip->clk_enabled = false;
0051         }
0052         return 0;
0053     } else if (!pwm->state.enabled) {
0054         ret = clk_enable(pcchip->clk);
0055         if (ret)
0056             return ret;
0057         pcchip->clk_enabled = true;
0058     }
0059 
0060     /*
0061      * We have to enable the clk before setting the rate and duty_cycle,
0062      * that however results in a window where the clk is on with a
0063      * (potentially) different setting. Also setting period and duty_cycle
0064      * are two separate calls, so that probably isn't atomic either.
0065      */
0066 
0067     rate = DIV64_U64_ROUND_UP(NSEC_PER_SEC, period);
0068     ret = clk_set_rate(pcchip->clk, rate);
0069     if (ret)
0070         return ret;
0071 
0072     if (state->polarity == PWM_POLARITY_INVERSED)
0073         duty_cycle = period - duty_cycle;
0074 
0075     return clk_set_duty_cycle(pcchip->clk, duty_cycle, period);
0076 }
0077 
0078 static const struct pwm_ops pwm_clk_ops = {
0079     .apply = pwm_clk_apply,
0080     .owner = THIS_MODULE,
0081 };
0082 
0083 static int pwm_clk_probe(struct platform_device *pdev)
0084 {
0085     struct pwm_clk_chip *pcchip;
0086     int ret;
0087 
0088     pcchip = devm_kzalloc(&pdev->dev, sizeof(*pcchip), GFP_KERNEL);
0089     if (!pcchip)
0090         return -ENOMEM;
0091 
0092     pcchip->clk = devm_clk_get(&pdev->dev, NULL);
0093     if (IS_ERR(pcchip->clk))
0094         return dev_err_probe(&pdev->dev, PTR_ERR(pcchip->clk),
0095                      "Failed to get clock\n");
0096 
0097     pcchip->chip.dev = &pdev->dev;
0098     pcchip->chip.ops = &pwm_clk_ops;
0099     pcchip->chip.npwm = 1;
0100 
0101     ret = clk_prepare(pcchip->clk);
0102     if (ret < 0)
0103         return dev_err_probe(&pdev->dev, ret, "Failed to prepare clock\n");
0104 
0105     ret = pwmchip_add(&pcchip->chip);
0106     if (ret < 0) {
0107         clk_unprepare(pcchip->clk);
0108         return dev_err_probe(&pdev->dev, ret, "Failed to add pwm chip\n");
0109     }
0110 
0111     platform_set_drvdata(pdev, pcchip);
0112     return 0;
0113 }
0114 
0115 static int pwm_clk_remove(struct platform_device *pdev)
0116 {
0117     struct pwm_clk_chip *pcchip = platform_get_drvdata(pdev);
0118 
0119     pwmchip_remove(&pcchip->chip);
0120 
0121     if (pcchip->clk_enabled)
0122         clk_disable(pcchip->clk);
0123 
0124     clk_unprepare(pcchip->clk);
0125 
0126     return 0;
0127 }
0128 
0129 static const struct of_device_id pwm_clk_dt_ids[] = {
0130     { .compatible = "clk-pwm", },
0131     { /* sentinel */ }
0132 };
0133 MODULE_DEVICE_TABLE(of, pwm_clk_dt_ids);
0134 
0135 static struct platform_driver pwm_clk_driver = {
0136     .driver = {
0137         .name = "pwm-clk",
0138         .of_match_table = pwm_clk_dt_ids,
0139     },
0140     .probe = pwm_clk_probe,
0141     .remove = pwm_clk_remove,
0142 };
0143 module_platform_driver(pwm_clk_driver);
0144 
0145 MODULE_ALIAS("platform:pwm-clk");
0146 MODULE_AUTHOR("Nikita Travkin <nikita@trvn.ru>");
0147 MODULE_DESCRIPTION("Clock based PWM driver");
0148 MODULE_LICENSE("GPL");