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