1/* 2 * simple driver for PWM (Pulse Width Modulator) controller 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License version 2 as 6 * published by the Free Software Foundation. 7 * 8 * Derived from pxa PWM driver by eric miao <eric.miao@marvell.com> 9 */ 10 11#include <linux/module.h> 12#include <linux/kernel.h> 13#include <linux/platform_device.h> 14#include <linux/slab.h> 15#include <linux/err.h> 16#include <linux/clk.h> 17#include <linux/io.h> 18#include <linux/pwm.h> 19#include <mach/hardware.h> 20 21 22/* i.MX1 and i.MX21 share the same PWM function block: */ 23 24#define MX1_PWMC 0x00 /* PWM Control Register */ 25#define MX1_PWMS 0x04 /* PWM Sample Register */ 26#define MX1_PWMP 0x08 /* PWM Period Register */ 27 28 29/* i.MX27, i.MX31, i.MX35 share the same PWM function block: */ 30 31#define MX3_PWMCR 0x00 /* PWM Control Register */ 32#define MX3_PWMSAR 0x0C /* PWM Sample Register */ 33#define MX3_PWMPR 0x10 /* PWM Period Register */ 34#define MX3_PWMCR_PRESCALER(x) (((x - 1) & 0xFFF) << 4) 35#define MX3_PWMCR_CLKSRC_IPG_HIGH (2 << 16) 36#define MX3_PWMCR_CLKSRC_IPG (1 << 16) 37#define MX3_PWMCR_EN (1 << 0) 38 39 40 41struct pwm_device { 42 struct list_head node; 43 struct platform_device *pdev; 44 45 const char *label; 46 struct clk *clk; 47 48 int clk_enabled; 49 void __iomem *mmio_base; 50 51 unsigned int use_count; 52 unsigned int pwm_id; 53}; 54 55int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) 56{ 57 if (pwm == NULL || period_ns == 0 || duty_ns > period_ns) 58 return -EINVAL; 59 60 if (cpu_is_mx27() || cpu_is_mx3() || cpu_is_mx25()) { 61 unsigned long long c; 62 unsigned long period_cycles, duty_cycles, prescale; 63 u32 cr; 64 65 c = clk_get_rate(pwm->clk); 66 c = c * period_ns; 67 do_div(c, 1000000000); 68 period_cycles = c; 69 70 prescale = period_cycles / 0x10000 + 1; 71 72 period_cycles /= prescale; 73 c = (unsigned long long)period_cycles * duty_ns; 74 do_div(c, period_ns); 75 duty_cycles = c; 76 77 writel(duty_cycles, pwm->mmio_base + MX3_PWMSAR); 78 writel(period_cycles, pwm->mmio_base + MX3_PWMPR); 79 80 cr = MX3_PWMCR_PRESCALER(prescale) | MX3_PWMCR_EN; 81 82 if (cpu_is_mx25()) 83 cr |= MX3_PWMCR_CLKSRC_IPG; 84 else 85 cr |= MX3_PWMCR_CLKSRC_IPG_HIGH; 86 87 writel(cr, pwm->mmio_base + MX3_PWMCR); 88 } else if (cpu_is_mx1() || cpu_is_mx21()) { 89 /* The PWM subsystem allows for exact frequencies. However, 90 * I cannot connect a scope on my device to the PWM line and 91 * thus cannot provide the program the PWM controller 92 * exactly. Instead, I'm relying on the fact that the 93 * Bootloader (u-boot or WinCE+haret) has programmed the PWM 94 * function group already. So I'll just modify the PWM sample 95 * register to follow the ratio of duty_ns vs. period_ns 96 * accordingly. 97 * 98 * This is good enough for programming the brightness of 99 * the LCD backlight. 100 * 101 * The real implementation would divide PERCLK[0] first by 102 * both the prescaler (/1 .. /128) and then by CLKSEL 103 * (/2 .. /16). 104 */ 105 u32 max = readl(pwm->mmio_base + MX1_PWMP); 106 u32 p = max * duty_ns / period_ns; 107 writel(max - p, pwm->mmio_base + MX1_PWMS); 108 } else { 109 BUG(); 110 } 111 112 return 0; 113} 114EXPORT_SYMBOL(pwm_config); 115 116int pwm_enable(struct pwm_device *pwm) 117{ 118 int rc = 0; 119 120 if (!pwm->clk_enabled) { 121 rc = clk_enable(pwm->clk); 122 if (!rc) 123 pwm->clk_enabled = 1; 124 } 125 return rc; 126} 127EXPORT_SYMBOL(pwm_enable); 128 129void pwm_disable(struct pwm_device *pwm) 130{ 131 writel(0, pwm->mmio_base + MX3_PWMCR); 132 133 if (pwm->clk_enabled) { 134 clk_disable(pwm->clk); 135 pwm->clk_enabled = 0; 136 } 137} 138EXPORT_SYMBOL(pwm_disable); 139 140static DEFINE_MUTEX(pwm_lock); 141static LIST_HEAD(pwm_list); 142 143struct pwm_device *pwm_request(int pwm_id, const char *label) 144{ 145 struct pwm_device *pwm; 146 int found = 0; 147 148 mutex_lock(&pwm_lock); 149 150 list_for_each_entry(pwm, &pwm_list, node) { 151 if (pwm->pwm_id == pwm_id) { 152 found = 1; 153 break; 154 } 155 } 156 157 if (found) { 158 if (pwm->use_count == 0) { 159 pwm->use_count++; 160 pwm->label = label; 161 } else 162 pwm = ERR_PTR(-EBUSY); 163 } else 164 pwm = ERR_PTR(-ENOENT); 165 166 mutex_unlock(&pwm_lock); 167 return pwm; 168} 169EXPORT_SYMBOL(pwm_request); 170 171void pwm_free(struct pwm_device *pwm) 172{ 173 mutex_lock(&pwm_lock); 174 175 if (pwm->use_count) { 176 pwm->use_count--; 177 pwm->label = NULL; 178 } else 179 pr_warning("PWM device already freed\n"); 180 181 mutex_unlock(&pwm_lock); 182} 183EXPORT_SYMBOL(pwm_free); 184 185static int __devinit mxc_pwm_probe(struct platform_device *pdev) 186{ 187 struct pwm_device *pwm; 188 struct resource *r; 189 int ret = 0; 190 191 pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); 192 if (pwm == NULL) { 193 dev_err(&pdev->dev, "failed to allocate memory\n"); 194 return -ENOMEM; 195 } 196 197 pwm->clk = clk_get(&pdev->dev, "pwm"); 198 199 if (IS_ERR(pwm->clk)) { 200 ret = PTR_ERR(pwm->clk); 201 goto err_free; 202 } 203 204 pwm->clk_enabled = 0; 205 206 pwm->use_count = 0; 207 pwm->pwm_id = pdev->id; 208 pwm->pdev = pdev; 209 210 r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 211 if (r == NULL) { 212 dev_err(&pdev->dev, "no memory resource defined\n"); 213 ret = -ENODEV; 214 goto err_free_clk; 215 } 216 217 r = request_mem_region(r->start, r->end - r->start + 1, pdev->name); 218 if (r == NULL) { 219 dev_err(&pdev->dev, "failed to request memory resource\n"); 220 ret = -EBUSY; 221 goto err_free_clk; 222 } 223 224 pwm->mmio_base = ioremap(r->start, r->end - r->start + 1); 225 if (pwm->mmio_base == NULL) { 226 dev_err(&pdev->dev, "failed to ioremap() registers\n"); 227 ret = -ENODEV; 228 goto err_free_mem; 229 } 230 231 mutex_lock(&pwm_lock); 232 list_add_tail(&pwm->node, &pwm_list); 233 mutex_unlock(&pwm_lock); 234 235 platform_set_drvdata(pdev, pwm); 236 return 0; 237 238err_free_mem: 239 release_mem_region(r->start, r->end - r->start + 1); 240err_free_clk: 241 clk_put(pwm->clk); 242err_free: 243 kfree(pwm); 244 return ret; 245} 246 247static int __devexit mxc_pwm_remove(struct platform_device *pdev) 248{ 249 struct pwm_device *pwm; 250 struct resource *r; 251 252 pwm = platform_get_drvdata(pdev); 253 if (pwm == NULL) 254 return -ENODEV; 255 256 mutex_lock(&pwm_lock); 257 list_del(&pwm->node); 258 mutex_unlock(&pwm_lock); 259 260 iounmap(pwm->mmio_base); 261 262 r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 263 release_mem_region(r->start, r->end - r->start + 1); 264 265 clk_put(pwm->clk); 266 267 kfree(pwm); 268 return 0; 269} 270 271static struct platform_driver mxc_pwm_driver = { 272 .driver = { 273 .name = "mxc_pwm", 274 }, 275 .probe = mxc_pwm_probe, 276 .remove = __devexit_p(mxc_pwm_remove), 277}; 278 279static int __init mxc_pwm_init(void) 280{ 281 return platform_driver_register(&mxc_pwm_driver); 282} 283arch_initcall(mxc_pwm_init); 284 285static void __exit mxc_pwm_exit(void) 286{ 287 platform_driver_unregister(&mxc_pwm_driver); 288} 289module_exit(mxc_pwm_exit); 290 291MODULE_LICENSE("GPL v2"); 292MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>"); 293