12132fa8dSAlexandre Pereira da Silva /* 22132fa8dSAlexandre Pereira da Silva * Copyright 2012 Alexandre Pereira da Silva <aletes.xgr@gmail.com> 32132fa8dSAlexandre Pereira da Silva * 42132fa8dSAlexandre Pereira da Silva * This program is free software; you can redistribute it and/or modify 52132fa8dSAlexandre Pereira da Silva * it under the terms of the GNU General Public License as published by 62132fa8dSAlexandre Pereira da Silva * the Free Software Foundation; version 2. 72132fa8dSAlexandre Pereira da Silva * 82132fa8dSAlexandre Pereira da Silva */ 92132fa8dSAlexandre Pereira da Silva 102132fa8dSAlexandre Pereira da Silva #include <linux/clk.h> 112132fa8dSAlexandre Pereira da Silva #include <linux/err.h> 122132fa8dSAlexandre Pereira da Silva #include <linux/io.h> 132132fa8dSAlexandre Pereira da Silva #include <linux/kernel.h> 142132fa8dSAlexandre Pereira da Silva #include <linux/module.h> 152132fa8dSAlexandre Pereira da Silva #include <linux/of.h> 162132fa8dSAlexandre Pereira da Silva #include <linux/of_address.h> 172132fa8dSAlexandre Pereira da Silva #include <linux/platform_device.h> 182132fa8dSAlexandre Pereira da Silva #include <linux/pwm.h> 192132fa8dSAlexandre Pereira da Silva #include <linux/slab.h> 202132fa8dSAlexandre Pereira da Silva 212132fa8dSAlexandre Pereira da Silva struct lpc32xx_pwm_chip { 222132fa8dSAlexandre Pereira da Silva struct pwm_chip chip; 232132fa8dSAlexandre Pereira da Silva struct clk *clk; 242132fa8dSAlexandre Pereira da Silva void __iomem *base; 252132fa8dSAlexandre Pereira da Silva }; 262132fa8dSAlexandre Pereira da Silva 275a9fc9c6SVladimir Zapolskiy #define PWM_ENABLE BIT(31) 282132fa8dSAlexandre Pereira da Silva 292132fa8dSAlexandre Pereira da Silva #define to_lpc32xx_pwm_chip(_chip) \ 302132fa8dSAlexandre Pereira da Silva container_of(_chip, struct lpc32xx_pwm_chip, chip) 312132fa8dSAlexandre Pereira da Silva 322132fa8dSAlexandre Pereira da Silva static int lpc32xx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, 332132fa8dSAlexandre Pereira da Silva int duty_ns, int period_ns) 342132fa8dSAlexandre Pereira da Silva { 352132fa8dSAlexandre Pereira da Silva struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip); 362132fa8dSAlexandre Pereira da Silva unsigned long long c; 372132fa8dSAlexandre Pereira da Silva int period_cycles, duty_cycles; 38affb923dSAxel Lin u32 val; 395a9fc9c6SVladimir Zapolskiy c = clk_get_rate(lpc32xx->clk); 402132fa8dSAlexandre Pereira da Silva 415a9fc9c6SVladimir Zapolskiy /* The highest acceptable divisor is 256, which is represented by 0 */ 425a9fc9c6SVladimir Zapolskiy period_cycles = div64_u64(c * period_ns, 435a9fc9c6SVladimir Zapolskiy (unsigned long long)NSEC_PER_SEC * 256); 44*d6dbdf0dSVladimir Zapolskiy if (!period_cycles || period_cycles > 256) 45*d6dbdf0dSVladimir Zapolskiy return -ERANGE; 46*d6dbdf0dSVladimir Zapolskiy if (period_cycles == 256) 475a9fc9c6SVladimir Zapolskiy period_cycles = 0; 482132fa8dSAlexandre Pereira da Silva 495a9fc9c6SVladimir Zapolskiy /* Compute 256 x #duty/period value and care for corner cases */ 505a9fc9c6SVladimir Zapolskiy duty_cycles = div64_u64((unsigned long long)(period_ns - duty_ns) * 256, 515a9fc9c6SVladimir Zapolskiy period_ns); 525a9fc9c6SVladimir Zapolskiy if (!duty_cycles) 535a9fc9c6SVladimir Zapolskiy duty_cycles = 1; 545a9fc9c6SVladimir Zapolskiy if (duty_cycles > 255) 555a9fc9c6SVladimir Zapolskiy duty_cycles = 255; 562132fa8dSAlexandre Pereira da Silva 57affb923dSAxel Lin val = readl(lpc32xx->base + (pwm->hwpwm << 2)); 58affb923dSAxel Lin val &= ~0xFFFF; 595a9fc9c6SVladimir Zapolskiy val |= (period_cycles << 8) | duty_cycles; 60affb923dSAxel Lin writel(val, lpc32xx->base + (pwm->hwpwm << 2)); 612132fa8dSAlexandre Pereira da Silva 622132fa8dSAlexandre Pereira da Silva return 0; 632132fa8dSAlexandre Pereira da Silva } 642132fa8dSAlexandre Pereira da Silva 652132fa8dSAlexandre Pereira da Silva static int lpc32xx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) 662132fa8dSAlexandre Pereira da Silva { 672132fa8dSAlexandre Pereira da Silva struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip); 6808ee77b5SAxel Lin u32 val; 6908ee77b5SAxel Lin int ret; 702132fa8dSAlexandre Pereira da Silva 7182aff048SVladimir Zapolskiy ret = clk_prepare_enable(lpc32xx->clk); 7208ee77b5SAxel Lin if (ret) 7308ee77b5SAxel Lin return ret; 7408ee77b5SAxel Lin 7508ee77b5SAxel Lin val = readl(lpc32xx->base + (pwm->hwpwm << 2)); 7608ee77b5SAxel Lin val |= PWM_ENABLE; 7708ee77b5SAxel Lin writel(val, lpc32xx->base + (pwm->hwpwm << 2)); 7808ee77b5SAxel Lin 7908ee77b5SAxel Lin return 0; 802132fa8dSAlexandre Pereira da Silva } 812132fa8dSAlexandre Pereira da Silva 822132fa8dSAlexandre Pereira da Silva static void lpc32xx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) 832132fa8dSAlexandre Pereira da Silva { 842132fa8dSAlexandre Pereira da Silva struct lpc32xx_pwm_chip *lpc32xx = to_lpc32xx_pwm_chip(chip); 8508ee77b5SAxel Lin u32 val; 862132fa8dSAlexandre Pereira da Silva 8708ee77b5SAxel Lin val = readl(lpc32xx->base + (pwm->hwpwm << 2)); 8808ee77b5SAxel Lin val &= ~PWM_ENABLE; 8908ee77b5SAxel Lin writel(val, lpc32xx->base + (pwm->hwpwm << 2)); 9008ee77b5SAxel Lin 9182aff048SVladimir Zapolskiy clk_disable_unprepare(lpc32xx->clk); 922132fa8dSAlexandre Pereira da Silva } 932132fa8dSAlexandre Pereira da Silva 942132fa8dSAlexandre Pereira da Silva static const struct pwm_ops lpc32xx_pwm_ops = { 952132fa8dSAlexandre Pereira da Silva .config = lpc32xx_pwm_config, 962132fa8dSAlexandre Pereira da Silva .enable = lpc32xx_pwm_enable, 972132fa8dSAlexandre Pereira da Silva .disable = lpc32xx_pwm_disable, 982132fa8dSAlexandre Pereira da Silva .owner = THIS_MODULE, 992132fa8dSAlexandre Pereira da Silva }; 1002132fa8dSAlexandre Pereira da Silva 1012132fa8dSAlexandre Pereira da Silva static int lpc32xx_pwm_probe(struct platform_device *pdev) 1022132fa8dSAlexandre Pereira da Silva { 1032132fa8dSAlexandre Pereira da Silva struct lpc32xx_pwm_chip *lpc32xx; 1042132fa8dSAlexandre Pereira da Silva struct resource *res; 1052132fa8dSAlexandre Pereira da Silva int ret; 1062132fa8dSAlexandre Pereira da Silva 1072132fa8dSAlexandre Pereira da Silva lpc32xx = devm_kzalloc(&pdev->dev, sizeof(*lpc32xx), GFP_KERNEL); 1082132fa8dSAlexandre Pereira da Silva if (!lpc32xx) 1092132fa8dSAlexandre Pereira da Silva return -ENOMEM; 1102132fa8dSAlexandre Pereira da Silva 1112132fa8dSAlexandre Pereira da Silva res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 1126d4294d1SThierry Reding lpc32xx->base = devm_ioremap_resource(&pdev->dev, res); 1136d4294d1SThierry Reding if (IS_ERR(lpc32xx->base)) 1146d4294d1SThierry Reding return PTR_ERR(lpc32xx->base); 1152132fa8dSAlexandre Pereira da Silva 1162132fa8dSAlexandre Pereira da Silva lpc32xx->clk = devm_clk_get(&pdev->dev, NULL); 1172132fa8dSAlexandre Pereira da Silva if (IS_ERR(lpc32xx->clk)) 1182132fa8dSAlexandre Pereira da Silva return PTR_ERR(lpc32xx->clk); 1192132fa8dSAlexandre Pereira da Silva 1202132fa8dSAlexandre Pereira da Silva lpc32xx->chip.dev = &pdev->dev; 1212132fa8dSAlexandre Pereira da Silva lpc32xx->chip.ops = &lpc32xx_pwm_ops; 122ebe1fca3SVladimir Zapolskiy lpc32xx->chip.npwm = 1; 1238fc6d09dSAlban Bedel lpc32xx->chip.base = -1; 1242132fa8dSAlexandre Pereira da Silva 1252132fa8dSAlexandre Pereira da Silva ret = pwmchip_add(&lpc32xx->chip); 1262132fa8dSAlexandre Pereira da Silva if (ret < 0) { 1272132fa8dSAlexandre Pereira da Silva dev_err(&pdev->dev, "failed to add PWM chip, error %d\n", ret); 1282132fa8dSAlexandre Pereira da Silva return ret; 1292132fa8dSAlexandre Pereira da Silva } 1302132fa8dSAlexandre Pereira da Silva 1312132fa8dSAlexandre Pereira da Silva platform_set_drvdata(pdev, lpc32xx); 1322132fa8dSAlexandre Pereira da Silva 1332132fa8dSAlexandre Pereira da Silva return 0; 1342132fa8dSAlexandre Pereira da Silva } 1352132fa8dSAlexandre Pereira da Silva 13677f37917SBill Pemberton static int lpc32xx_pwm_remove(struct platform_device *pdev) 1372132fa8dSAlexandre Pereira da Silva { 1382132fa8dSAlexandre Pereira da Silva struct lpc32xx_pwm_chip *lpc32xx = platform_get_drvdata(pdev); 13954b2a999SAlban Bedel unsigned int i; 1402132fa8dSAlexandre Pereira da Silva 14154b2a999SAlban Bedel for (i = 0; i < lpc32xx->chip.npwm; i++) 14254b2a999SAlban Bedel pwm_disable(&lpc32xx->chip.pwms[i]); 14354b2a999SAlban Bedel 1442132fa8dSAlexandre Pereira da Silva return pwmchip_remove(&lpc32xx->chip); 1452132fa8dSAlexandre Pereira da Silva } 1462132fa8dSAlexandre Pereira da Silva 147f1a8870aSThierry Reding static const struct of_device_id lpc32xx_pwm_dt_ids[] = { 1482132fa8dSAlexandre Pereira da Silva { .compatible = "nxp,lpc3220-pwm", }, 1492132fa8dSAlexandre Pereira da Silva { /* sentinel */ } 1502132fa8dSAlexandre Pereira da Silva }; 1512132fa8dSAlexandre Pereira da Silva MODULE_DEVICE_TABLE(of, lpc32xx_pwm_dt_ids); 1522132fa8dSAlexandre Pereira da Silva 1532132fa8dSAlexandre Pereira da Silva static struct platform_driver lpc32xx_pwm_driver = { 1542132fa8dSAlexandre Pereira da Silva .driver = { 1552132fa8dSAlexandre Pereira da Silva .name = "lpc32xx-pwm", 1563cb3b2bfSSachin Kamat .of_match_table = lpc32xx_pwm_dt_ids, 1572132fa8dSAlexandre Pereira da Silva }, 1582132fa8dSAlexandre Pereira da Silva .probe = lpc32xx_pwm_probe, 159fd109112SBill Pemberton .remove = lpc32xx_pwm_remove, 1602132fa8dSAlexandre Pereira da Silva }; 1612132fa8dSAlexandre Pereira da Silva module_platform_driver(lpc32xx_pwm_driver); 1622132fa8dSAlexandre Pereira da Silva 1632132fa8dSAlexandre Pereira da Silva MODULE_ALIAS("platform:lpc32xx-pwm"); 1642132fa8dSAlexandre Pereira da Silva MODULE_AUTHOR("Alexandre Pereira da Silva <aletes.xgr@gmail.com>"); 1652132fa8dSAlexandre Pereira da Silva MODULE_DESCRIPTION("LPC32XX PWM Driver"); 1662132fa8dSAlexandre Pereira da Silva MODULE_LICENSE("GPL v2"); 167