Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v3.12-rc2 308 lines 7.7 kB view raw
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 <linux/of_device.h> 20 21/* i.MX1 and i.MX21 share the same PWM function block: */ 22 23#define MX1_PWMC 0x00 /* PWM Control Register */ 24#define MX1_PWMS 0x04 /* PWM Sample Register */ 25#define MX1_PWMP 0x08 /* PWM Period Register */ 26 27#define MX1_PWMC_EN (1 << 4) 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_DOZEEN (1 << 24) 36#define MX3_PWMCR_WAITEN (1 << 23) 37#define MX3_PWMCR_DBGEN (1 << 22) 38#define MX3_PWMCR_CLKSRC_IPG_HIGH (2 << 16) 39#define MX3_PWMCR_CLKSRC_IPG (1 << 16) 40#define MX3_PWMCR_EN (1 << 0) 41 42struct imx_chip { 43 struct clk *clk_per; 44 struct clk *clk_ipg; 45 46 void __iomem *mmio_base; 47 48 struct pwm_chip chip; 49 50 int (*config)(struct pwm_chip *chip, 51 struct pwm_device *pwm, int duty_ns, int period_ns); 52 void (*set_enable)(struct pwm_chip *chip, bool enable); 53}; 54 55#define to_imx_chip(chip) container_of(chip, struct imx_chip, chip) 56 57static int imx_pwm_config_v1(struct pwm_chip *chip, 58 struct pwm_device *pwm, int duty_ns, int period_ns) 59{ 60 struct imx_chip *imx = to_imx_chip(chip); 61 62 /* 63 * The PWM subsystem allows for exact frequencies. However, 64 * I cannot connect a scope on my device to the PWM line and 65 * thus cannot provide the program the PWM controller 66 * exactly. Instead, I'm relying on the fact that the 67 * Bootloader (u-boot or WinCE+haret) has programmed the PWM 68 * function group already. So I'll just modify the PWM sample 69 * register to follow the ratio of duty_ns vs. period_ns 70 * accordingly. 71 * 72 * This is good enough for programming the brightness of 73 * the LCD backlight. 74 * 75 * The real implementation would divide PERCLK[0] first by 76 * both the prescaler (/1 .. /128) and then by CLKSEL 77 * (/2 .. /16). 78 */ 79 u32 max = readl(imx->mmio_base + MX1_PWMP); 80 u32 p = max * duty_ns / period_ns; 81 writel(max - p, imx->mmio_base + MX1_PWMS); 82 83 return 0; 84} 85 86static void imx_pwm_set_enable_v1(struct pwm_chip *chip, bool enable) 87{ 88 struct imx_chip *imx = to_imx_chip(chip); 89 u32 val; 90 91 val = readl(imx->mmio_base + MX1_PWMC); 92 93 if (enable) 94 val |= MX1_PWMC_EN; 95 else 96 val &= ~MX1_PWMC_EN; 97 98 writel(val, imx->mmio_base + MX1_PWMC); 99} 100 101static int imx_pwm_config_v2(struct pwm_chip *chip, 102 struct pwm_device *pwm, int duty_ns, int period_ns) 103{ 104 struct imx_chip *imx = to_imx_chip(chip); 105 unsigned long long c; 106 unsigned long period_cycles, duty_cycles, prescale; 107 u32 cr; 108 109 c = clk_get_rate(imx->clk_per); 110 c = c * period_ns; 111 do_div(c, 1000000000); 112 period_cycles = c; 113 114 prescale = period_cycles / 0x10000 + 1; 115 116 period_cycles /= prescale; 117 c = (unsigned long long)period_cycles * duty_ns; 118 do_div(c, period_ns); 119 duty_cycles = c; 120 121 /* 122 * according to imx pwm RM, the real period value should be 123 * PERIOD value in PWMPR plus 2. 124 */ 125 if (period_cycles > 2) 126 period_cycles -= 2; 127 else 128 period_cycles = 0; 129 130 writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); 131 writel(period_cycles, imx->mmio_base + MX3_PWMPR); 132 133 cr = MX3_PWMCR_PRESCALER(prescale) | 134 MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN | 135 MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH; 136 137 if (test_bit(PWMF_ENABLED, &pwm->flags)) 138 cr |= MX3_PWMCR_EN; 139 140 writel(cr, imx->mmio_base + MX3_PWMCR); 141 142 return 0; 143} 144 145static void imx_pwm_set_enable_v2(struct pwm_chip *chip, bool enable) 146{ 147 struct imx_chip *imx = to_imx_chip(chip); 148 u32 val; 149 150 val = readl(imx->mmio_base + MX3_PWMCR); 151 152 if (enable) 153 val |= MX3_PWMCR_EN; 154 else 155 val &= ~MX3_PWMCR_EN; 156 157 writel(val, imx->mmio_base + MX3_PWMCR); 158} 159 160static int imx_pwm_config(struct pwm_chip *chip, 161 struct pwm_device *pwm, int duty_ns, int period_ns) 162{ 163 struct imx_chip *imx = to_imx_chip(chip); 164 int ret; 165 166 ret = clk_prepare_enable(imx->clk_ipg); 167 if (ret) 168 return ret; 169 170 ret = imx->config(chip, pwm, duty_ns, period_ns); 171 172 clk_disable_unprepare(imx->clk_ipg); 173 174 return ret; 175} 176 177static int imx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) 178{ 179 struct imx_chip *imx = to_imx_chip(chip); 180 int ret; 181 182 ret = clk_prepare_enable(imx->clk_per); 183 if (ret) 184 return ret; 185 186 imx->set_enable(chip, true); 187 188 return 0; 189} 190 191static void imx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) 192{ 193 struct imx_chip *imx = to_imx_chip(chip); 194 195 imx->set_enable(chip, false); 196 197 clk_disable_unprepare(imx->clk_per); 198} 199 200static struct pwm_ops imx_pwm_ops = { 201 .enable = imx_pwm_enable, 202 .disable = imx_pwm_disable, 203 .config = imx_pwm_config, 204 .owner = THIS_MODULE, 205}; 206 207struct imx_pwm_data { 208 int (*config)(struct pwm_chip *chip, 209 struct pwm_device *pwm, int duty_ns, int period_ns); 210 void (*set_enable)(struct pwm_chip *chip, bool enable); 211}; 212 213static struct imx_pwm_data imx_pwm_data_v1 = { 214 .config = imx_pwm_config_v1, 215 .set_enable = imx_pwm_set_enable_v1, 216}; 217 218static struct imx_pwm_data imx_pwm_data_v2 = { 219 .config = imx_pwm_config_v2, 220 .set_enable = imx_pwm_set_enable_v2, 221}; 222 223static const struct of_device_id imx_pwm_dt_ids[] = { 224 { .compatible = "fsl,imx1-pwm", .data = &imx_pwm_data_v1, }, 225 { .compatible = "fsl,imx27-pwm", .data = &imx_pwm_data_v2, }, 226 { /* sentinel */ } 227}; 228MODULE_DEVICE_TABLE(of, imx_pwm_dt_ids); 229 230static int imx_pwm_probe(struct platform_device *pdev) 231{ 232 const struct of_device_id *of_id = 233 of_match_device(imx_pwm_dt_ids, &pdev->dev); 234 const struct imx_pwm_data *data; 235 struct imx_chip *imx; 236 struct resource *r; 237 int ret = 0; 238 239 if (!of_id) 240 return -ENODEV; 241 242 imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL); 243 if (imx == NULL) { 244 dev_err(&pdev->dev, "failed to allocate memory\n"); 245 return -ENOMEM; 246 } 247 248 imx->clk_per = devm_clk_get(&pdev->dev, "per"); 249 if (IS_ERR(imx->clk_per)) { 250 dev_err(&pdev->dev, "getting per clock failed with %ld\n", 251 PTR_ERR(imx->clk_per)); 252 return PTR_ERR(imx->clk_per); 253 } 254 255 imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); 256 if (IS_ERR(imx->clk_ipg)) { 257 dev_err(&pdev->dev, "getting ipg clock failed with %ld\n", 258 PTR_ERR(imx->clk_ipg)); 259 return PTR_ERR(imx->clk_ipg); 260 } 261 262 imx->chip.ops = &imx_pwm_ops; 263 imx->chip.dev = &pdev->dev; 264 imx->chip.base = -1; 265 imx->chip.npwm = 1; 266 267 r = platform_get_resource(pdev, IORESOURCE_MEM, 0); 268 imx->mmio_base = devm_ioremap_resource(&pdev->dev, r); 269 if (IS_ERR(imx->mmio_base)) 270 return PTR_ERR(imx->mmio_base); 271 272 data = of_id->data; 273 imx->config = data->config; 274 imx->set_enable = data->set_enable; 275 276 ret = pwmchip_add(&imx->chip); 277 if (ret < 0) 278 return ret; 279 280 platform_set_drvdata(pdev, imx); 281 return 0; 282} 283 284static int imx_pwm_remove(struct platform_device *pdev) 285{ 286 struct imx_chip *imx; 287 288 imx = platform_get_drvdata(pdev); 289 if (imx == NULL) 290 return -ENODEV; 291 292 return pwmchip_remove(&imx->chip); 293} 294 295static struct platform_driver imx_pwm_driver = { 296 .driver = { 297 .name = "imx-pwm", 298 .owner = THIS_MODULE, 299 .of_match_table = of_match_ptr(imx_pwm_dt_ids), 300 }, 301 .probe = imx_pwm_probe, 302 .remove = imx_pwm_remove, 303}; 304 305module_platform_driver(imx_pwm_driver); 306 307MODULE_LICENSE("GPL v2"); 308MODULE_AUTHOR("Sascha Hauer <s.hauer@pengutronix.de>");