Message ID | 20180108154336.GE4077@lenoch (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
* Ladislav Michl <ladis@linux-mips.org> [180108 15:46]: > Here it seems hardware can capture both edges, but I do not see a way > how to tell it I want start from either low to high or high to low > transition. Clues? At least dm3730 TRM documents TCM bits [9:8] for TCLR, but you probably know that already.. If you're having hard time getting things starting, maybe something like this helps: stop timer in TCLR register configure timer in TCLR write some value to TLDR, maybe 0? set ST bit in TCLR to start Regards, Tony -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Mon, Jan 08, 2018 at 01:59:31PM -0800, Tony Lindgren wrote: > * Ladislav Michl <ladis@linux-mips.org> [180108 15:46]: > > Here it seems hardware can capture both edges, but I do not see a way > > how to tell it I want start from either low to high or high to low > > transition. Clues? > > At least dm3730 TRM documents TCM bits [9:8] for TCLR, but you > probably know that already.. > > If you're having hard time getting things starting, maybe something > like this helps: > > stop timer in TCLR register > configure timer in TCLR > write some value to TLDR, maybe 0? > set ST bit in TCLR to start Let me clarify it a bit more. I have no problem starting timer and capture events. I just didn't find a way how to tell hardware I want to start with for example rising edge, so rising edge goes to TCAR1 and failing edge to TCAR2. Substracting those gives pulse width. ladis -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
* Ladislav Michl <ladis@linux-mips.org> [180108 22:09]: > On Mon, Jan 08, 2018 at 01:59:31PM -0800, Tony Lindgren wrote: > > * Ladislav Michl <ladis@linux-mips.org> [180108 15:46]: > > > Here it seems hardware can capture both edges, but I do not see a way > > > how to tell it I want start from either low to high or high to low > > > transition. Clues? > > > > At least dm3730 TRM documents TCM bits [9:8] for TCLR, but you > > probably know that already.. > > > > If you're having hard time getting things starting, maybe something > > like this helps: > > > > stop timer in TCLR register > > configure timer in TCLR > > write some value to TLDR, maybe 0? > > set ST bit in TCLR to start > > Let me clarify it a bit more. I have no problem starting timer and capture > events. I just didn't find a way how to tell hardware I want to start > with for example rising edge, so rising edge goes to TCAR1 and failing edge > to TCAR2. Substracting those gives pulse width. Oh I see, yeah that would be cool :) Maybe you can first configure an interrupt to trigger on rising edge, then configure things for falling edge, then subtract.. Regards, Tony -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Mon, Jan 08, 2018 at 02:13:27PM -0800, Tony Lindgren wrote: > * Ladislav Michl <ladis@linux-mips.org> [180108 22:09]: > > On Mon, Jan 08, 2018 at 01:59:31PM -0800, Tony Lindgren wrote: > > > * Ladislav Michl <ladis@linux-mips.org> [180108 15:46]: > > > > Here it seems hardware can capture both edges, but I do not see a way > > > > how to tell it I want start from either low to high or high to low > > > > transition. Clues? > > > > > > At least dm3730 TRM documents TCM bits [9:8] for TCLR, but you > > > probably know that already.. > > > > > > If you're having hard time getting things starting, maybe something > > > like this helps: > > > > > > stop timer in TCLR register > > > configure timer in TCLR > > > write some value to TLDR, maybe 0? > > > set ST bit in TCLR to start > > > > Let me clarify it a bit more. I have no problem starting timer and capture > > events. I just didn't find a way how to tell hardware I want to start > > with for example rising edge, so rising edge goes to TCAR1 and failing edge > > to TCAR2. Substracting those gives pulse width. > > Oh I see, yeah that would be cool :) Maybe you can first configure > an interrupt to trigger on rising edge, then configure things for > falling edge, then subtract.. That will work only for very long periods. I did test where value captured into TCAR was compared to value read from TCRR. Sometimes those differ by few hundreds microseconds... I started this work to overcome GPIO edge interrupts latency, but so far I'm a bit dissapointed. (also please read the code, reconfiguration is done to get period and duty cycle - but in case of duty cycle it is not clear whenever we are measuring pulse or space length) ladis -- To unsubscribe from this list: send the line "unsubscribe linux-omap" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/drivers/pwm/pwm-omap-dmtimer.c b/drivers/pwm/pwm-omap-dmtimer.c index ee1cd92b1744..90d07df0f69d 100644 --- a/drivers/pwm/pwm-omap-dmtimer.c +++ b/drivers/pwm/pwm-omap-dmtimer.c @@ -17,7 +17,9 @@ */ #include <linux/clk.h> +#include <clocksource/dmtimer.h> #include <linux/err.h> +#include <linux/interrupt.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/mutex.h> @@ -30,17 +32,21 @@ #include <linux/pwm.h> #include <linux/slab.h> #include <linux/time.h> +#include <linux/wait.h> #define DM_TIMER_LOAD_MIN 0xfffffffe #define DM_TIMER_MAX 0xffffffff struct pwm_omap_dmtimer_chip { struct pwm_chip chip; + spinlock_t lock; struct mutex mutex; + wait_queue_head_t wait; pwm_omap_dmtimer *dm_timer; struct omap_dm_timer_ops *pdata; struct platform_device *dm_timer_pdev; unsigned long freq; + unsigned int ev_cnt, overflow, width; }; static inline struct pwm_omap_dmtimer_chip * @@ -55,6 +61,22 @@ pwm_omap_dmtimer_get_clock_cycles(struct pwm_omap_dmtimer_chip *omap, int ns) return DIV_ROUND_CLOSEST_ULL((u64)omap->freq * ns, NSEC_PER_SEC); } +static inline unsigned int +pwm_omap_dmtimer_ticks_to_ns(struct pwm_omap_dmtimer_chip *omap, + unsigned int ticks) +{ + return DIV_ROUND_CLOSEST_ULL((u64)ticks * NSEC_PER_SEC, omap->freq); +} + +static unsigned int pwm_omap_dmtimer_get_width(struct pwm_omap_dmtimer_chip *omap, + unsigned int c1, unsigned int c2) +{ + if (c1 <= c2) + return c2 - c1; + + return (c2 - omap->overflow) + (DM_TIMER_MAX - c1); +} + static void pwm_omap_dmtimer_start(struct pwm_omap_dmtimer_chip *omap) { /* @@ -218,7 +240,101 @@ static int pwm_omap_dmtimer_set_polarity(struct pwm_chip *chip, return 0; } +static int pwm_omap_dmtimer_capture(struct pwm_chip *chip, + struct pwm_device *pwm, + struct pwm_capture *result, + unsigned long timeout) + +{ + int res; + unsigned long flags; + struct pwm_omap_dmtimer_chip *omap = to_pwm_omap_dmtimer_chip(chip); + + mutex_lock(&omap->mutex); + + if (pm_runtime_active(&omap->dm_timer_pdev->dev)) + omap->pdata->stop(omap->dm_timer); + + /* Let timer overflow every 1 second */ + omap->overflow = DM_TIMER_MAX - (1 * omap->freq); + timeout = msecs_to_jiffies(timeout); + result->period = result->duty_cycle = 0; + + spin_lock_irqsave(&omap->lock, flags); + omap->pdata->set_int_enable(omap->dm_timer, OMAP_TIMER_INT_CAPTURE | + OMAP_TIMER_INT_OVERFLOW); + omap->pdata->set_capture(omap->dm_timer, 1, 1); + omap->pdata->set_load_start(omap->dm_timer, true, omap->overflow); + omap->ev_cnt = omap->width = 0; + spin_unlock_irqrestore(&omap->lock, flags); + + res = wait_event_interruptible_timeout(omap->wait, + omap->width > 0, timeout); + if (res > 0) { + spin_lock_irqsave(&omap->lock, flags); + result->period = + pwm_omap_dmtimer_ticks_to_ns(omap, omap->width); + omap->pdata->stop(omap->dm_timer); + omap->pdata->set_capture(omap->dm_timer, 1, 3); + omap->pdata->start(omap->dm_timer); + omap->ev_cnt = omap->width = 0; + spin_unlock_irqrestore(&omap->lock, flags); + + res = wait_event_interruptible_timeout(omap->wait, + omap->width > 0, + timeout); + } + + spin_lock_irqsave(&omap->lock, flags); + omap->pdata->stop(omap->dm_timer); + omap->pdata->set_int_disable(omap->dm_timer, OMAP_TIMER_INT_CAPTURE | + OMAP_TIMER_INT_OVERFLOW); + spin_unlock_irqrestore(&omap->lock, flags); + + if (res == 0) { + res = -ETIMEDOUT; + } else if (res > 0) { + result->duty_cycle = + pwm_omap_dmtimer_ticks_to_ns(omap, omap->width); + res = 0; + } + + mutex_unlock(&omap->mutex); + + return res; +} + +static irqreturn_t pwm_omap_dmtimer_irq(int irq, void *dev_id) +{ + u32 l; + unsigned int c1, c2; + unsigned long flags; + struct pwm_omap_dmtimer_chip *omap = dev_id; + + spin_lock_irqsave(&omap->lock, flags); + + l = omap->pdata->read_status(omap->dm_timer); + if (l & OMAP_TIMER_INT_CAPTURE) { + if (!omap->width && omap->ev_cnt == 1) { + omap->pdata->read_capture(omap->dm_timer, &c1, &c2); + omap->width = pwm_omap_dmtimer_get_width(omap, c1, c2); + wake_up(&omap->wait); + } + omap->ev_cnt++; + } + /* TODO: handle overflow as well + if (l & OMAP_TIMER_INT_OVERFLOW) + overflow++; + */ + omap->pdata->write_status(omap->dm_timer, l); + + spin_unlock_irqrestore(&omap->lock, flags); + + return IRQ_HANDLED; +} + static const struct pwm_ops pwm_omap_dmtimer_ops = { + .capture = pwm_omap_dmtimer_capture, .enable = pwm_omap_dmtimer_enable, .disable = pwm_omap_dmtimer_disable, .config = pwm_omap_dmtimer_config, @@ -237,7 +353,7 @@ static int pwm_omap_dmtimer_probe(struct platform_device *pdev) pwm_omap_dmtimer *dm_timer; struct clk *fclk; u32 v; - int status; + int irq, status; timer = of_parse_phandle(np, "ti,timers", 0); if (!timer) @@ -338,7 +454,18 @@ static int pwm_omap_dmtimer_probe(struct platform_device *pdev) omap->chip.of_xlate = of_pwm_xlate_with_flags; omap->chip.of_pwm_n_cells = 3; + spin_lock_init(&omap->lock); mutex_init(&omap->mutex); + init_waitqueue_head(&omap->wait); + + irq = omap->pdata->get_irq(omap->dm_timer); + if (irq < 0) + return irq; + + status = devm_request_irq(&pdev->dev, irq, pwm_omap_dmtimer_irq, 0, + "event_capture", omap); + if (status) + return status; status = pwmchip_add(&omap->chip); if (status < 0) {
Call for help: For testing purposes pwm_evt pin is driven by a signal generator, in this example at 200us period and 80% duty cycle. Repeated testing gives: $ cat /sys/class/pwm/pwmchip4/pwm0/capture 200000 160000 or: $ cat /sys/class/pwm/pwmchip4/pwm0/capture 200000 40000 Here it seems hardware can capture both edges, but I do not see a way how to tell it I want start from either low to high or high to low transition. Clues? Also there is probably bug in the code as for shorter periods (here 10us) I'm getting: $ cat /sys/class/pwm/pwmchip4/pwm0/capture omap_dm_timer_read_status: timer not available or enabled. 10000 2000 Unfortunately I do not see how it can happen in the code. Any hints greatly appreciated, thank you... Signed-off-by: Ladislav Michl <ladis@linux-mips.org>