1/* 2 * linux/arch/unicore32/kernel/pwm.c 3 * 4 * Code specific to PKUnity SoC and UniCore ISA 5 * 6 * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> 7 * Copyright (C) 2001-2010 Guan Xuetao 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License version 2 as 11 * published by the Free Software Foundation. 12 */ 13 14#include <linux/module.h> 15#include <linux/kernel.h> 16#include <linux/platform_device.h> 17#include <linux/slab.h> 18#include <linux/err.h> 19#include <linux/clk.h> 20#include <linux/io.h> 21#include <linux/pwm.h> 22 23#include <asm/div64.h> 24#include <mach/hardware.h> 25 26struct puv3_pwm_chip { 27 struct pwm_chip chip; 28 void __iomem *base; 29 struct clk *clk; 30}; 31 32static inline struct puv3_pwm_chip *to_puv3(struct pwm_chip *chip) 33{ 34 return container_of(chip, struct puv3_pwm_chip, chip); 35} 36 37/* 38 * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE 39 * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE 40 */ 41static int puv3_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, 42 int duty_ns, int period_ns) 43{ 44 unsigned long period_cycles, prescale, pv, dc; 45 struct puv3_pwm_chip *puv3 = to_puv3(chip); 46 unsigned long long c; 47 48 c = clk_get_rate(puv3->clk); 49 c = c * period_ns; 50 do_div(c, 1000000000); 51 period_cycles = c; 52 53 if (period_cycles < 1) 54 period_cycles = 1; 55 56 prescale = (period_cycles - 1) / 1024; 57 pv = period_cycles / (prescale + 1) - 1; 58 59 if (prescale > 63) 60 return -EINVAL; 61 62 if (duty_ns == period_ns) 63 dc = OST_PWMDCCR_FDCYCLE; 64 else 65 dc = (pv + 1) * duty_ns / period_ns; 66 67 /* 68 * NOTE: the clock to PWM has to be enabled first 69 * before writing to the registers 70 */ 71 clk_prepare_enable(puv3->clk); 72 73 writel(prescale, puv3->base + OST_PWM_PWCR); 74 writel(pv - dc, puv3->base + OST_PWM_DCCR); 75 writel(pv, puv3->base + OST_PWM_PCR); 76 77 clk_disable_unprepare(puv3->clk); 78 79 return 0; 80} 81 82static int puv3_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) 83{ 84 struct puv3_pwm_chip *puv3 = to_puv3(chip); 85 86 return clk_prepare_enable(puv3->clk); 87} 88 89static void puv3_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) 90{ 91 struct puv3_pwm_chip *puv3 = to_puv3(chip); 92 93 clk_disable_unprepare(puv3->clk); 94} 95 96static const struct pwm_ops puv3_pwm_ops = { 97 .config = puv3_pwm_config, 98 .enable = puv3_pwm_enable, 99 .disable = puv3_pwm_disable, 100 .owner = THIS_MODULE, 101}; 102 103static int pwm_probe(struct platform_device *pdev) 104{ 105 struct puv3_pwm_chip *puv3; 106 struct resource *r; 107 int ret; 108 109 puv3 = devm_kzalloc(&pdev->dev, sizeof(*puv3), GFP_KERNEL); 110 if (puv3 == NULL) { 111 dev_err(&pdev->dev, "failed to allocate memory\n"); 112 return -ENOMEM; 113 } 114 115 puv3->clk = devm_clk_get(&pdev->dev, "OST_CLK"); 116 if (IS_ERR(puv3->clk)) 117 return PTR_ERR(puv3->clk); 118 119 r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 120 puv3->base = devm_ioremap_resource(&pdev->dev, r); 121 if (IS_ERR(puv3->base)) 122 return PTR_ERR(puv3->base); 123 124 puv3->chip.dev = &pdev->dev; 125 puv3->chip.ops = &puv3_pwm_ops; 126 puv3->chip.base = -1; 127 puv3->chip.npwm = 1; 128 129 ret = pwmchip_add(&puv3->chip); 130 if (ret < 0) { 131 dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); 132 return ret; 133 } 134 135 platform_set_drvdata(pdev, puv3); 136 return 0; 137} 138 139static int pwm_remove(struct platform_device *pdev) 140{ 141 struct puv3_pwm_chip *puv3 = platform_get_drvdata(pdev); 142 143 return pwmchip_remove(&puv3->chip); 144} 145 146static struct platform_driver puv3_pwm_driver = { 147 .driver = { 148 .name = "PKUnity-v3-PWM", 149 }, 150 .probe = pwm_probe, 151 .remove = pwm_remove, 152}; 153module_platform_driver(puv3_pwm_driver); 154 155MODULE_LICENSE("GPL v2"); 156