From 215c29d3d0e925189ade522d1ea6052a320d7692 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 15 Mar 2012 10:04:36 +0100 Subject: ARM Samsung: Move s3c pwm driver to pwm framework MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the driver to drivers/pwm/ and convert it to use the framework. Signed-off-by: Sascha Hauer Cc: Ben Dooks Cc: Kukjin Kim [eric@eukrea.com: fix pwmchip_add return code test] Signed-off-by: Eric Bénard Signed-off-by: Thierry Reding --- drivers/pwm/pwm-samsung.c | 366 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 366 insertions(+) create mode 100644 drivers/pwm/pwm-samsung.c (limited to 'drivers/pwm/pwm-samsung.c') diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c new file mode 100644 index 000000000000..c40c37e968e6 --- /dev/null +++ b/drivers/pwm/pwm-samsung.c @@ -0,0 +1,366 @@ +/* arch/arm/plat-s3c/pwm.c + * + * Copyright (c) 2007 Ben Dooks + * Copyright (c) 2008 Simtec Electronics + * Ben Dooks , + * + * S3C series PWM device core + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +struct s3c_chip { + struct platform_device *pdev; + + struct clk *clk_div; + struct clk *clk; + const char *label; + + unsigned int period_ns; + unsigned int duty_ns; + + unsigned char tcon_base; + unsigned char pwm_id; + struct pwm_chip chip; +}; + +#define to_s3c_chip(chip) container_of(chip, struct s3c_chip, chip) + +#define pwm_dbg(_pwm, msg...) dev_dbg(&(_pwm)->pdev->dev, msg) + +static struct clk *clk_scaler[2]; + +static inline int pwm_is_tdiv(struct s3c_chip *chip) +{ + return clk_get_parent(chip->clk) == chip->clk_div; +} + +#define pwm_tcon_start(pwm) (1 << (pwm->tcon_base + 0)) +#define pwm_tcon_invert(pwm) (1 << (pwm->tcon_base + 2)) +#define pwm_tcon_autoreload(pwm) (1 << (pwm->tcon_base + 3)) +#define pwm_tcon_manulupdate(pwm) (1 << (pwm->tcon_base + 1)) + +static int s3c_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct s3c_chip *s3c = to_s3c_chip(chip); + unsigned long flags; + unsigned long tcon; + + local_irq_save(flags); + + tcon = __raw_readl(S3C2410_TCON); + tcon |= pwm_tcon_start(s3c); + __raw_writel(tcon, S3C2410_TCON); + + local_irq_restore(flags); + + return 0; +} + +static void s3c_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct s3c_chip *s3c = to_s3c_chip(chip); + unsigned long flags; + unsigned long tcon; + + local_irq_save(flags); + + tcon = __raw_readl(S3C2410_TCON); + tcon &= ~pwm_tcon_start(s3c); + __raw_writel(tcon, S3C2410_TCON); + + local_irq_restore(flags); +} + +static unsigned long pwm_calc_tin(struct s3c_chip *s3c, unsigned long freq) +{ + unsigned long tin_parent_rate; + unsigned int div; + + tin_parent_rate = clk_get_rate(clk_get_parent(s3c->clk_div)); + pwm_dbg(s3c, "tin parent at %lu\n", tin_parent_rate); + + for (div = 2; div <= 16; div *= 2) { + if ((tin_parent_rate / (div << 16)) < freq) + return tin_parent_rate / div; + } + + return tin_parent_rate / 16; +} + +#define NS_IN_HZ (1000000000UL) + +static int s3c_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct s3c_chip *s3c = to_s3c_chip(chip); + unsigned long tin_rate; + unsigned long tin_ns; + unsigned long period; + unsigned long flags; + unsigned long tcon; + unsigned long tcnt; + long tcmp; + + /* We currently avoid using 64bit arithmetic by using the + * fact that anything faster than 1Hz is easily representable + * by 32bits. */ + + if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ) + return -ERANGE; + + if (duty_ns > period_ns) + return -EINVAL; + + if (period_ns == s3c->period_ns && + duty_ns == s3c->duty_ns) + return 0; + + /* The TCMP and TCNT can be read without a lock, they're not + * shared between the timers. */ + + tcmp = __raw_readl(S3C2410_TCMPB(s3c->pwm_id)); + tcnt = __raw_readl(S3C2410_TCNTB(s3c->pwm_id)); + + period = NS_IN_HZ / period_ns; + + pwm_dbg(s3c, "duty_ns=%d, period_ns=%d (%lu)\n", + duty_ns, period_ns, period); + + /* Check to see if we are changing the clock rate of the PWM */ + + if (s3c->period_ns != period_ns) { + if (pwm_is_tdiv(s3c)) { + tin_rate = pwm_calc_tin(s3c, period); + clk_set_rate(s3c->clk_div, tin_rate); + } else + tin_rate = clk_get_rate(s3c->clk); + + s3c->period_ns = period_ns; + + pwm_dbg(s3c, "tin_rate=%lu\n", tin_rate); + + tin_ns = NS_IN_HZ / tin_rate; + tcnt = period_ns / tin_ns; + } else + tin_ns = NS_IN_HZ / clk_get_rate(s3c->clk); + + /* Note, counters count down */ + + tcmp = duty_ns / tin_ns; + tcmp = tcnt - tcmp; + /* the pwm hw only checks the compare register after a decrement, + so the pin never toggles if tcmp = tcnt */ + if (tcmp == tcnt) + tcmp--; + + pwm_dbg(s3c, "tin_ns=%lu, tcmp=%ld/%lu\n", tin_ns, tcmp, tcnt); + + if (tcmp < 0) + tcmp = 0; + + /* Update the PWM register block. */ + + local_irq_save(flags); + + __raw_writel(tcmp, S3C2410_TCMPB(s3c->pwm_id)); + __raw_writel(tcnt, S3C2410_TCNTB(s3c->pwm_id)); + + tcon = __raw_readl(S3C2410_TCON); + tcon |= pwm_tcon_manulupdate(s3c); + tcon |= pwm_tcon_autoreload(s3c); + __raw_writel(tcon, S3C2410_TCON); + + tcon &= ~pwm_tcon_manulupdate(s3c); + __raw_writel(tcon, S3C2410_TCON); + + local_irq_restore(flags); + + return 0; +} + +static struct pwm_ops s3c_pwm_ops = { + .enable = s3c_pwm_enable, + .disable = s3c_pwm_disable, + .config = s3c_pwm_config, + .owner = THIS_MODULE, +}; + +static int s3c_pwm_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct s3c_chip *s3c; + unsigned long flags; + unsigned long tcon; + unsigned int id = pdev->id; + int ret; + + if (id == 4) { + dev_err(dev, "TIMER4 is currently not supported\n"); + return -ENXIO; + } + + s3c = kzalloc(sizeof(*s3c), GFP_KERNEL); + if (s3c == NULL) { + dev_err(dev, "failed to allocate pwm_device\n"); + return -ENOMEM; + } + + /* calculate base of control bits in TCON */ + s3c->tcon_base = id == 0 ? 0 : (id * 4) + 4; + s3c->chip.ops = &s3c_pwm_ops; + s3c->chip.base = -1; + s3c->chip.npwm = 1; + + s3c->clk = clk_get(dev, "pwm-tin"); + if (IS_ERR(s3c->clk)) { + dev_err(dev, "failed to get pwm tin clk\n"); + ret = PTR_ERR(s3c->clk); + goto err_alloc; + } + + s3c->clk_div = clk_get(dev, "pwm-tdiv"); + if (IS_ERR(s3c->clk_div)) { + dev_err(dev, "failed to get pwm tdiv clk\n"); + ret = PTR_ERR(s3c->clk_div); + goto err_clk_tin; + } + + clk_enable(s3c->clk); + clk_enable(s3c->clk_div); + + local_irq_save(flags); + + tcon = __raw_readl(S3C2410_TCON); + tcon |= pwm_tcon_invert(s3c); + __raw_writel(tcon, S3C2410_TCON); + + local_irq_restore(flags); + + ret = pwmchip_add(&s3c->chip); + if (ret < 0) { + dev_err(dev, "failed to register pwm\n"); + goto err_clk_tdiv; + } + + pwm_dbg(s3c, "config bits %02x\n", + (__raw_readl(S3C2410_TCON) >> s3c->tcon_base) & 0x0f); + + dev_info(dev, "tin at %lu, tdiv at %lu, tin=%sclk, base %d\n", + clk_get_rate(s3c->clk), + clk_get_rate(s3c->clk_div), + pwm_is_tdiv(s3c) ? "div" : "ext", s3c->tcon_base); + + platform_set_drvdata(pdev, s3c); + return 0; + + err_clk_tdiv: + clk_disable(s3c->clk_div); + clk_disable(s3c->clk); + clk_put(s3c->clk_div); + + err_clk_tin: + clk_put(s3c->clk); + + err_alloc: + kfree(s3c); + return ret; +} + +static int __devexit s3c_pwm_remove(struct platform_device *pdev) +{ + struct s3c_chip *s3c = platform_get_drvdata(pdev); + int err; + + err = pwmchip_remove(&s3c->chip); + if (err < 0) + return err; + + clk_disable(s3c->clk_div); + clk_disable(s3c->clk); + clk_put(s3c->clk_div); + clk_put(s3c->clk); + kfree(s3c); + + return 0; +} + +#ifdef CONFIG_PM +static int s3c_pwm_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct s3c_chip *s3c = platform_get_drvdata(pdev); + + /* No one preserve these values during suspend so reset them + * Otherwise driver leaves PWM unconfigured if same values + * passed to pwm_config + */ + s3c->period_ns = 0; + s3c->duty_ns = 0; + + return 0; +} + +static int s3c_pwm_resume(struct platform_device *pdev) +{ + struct s3c_chip *s3c = platform_get_drvdata(pdev); + unsigned long tcon; + + /* Restore invertion */ + tcon = __raw_readl(S3C2410_TCON); + tcon |= pwm_tcon_invert(s3c); + __raw_writel(tcon, S3C2410_TCON); + + return 0; +} + +#else +#define s3c_pwm_suspend NULL +#define s3c_pwm_resume NULL +#endif + +static struct platform_driver s3c_pwm_driver = { + .driver = { + .name = "s3c24xx-pwm", + .owner = THIS_MODULE, + }, + .probe = s3c_pwm_probe, + .remove = __devexit_p(s3c_pwm_remove), + .suspend = s3c_pwm_suspend, + .resume = s3c_pwm_resume, +}; + +static int __init pwm_init(void) +{ + int ret; + + clk_scaler[0] = clk_get(NULL, "pwm-scaler0"); + clk_scaler[1] = clk_get(NULL, "pwm-scaler1"); + + if (IS_ERR(clk_scaler[0]) || IS_ERR(clk_scaler[1])) { + printk(KERN_ERR "%s: failed to get scaler clocks\n", __func__); + return -EINVAL; + } + + ret = platform_driver_register(&s3c_pwm_driver); + if (ret) + printk(KERN_ERR "%s: failed to add pwm driver\n", __func__); + + return ret; +} + +arch_initcall(pwm_init); -- cgit v1.2.2 From 6192fa87447ed3c6c4005be640a088fa8e5733f8 Mon Sep 17 00:00:00 2001 From: Axel Lin Date: Fri, 29 Jun 2012 21:32:15 +0800 Subject: pwm: Convert pwm-samsung to use devm_* APIs Signed-off-by: Axel Lin Signed-off-by: Thierry Reding --- drivers/pwm/pwm-samsung.c | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) (limited to 'drivers/pwm/pwm-samsung.c') diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c index c40c37e968e6..35fa0e8b0f87 100644 --- a/drivers/pwm/pwm-samsung.c +++ b/drivers/pwm/pwm-samsung.c @@ -1,4 +1,4 @@ -/* arch/arm/plat-s3c/pwm.c +/* drivers/pwm/pwm-samsung.c * * Copyright (c) 2007 Ben Dooks * Copyright (c) 2008 Simtec Electronics @@ -215,7 +215,7 @@ static int s3c_pwm_probe(struct platform_device *pdev) return -ENXIO; } - s3c = kzalloc(sizeof(*s3c), GFP_KERNEL); + s3c = devm_kzalloc(&pdev->dev, sizeof(*s3c), GFP_KERNEL); if (s3c == NULL) { dev_err(dev, "failed to allocate pwm_device\n"); return -ENOMEM; @@ -227,18 +227,16 @@ static int s3c_pwm_probe(struct platform_device *pdev) s3c->chip.base = -1; s3c->chip.npwm = 1; - s3c->clk = clk_get(dev, "pwm-tin"); + s3c->clk = devm_clk_get(dev, "pwm-tin"); if (IS_ERR(s3c->clk)) { dev_err(dev, "failed to get pwm tin clk\n"); - ret = PTR_ERR(s3c->clk); - goto err_alloc; + return PTR_ERR(s3c->clk); } - s3c->clk_div = clk_get(dev, "pwm-tdiv"); + s3c->clk_div = devm_clk_get(dev, "pwm-tdiv"); if (IS_ERR(s3c->clk_div)) { dev_err(dev, "failed to get pwm tdiv clk\n"); - ret = PTR_ERR(s3c->clk_div); - goto err_clk_tin; + return PTR_ERR(s3c->clk_div); } clk_enable(s3c->clk); @@ -272,13 +270,6 @@ static int s3c_pwm_probe(struct platform_device *pdev) err_clk_tdiv: clk_disable(s3c->clk_div); clk_disable(s3c->clk); - clk_put(s3c->clk_div); - - err_clk_tin: - clk_put(s3c->clk); - - err_alloc: - kfree(s3c); return ret; } @@ -293,9 +284,6 @@ static int __devexit s3c_pwm_remove(struct platform_device *pdev) clk_disable(s3c->clk_div); clk_disable(s3c->clk); - clk_put(s3c->clk_div); - clk_put(s3c->clk); - kfree(s3c); return 0; } -- cgit v1.2.2 From 2437b0d95c609365ce88039b96a1c020af71c6dc Mon Sep 17 00:00:00 2001 From: Sachin Kamat Date: Fri, 6 Jul 2012 14:43:50 +0530 Subject: pwm: Use pr_* functions in pwm-samsung.c file Replace printk with pr_* functions to avoid checkpatch warnings. Signed-off-by: Sachin Kamat Signed-off-by: Thierry Reding --- drivers/pwm/pwm-samsung.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'drivers/pwm/pwm-samsung.c') diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c index 35fa0e8b0f87..d10386528c9c 100644 --- a/drivers/pwm/pwm-samsung.c +++ b/drivers/pwm/pwm-samsung.c @@ -11,6 +11,8 @@ * the Free Software Foundation; either version 2 of the License. */ +#define pr_fmt(fmt) "pwm-samsung: " fmt + #include #include #include @@ -340,13 +342,13 @@ static int __init pwm_init(void) clk_scaler[1] = clk_get(NULL, "pwm-scaler1"); if (IS_ERR(clk_scaler[0]) || IS_ERR(clk_scaler[1])) { - printk(KERN_ERR "%s: failed to get scaler clocks\n", __func__); + pr_err("failed to get scaler clocks\n"); return -EINVAL; } ret = platform_driver_register(&s3c_pwm_driver); if (ret) - printk(KERN_ERR "%s: failed to add pwm driver\n", __func__); + pr_err("failed to add pwm driver\n"); return ret; } -- cgit v1.2.2 From ecefeb79218064bf011c5d3ff25322ffa60c302c Mon Sep 17 00:00:00 2001 From: Jingoo Han Date: Thu, 2 Aug 2012 17:55:54 +0900 Subject: pwm: samsung: add missing device pointer to struct pwm_chip This patch adds missing device pointer to struct pwm_chip. If the device pointer is NULL, pwmchip_add() will return error. Signed-off-by: Jingoo Han Signed-off-by: Thierry Reding --- drivers/pwm/pwm-samsung.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/pwm/pwm-samsung.c') diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c index d10386528c9c..e5187c0ade9f 100644 --- a/drivers/pwm/pwm-samsung.c +++ b/drivers/pwm/pwm-samsung.c @@ -225,6 +225,7 @@ static int s3c_pwm_probe(struct platform_device *pdev) /* calculate base of control bits in TCON */ s3c->tcon_base = id == 0 ? 0 : (id * 4) + 4; + s3c->chip.dev = &pdev->dev; s3c->chip.ops = &s3c_pwm_ops; s3c->chip.base = -1; s3c->chip.npwm = 1; -- cgit v1.2.2