1 // SPDX-License-Identifier: GPL-2.0 2 /* 3 * Clock based PWM controller 4 * 5 * Copyright (c) 2021 Nikita Travkin <nikita@trvn.ru> 6 * 7 * This is an "adapter" driver that allows PWM consumers to use 8 * system clocks with duty cycle control as PWM outputs. 9 * 10 * Limitations: 11 * - Due to the fact that exact behavior depends on the underlying 12 * clock driver, various limitations are possible. 13 * - Underlying clock may not be able to give 0% or 100% duty cycle 14 * (constant off or on), exact behavior will depend on the clock. 15 * - When the PWM is disabled, the clock will be disabled as well, 16 * line state will depend on the clock. 17 * - The clk API doesn't expose the necessary calls to implement 18 * .get_state(). 19 */ 20 21 #include <linux/kernel.h> 22 #include <linux/math64.h> 23 #include <linux/err.h> 24 #include <linux/module.h> 25 #include <linux/of.h> 26 #include <linux/platform_device.h> 27 #include <linux/clk.h> 28 #include <linux/pwm.h> 29 30 struct pwm_clk_chip { 31 struct pwm_chip chip; 32 struct clk *clk; 33 bool clk_enabled; 34 }; 35 36 #define to_pwm_clk_chip(_chip) container_of(_chip, struct pwm_clk_chip, chip) 37 38 static int pwm_clk_apply(struct pwm_chip *chip, struct pwm_device *pwm, 39 const struct pwm_state *state) 40 { 41 struct pwm_clk_chip *pcchip = to_pwm_clk_chip(chip); 42 int ret; 43 u32 rate; 44 u64 period = state->period; 45 u64 duty_cycle = state->duty_cycle; 46 47 if (!state->enabled) { 48 if (pwm->state.enabled) { 49 clk_disable(pcchip->clk); 50 pcchip->clk_enabled = false; 51 } 52 return 0; 53 } else if (!pwm->state.enabled) { 54 ret = clk_enable(pcchip->clk); 55 if (ret) 56 return ret; 57 pcchip->clk_enabled = true; 58 } 59 60 /* 61 * We have to enable the clk before setting the rate and duty_cycle, 62 * that however results in a window where the clk is on with a 63 * (potentially) different setting. Also setting period and duty_cycle 64 * are two separate calls, so that probably isn't atomic either. 65 */ 66 67 rate = DIV64_U64_ROUND_UP(NSEC_PER_SEC, period); 68 ret = clk_set_rate(pcchip->clk, rate); 69 if (ret) 70 return ret; 71 72 if (state->polarity == PWM_POLARITY_INVERSED) 73 duty_cycle = period - duty_cycle; 74 75 return clk_set_duty_cycle(pcchip->clk, duty_cycle, period); 76 } 77 78 static const struct pwm_ops pwm_clk_ops = { 79 .apply = pwm_clk_apply, 80 .owner = THIS_MODULE, 81 }; 82 83 static int pwm_clk_probe(struct platform_device *pdev) 84 { 85 struct pwm_clk_chip *pcchip; 86 int ret; 87 88 pcchip = devm_kzalloc(&pdev->dev, sizeof(*pcchip), GFP_KERNEL); 89 if (!pcchip) 90 return -ENOMEM; 91 92 pcchip->clk = devm_clk_get(&pdev->dev, NULL); 93 if (IS_ERR(pcchip->clk)) 94 return dev_err_probe(&pdev->dev, PTR_ERR(pcchip->clk), 95 "Failed to get clock\n"); 96 97 pcchip->chip.dev = &pdev->dev; 98 pcchip->chip.ops = &pwm_clk_ops; 99 pcchip->chip.npwm = 1; 100 101 ret = clk_prepare(pcchip->clk); 102 if (ret < 0) 103 return dev_err_probe(&pdev->dev, ret, "Failed to prepare clock\n"); 104 105 ret = pwmchip_add(&pcchip->chip); 106 if (ret < 0) { 107 clk_unprepare(pcchip->clk); 108 return dev_err_probe(&pdev->dev, ret, "Failed to add pwm chip\n"); 109 } 110 111 platform_set_drvdata(pdev, pcchip); 112 return 0; 113 } 114 115 static int pwm_clk_remove(struct platform_device *pdev) 116 { 117 struct pwm_clk_chip *pcchip = platform_get_drvdata(pdev); 118 119 pwmchip_remove(&pcchip->chip); 120 121 if (pcchip->clk_enabled) 122 clk_disable(pcchip->clk); 123 124 clk_unprepare(pcchip->clk); 125 126 return 0; 127 } 128 129 static const struct of_device_id pwm_clk_dt_ids[] = { 130 { .compatible = "clk-pwm", }, 131 { /* sentinel */ } 132 }; 133 MODULE_DEVICE_TABLE(of, pwm_clk_dt_ids); 134 135 static struct platform_driver pwm_clk_driver = { 136 .driver = { 137 .name = "pwm-clk", 138 .of_match_table = pwm_clk_dt_ids, 139 }, 140 .probe = pwm_clk_probe, 141 .remove = pwm_clk_remove, 142 }; 143 module_platform_driver(pwm_clk_driver); 144 145 MODULE_ALIAS("platform:pwm-clk"); 146 MODULE_AUTHOR("Nikita Travkin <nikita@trvn.ru>"); 147 MODULE_DESCRIPTION("Clock based PWM driver"); 148 MODULE_LICENSE("GPL"); 149