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 14struct clps711x_chip { 15 struct pwm_chip chip; 16 void __iomem *pmpcon; 17 struct clk *clk; 18 spinlock_t lock; 19}; 20 21static inline struct clps711x_chip *to_clps711x_chip(struct pwm_chip *chip) 22{ 23 return container_of(chip, struct clps711x_chip, chip); 24} 25 26static 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 43static 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 49static 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 63static 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 78static 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 89static 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 96static 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 104static 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 113static int clps711x_pwm_probe(struct platform_device *pdev) 114{ 115 struct clps711x_chip *priv; 116 struct resource *res; 117 118 priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); 119 if (!priv) 120 return -ENOMEM; 121 122 res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 123 priv->pmpcon = devm_ioremap_resource(&pdev->dev, res); 124 if (IS_ERR(priv->pmpcon)) 125 return PTR_ERR(priv->pmpcon); 126 127 priv->clk = devm_clk_get(&pdev->dev, NULL); 128 if (IS_ERR(priv->clk)) 129 return PTR_ERR(priv->clk); 130 131 priv->chip.ops = &clps711x_pwm_ops; 132 priv->chip.dev = &pdev->dev; 133 priv->chip.base = -1; 134 priv->chip.npwm = 2; 135 priv->chip.of_xlate = clps711x_pwm_xlate; 136 priv->chip.of_pwm_n_cells = 1; 137 138 spin_lock_init(&priv->lock); 139 140 platform_set_drvdata(pdev, priv); 141 142 return pwmchip_add(&priv->chip); 143} 144 145static int clps711x_pwm_remove(struct platform_device *pdev) 146{ 147 struct clps711x_chip *priv = platform_get_drvdata(pdev); 148 149 return pwmchip_remove(&priv->chip); 150} 151 152static const struct of_device_id __maybe_unused clps711x_pwm_dt_ids[] = { 153 { .compatible = "cirrus,ep7209-pwm", }, 154 { } 155}; 156MODULE_DEVICE_TABLE(of, clps711x_pwm_dt_ids); 157 158static struct platform_driver clps711x_pwm_driver = { 159 .driver = { 160 .name = "clps711x-pwm", 161 .of_match_table = of_match_ptr(clps711x_pwm_dt_ids), 162 }, 163 .probe = clps711x_pwm_probe, 164 .remove = clps711x_pwm_remove, 165}; 166module_platform_driver(clps711x_pwm_driver); 167 168MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); 169MODULE_DESCRIPTION("Cirrus Logic CLPS711X PWM driver"); 170MODULE_LICENSE("GPL"); 171