1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * Cirrus Logic CLPS711X PWM driver 4 * Author: Alexander Shiyan <shc_work@mail.ru> 5 */ 6 7 #include <linux/clk.h> 8 #include <linux/io.h> 9 #include <linux/module.h> 10 #include <linux/of.h> 11 #include <linux/platform_device.h> 12 #include <linux/pwm.h> 13 14 struct clps711x_chip { 15 struct pwm_chip chip; 16 void __iomem *pmpcon; 17 struct clk *clk; 18 spinlock_t lock; 19 }; 20 21 static inline struct clps711x_chip *to_clps711x_chip(struct pwm_chip *chip) 22 { 23 return container_of(chip, struct clps711x_chip, chip); 24 } 25 26 static void clps711x_pwm_update_val(struct clps711x_chip *priv, u32 n, u32 v) 27 { 28 /* PWM0 - bits 4..7, PWM1 - bits 8..11 */ 29 u32 shift = (n + 1) * 4; 30 unsigned long flags; 31 u32 tmp; 32 33 spin_lock_irqsave(&priv->lock, flags); 34 35 tmp = readl(priv->pmpcon); 36 tmp &= ~(0xf << shift); 37 tmp |= v << shift; 38 writel(tmp, priv->pmpcon); 39 40 spin_unlock_irqrestore(&priv->lock, flags); 41 } 42 43 static unsigned int clps711x_get_duty(struct pwm_device *pwm, unsigned int v) 44 { 45 /* Duty cycle 0..15 max */ 46 return DIV64_U64_ROUND_CLOSEST(v * 0xf, pwm->args.period); 47 } 48 49 static int clps711x_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) 50 { 51 struct clps711x_chip *priv = to_clps711x_chip(chip); 52 unsigned int freq = clk_get_rate(priv->clk); 53 54 if (!freq) 55 return -EINVAL; 56 57 /* Store constant period value */ 58 pwm->args.period = DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq); 59 60 return 0; 61 } 62 63 static int clps711x_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, 64 int duty_ns, int period_ns) 65 { 66 struct clps711x_chip *priv = to_clps711x_chip(chip); 67 unsigned int duty; 68 69 if (period_ns != pwm->args.period) 70 return -EINVAL; 71 72 duty = clps711x_get_duty(pwm, duty_ns); 73 clps711x_pwm_update_val(priv, pwm->hwpwm, duty); 74 75 return 0; 76 } 77 78 static int clps711x_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) 79 { 80 struct clps711x_chip *priv = to_clps711x_chip(chip); 81 unsigned int duty; 82 83 duty = clps711x_get_duty(pwm, pwm_get_duty_cycle(pwm)); 84 clps711x_pwm_update_val(priv, pwm->hwpwm, duty); 85 86 return 0; 87 } 88 89 static void clps711x_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) 90 { 91 struct clps711x_chip *priv = to_clps711x_chip(chip); 92 93 clps711x_pwm_update_val(priv, pwm->hwpwm, 0); 94 } 95 96 static const struct pwm_ops clps711x_pwm_ops = { 97 .request = clps711x_pwm_request, 98 .config = clps711x_pwm_config, 99 .enable = clps711x_pwm_enable, 100 .disable = clps711x_pwm_disable, 101 .owner = THIS_MODULE, 102 }; 103 104 static struct pwm_device *clps711x_pwm_xlate(struct pwm_chip *chip, 105 const struct of_phandle_args *args) 106 { 107 if (args->args[0] >= chip->npwm) 108 return ERR_PTR(-EINVAL); 109 110 return pwm_request_from_chip(chip, args->args[0], NULL); 111 } 112 113 static int clps711x_pwm_probe(struct platform_device *pdev) 114 { 115 struct clps711x_chip *priv; 116 117 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 118 if (!priv) 119 return -ENOMEM; 120 121 priv->pmpcon = devm_platform_ioremap_resource(pdev, 0); 122 if (IS_ERR(priv->pmpcon)) 123 return PTR_ERR(priv->pmpcon); 124 125 priv->clk = devm_clk_get(&pdev->dev, NULL); 126 if (IS_ERR(priv->clk)) 127 return PTR_ERR(priv->clk); 128 129 priv->chip.ops = &clps711x_pwm_ops; 130 priv->chip.dev = &pdev->dev; 131 priv->chip.base = -1; 132 priv->chip.npwm = 2; 133 priv->chip.of_xlate = clps711x_pwm_xlate; 134 priv->chip.of_pwm_n_cells = 1; 135 136 spin_lock_init(&priv->lock); 137 138 platform_set_drvdata(pdev, priv); 139 140 return pwmchip_add(&priv->chip); 141 } 142 143 static int clps711x_pwm_remove(struct platform_device *pdev) 144 { 145 struct clps711x_chip *priv = platform_get_drvdata(pdev); 146 147 return pwmchip_remove(&priv->chip); 148 } 149 150 static const struct of_device_id __maybe_unused clps711x_pwm_dt_ids[] = { 151 { .compatible = "cirrus,ep7209-pwm", }, 152 { } 153 }; 154 MODULE_DEVICE_TABLE(of, clps711x_pwm_dt_ids); 155 156 static struct platform_driver clps711x_pwm_driver = { 157 .driver = { 158 .name = "clps711x-pwm", 159 .of_match_table = of_match_ptr(clps711x_pwm_dt_ids), 160 }, 161 .probe = clps711x_pwm_probe, 162 .remove = clps711x_pwm_remove, 163 }; 164 module_platform_driver(clps711x_pwm_driver); 165 166 MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); 167 MODULE_DESCRIPTION("Cirrus Logic CLPS711X PWM driver"); 168 MODULE_LICENSE("GPL"); 169