Messages in this thread Patch in this message | | | Date | Thu, 8 Jan 2009 11:07:11 +0100 | From | "Davide Rizzo" <> | Subject | [PATCH 2/2] Driver for user access to internal timer |
| |
Adds the function pwm_one_shot to the pwm interface, to use a timer to wait for a short period and call a callback at the end. This is for ms range delays. There is also the implementation for the Samsung S3C24xx platform.
Signed-off-by: Davide Rizzo <elpa-rizzo@gmail.com> --- diff -urNp linux-2.6.28/arch/arm/plat-s3c24xx/pwm.c linux-2.6.28.elpa/arch/arm/plat-s3c24xx/pwm.c --- linux-2.6.28/arch/arm/plat-s3c24xx/pwm.c 2008-12-25 00:26:37.000000000 +0100 +++ linux-2.6.28.elpa/arch/arm/plat-s3c24xx/pwm.c 2009-01-06 19:04:44.000000000 +0100 @@ -15,6 +15,7 @@ #include <linux/kernel.h> #include <linux/platform_device.h> #include <linux/err.h> +#include <linux/interrupt.h> #include <linux/clk.h> #include <linux/io.h> #include <linux/pwm.h> @@ -22,6 +23,8 @@ #include <plat/devs.h> #include <plat/regs-timer.h>
+#define DEV_NAME "s3c24xx-pwm" + struct pwm_device { struct list_head list; struct platform_device *pdev; @@ -37,6 +40,11 @@ struct pwm_device { unsigned char running; unsigned char use_count; unsigned char pwm_id; + + /* For one-shot mode callback*/ + int irq; + void (*expired)(struct pwm_device *, void *arg); + void *arg; };
#define pwm_dbg(_pwm, msg...) dev_dbg(&(_pwm)->pdev->dev, msg) @@ -57,7 +65,7 @@ static struct clk *clk_scaler[2]; }
#define DEFINE_S3C_TIMER(_tmr_no, _irq) \ - .name = "s3c24xx-pwm", \ + .name = DEV_NAME, \ .id = _tmr_no, \ .num_resources = TIMER_RESOURCE_SIZE, \ .resource = TIMER_RESOURCE(_tmr_no, _irq), \ @@ -272,6 +280,88 @@ int pwm_config(struct pwm_device *pwm, i
EXPORT_SYMBOL(pwm_config);
+static irqreturn_t timer_isr(int irq, void *arg) +{ + struct pwm_device *pwm = arg; + + if (pwm->expired) { + disable_irq(irq); + pwm_disable(pwm); + pwm->expired(pwm, pwm->arg); + pwm->expired = NULL; + } + return IRQ_HANDLED; +} + +int pwm_one_shot(struct pwm_device *pwm, int timeout_ns, + void(*expired)(struct pwm_device *, void *arg), void *arg) +{ + unsigned long tin_rate; + unsigned long tin_ns; + unsigned long period; + unsigned long flags; + unsigned long tcon; + unsigned long tcnt; + + /* We currently avoid using 64bit arithmetic by using the + * fact that anything faster than 1Hz is easily representable + * by 32bits. */ + + if (timeout_ns > NS_IN_HZ) + return -ERANGE; + + /* The TCMP and TCNT can be read without a lock, they're not + * shared between the timers. */ + + tcnt = __raw_readl(S3C2410_TCNTB(pwm->pwm_id)); + + period = NS_IN_HZ / timeout_ns; + + pwm_dbg(pwm, "one shot timeout_ns=%d\n", timeout_ns); + + if (pwm_is_tdiv(pwm)) { + tin_rate = pwm_calc_tin(pwm, period); + clk_set_rate(pwm->clk_div, tin_rate); + } else + tin_rate = clk_get_rate(pwm->clk); + + pwm_dbg(pwm, "tin_rate=%lu\n", tin_rate); + + tin_ns = NS_IN_HZ / tin_rate; + tcnt = timeout_ns / tin_ns; + /* Note, counters count down */ + + pwm_dbg(pwm, "tin_ns=%lu\n", tin_ns); + + /* Update the PWM register block. */ + + local_irq_save(flags); + + if (!pwm->expired) + enable_irq(pwm->irq); + + __raw_writel(tcnt, S3C2410_TCNTB(pwm->pwm_id)); + + tcon = __raw_readl(S3C2410_TCON); + tcon |= pwm_tcon_manulupdate(pwm); + tcon &= ~pwm_tcon_autoreload(pwm); + tcon &= ~pwm_tcon_start(pwm); + __raw_writel(tcon, S3C2410_TCON); + + + pwm->expired = expired; + pwm->arg = arg; + + tcon &= ~pwm_tcon_manulupdate(pwm); + tcon |= pwm_tcon_start(pwm); + __raw_writel(tcon, S3C2410_TCON); + + local_irq_restore(flags); + + return 0; +} +EXPORT_SYMBOL(pwm_one_shot); + static int pwm_register(struct pwm_device *pwm) { pwm->duty_ns = -1; @@ -288,6 +378,7 @@ static int s3c_pwm_probe(struct platform { struct device *dev = &pdev->dev; struct pwm_device *pwm; + struct resource *res; unsigned long flags; unsigned long tcon; unsigned int id = pdev->id; @@ -306,6 +397,22 @@ static int s3c_pwm_probe(struct platform
pwm->pdev = pdev; pwm->pwm_id = id; + pwm->expired = NULL; + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(dev, "cannot get platform interrupt resource\n"); + ret = -EBUSY; + goto err_getresources; + } + pwm->irq = res->start; + ret = request_irq(pwm->irq, timer_isr, IRQF_DISABLED, + DEV_NAME, pwm); + if (ret) { + dev_err(dev, "request_irq failed\n"); + goto err_getirq; + } + disable_irq(pwm->irq);
/* calculate base of control bits in TCON */ pwm->tcon_base = id == 0 ? 0 : (id * 4) + 4; @@ -354,6 +461,10 @@ static int s3c_pwm_probe(struct platform clk_put(pwm->clk_div);
err_clk_tin: + err_getresources: + free_irq(pwm->irq, DEV_NAME); + + err_getirq: clk_put(pwm->clk);
err_alloc: @@ -365,6 +476,7 @@ static int s3c_pwm_remove(struct platfor { struct pwm_device *pwm = platform_get_drvdata(pdev);
+ free_irq(pwm->irq, DEV_NAME); clk_put(pwm->clk_div); clk_put(pwm->clk); kfree(pwm); @@ -374,7 +486,7 @@ static int s3c_pwm_remove(struct platfor
static struct platform_driver s3c_pwm_driver = { .driver = { - .name = "s3c24xx-pwm", + .name = DEV_NAME, .owner = THIS_MODULE, }, .probe = s3c_pwm_probe, diff -urNp linux-2.6.28/include/linux/pwm.h linux-2.6.28.elpa/include/linux/pwm.h --- linux-2.6.28/include/linux/pwm.h 2008-12-25 00:26:37.000000000 +0100 +++ linux-2.6.28.elpa/include/linux/pwm.h 2009-01-06 19:35:19.000000000 +0100 @@ -19,6 +19,13 @@ void pwm_free(struct pwm_device *pwm); int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
/* + * pwm_one_shot - programs and starts a timer for one-shot operation + * when expired calls callback + */ +int pwm_one_shot(struct pwm_device *pwm, int timeout_ns, + void(*expired)(struct pwm_device *, void *arg), void *arg); + +/* * pwm_enable - start a PWM output toggling */ int pwm_enable(struct pwm_device *pwm);
| |