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 19struct clps711x_chip { 20 struct pwm_chip chip; 21 void __iomem *pmpcon; 22 struct clk *clk; 23 spinlock_t lock; 24}; 25 26static inline struct clps711x_chip *to_clps711x_chip(struct pwm_chip *chip) 27{ 28 return container_of(chip, struct clps711x_chip, chip); 29} 30 31static 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 48static 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 54static 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_set_period(pwm, DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq)); 64 65 return 0; 66} 67 68static 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 83static 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 94static 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 101static 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 109static 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 118static 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 150static 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 157static const struct of_device_id __maybe_unused clps711x_pwm_dt_ids[] = { 158 { .compatible = "cirrus,clps711x-pwm", }, 159 { } 160}; 161MODULE_DEVICE_TABLE(of, clps711x_pwm_dt_ids); 162 163static struct platform_driver clps711x_pwm_driver = { 164 .driver = { 165 .name = "clps711x-pwm", 166 .owner = THIS_MODULE, 167 .of_match_table = of_match_ptr(clps711x_pwm_dt_ids), 168 }, 169 .probe = clps711x_pwm_probe, 170 .remove = clps711x_pwm_remove, 171}; 172module_platform_driver(clps711x_pwm_driver); 173 174MODULE_AUTHOR("Alexander Shiyan <shc_work@mail.ru>"); 175MODULE_DESCRIPTION("Cirrus Logic CLPS711X PWM driver"); 176MODULE_LICENSE("GPL"); 177