iMX6 PWM Interrupt

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

iMX6 PWM Interrupt

4,533 Views
evgenymolchanov
Contributor III

Hello all.

In iMX6 datasheet chapter 51 PWM

51.4.1.1 FIFO

Digital sample values can be loaded into the pulse-width modulator as 16-bit words. The

endianess can be changed using the BCTR and HCTR bits of the control register. A 4-

word (16-bit) FIFO minimizes interrupt overhead. A maskable interrupt is generated

when the number of data words fall below the water level set by the FWM field in the

control register.

Give please an example how to request this interrupt in linux 3.10.53.

Thank you.

Labels (3)
6 Replies

1,900 Views
evgenymolchanov
Contributor III

I have added functions to pwm-imx.c for enable compare, rollover interrupts and got interrupts from PWM1 in my driver.

Then I reset interrupt flags in my driver.

Also I have changed irq affinity to CPU3.

So I got more accuracy, than while using EPIT.

But I still need more accuracy...

0 Kudos

1,900 Views
sandeepajesh
Contributor III

Hi Evgeny,

Can you please share steps for enabling and handling interrupt in PWM. Currently i am also stucked with similar problem.

Regards

Sandeep S

0 Kudos

1,900 Views
evgenymolchanov
Contributor III

Hi Sandeep.

I have wrote some driver for setup PWMs via dtsi for start PWMs:

#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <linux/io.h>
#include <linux/pwm.h>
#include <linux/fsl_devices.h>

enum PwmDevs{DEVSTEP=0,DEVSERVO,DEVNUM};

struct tvh_pwm {
    struct pwm_device    *pwm;
    bool                enabled;
    unsigned int        period;
    unsigned int        irq;
};
struct tvh_pwm_device {
    struct device        *dev;
    struct tvh_pwm        *tvh[DEVNUM];
};

struct tvh_pwm_device *pb;

#if defined(CONFIG_OF)
static const struct of_device_id tvh_pwm_dt_ids[] = {
    { .compatible = "tvh,pwm"},
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, tvh_pwm_dt_ids);
#endif
/*
int tvh_pwm_freq(int dev, int freq)
{
    unsigned int period_ns;
//    printk("%s dev %d freq %d\n",__func__, dev, freq);
    if(dev < 0 || dev >= DEVNUM) return -EINVAL;
    if(!freq) return -EINVAL;
    if(!pb) return -ENODEV;
    if(!pb->tvh[dev]->pwm) return -ENODEV;

    period_ns = 1000000000;
    do_div(period_ns, freq);
    pwm_config(pb->tvh[dev]->pwm, (period_ns >> 1), period_ns);
    return 0;

}
EXPORT_SYMBOL(tvh_pwm_freq);
*/
static int tvh_pwm_probe(struct platform_device *pdev)
{
    int ret = 0;
    struct tvh_pwm *step;
    struct tvh_pwm *servo;
//    struct device_node *np = pdev->dev.of_node;

printk("%s\n",__func__);
    step = kzalloc(sizeof(*step), GFP_KERNEL);
    if (!step) {
        dev_err(&pdev->dev, "no memory for state\n");
        ret = -ENOMEM;
        goto err_alloc;
    }
    servo = kzalloc(sizeof(*servo), GFP_KERNEL);
    if (!servo) {
        dev_err(&pdev->dev, "no memory for state\n");
        ret = -ENOMEM;
        goto err_alloc;
    }
    pb = kzalloc(sizeof(*pb), GFP_KERNEL);
    if (!pb) {
        dev_err(&pdev->dev, "no memory for state\n");
        ret = -ENOMEM;
        goto err_alloc;
    }
    pb->dev = &pdev->dev;

/*STEPPER*/
    step->pwm = devm_pwm_get(&pdev->dev, "tvh-step");
    if (IS_ERR(step->pwm)) {
        dev_err(&pdev->dev, "unable to request PWM for stepper.\n");
        ret = (PTR_ERR)(step->pwm);
        kfree(step);
    }
    else
    {
        step->period = pwm_get_period(step->pwm);
        printk("DEVSTEP period %d\n",step->period);
        pwm_config(step->pwm, (step->period >> 1), step->period);
        pwm_out_enable(step->pwm, 1);
        pwm_enable(step->pwm);
        step->enabled = true;
        pb->tvh[DEVSTEP] = step;
    }
/*SERVO*/
    servo->pwm = devm_pwm_get(&pdev->dev, "tvh-servo");
    if (IS_ERR(servo->pwm)) {
        dev_err(&pdev->dev, "unable to request PWM for servo\n");
        ret = (PTR_ERR)(servo->pwm);
        kfree(servo);
    }
    else
    {
        servo->period = pwm_get_period(servo->pwm);
        printk("DEVSERVO period %d\n",servo->period);
        pwm_config(servo->pwm, (servo->period >> 1), servo->period);
        pwm_out_enable(servo->pwm, 1);
        pwm_enable(servo->pwm);
        servo->enabled = true;
        pb->tvh[DEVSERVO] = servo;
    }
    platform_set_drvdata(pdev, pb);
    return 0;
//err_pwm:
    kfree(pb);
err_alloc:
    return ret;
}

static int tvh_pwm_remove(struct platform_device *pdev)
{
    struct tvh_pwm_device *pb = platform_get_drvdata(pdev);
    pwm_disable(pb->tvh[DEVSTEP]->pwm);
    devm_pwm_put(&pdev->dev, pb->tvh[DEVSTEP]->pwm);
    pwm_disable(pb->tvh[DEVSERVO]->pwm);
    devm_pwm_put(&pdev->dev, pb->tvh[DEVSERVO]->pwm);
    kfree(pb);
    return 0;
}

static struct platform_driver tvh_pwm_driver = {
    .probe    = tvh_pwm_probe,
    .remove    = tvh_pwm_remove,
    .driver = {
        .name    = "tvh_pwm",
        .owner    = THIS_MODULE,
        .of_match_table = tvh_pwm_dt_ids,
    },
};

module_platform_driver(tvh_pwm_driver)

MODULE_LICENSE("GPL");
MODULE_AUTHOR("TvHelp, Ltd.");
MODULE_DESCRIPTION("Manage Servo and Stepper motors via PWM");

and add to my dtsi following lines:

...

    pwm-tvh {
        compatible = "tvh,pwm";
        pwms = <&pwm1 0 5000000>, <&pwm2 0 1000000>;//period in nanosec
        pwm-names = "tvh-servo", "tvh-step";
    };

...

&pwm2 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_pwm2_2>;
    status = "okay";
};


add to imx6qdl.dtsi:
    pinctrl_pwm2_2: pwm2grp-2 {
        fsl,pins = <
                MX6QDL_PAD_GPIO_1__PWM2_OUT 0x1b0b1
            >;
        };

In application I call this:

#define MHZ    1000000
#define CLK 66*MHZ
#define PCLK 2*MHZ
#define PRESCALE (CLK)/(PCLK)

#define STEP_PWM2_IRQ 116

#define PWM1_BASE_ADDRESS 0xc09b0000
#define PWM2_BASE_ADDRESS 0xc09b8000
#define MX3_PWMCR            0x00    /* PWM Control Register */
#define MX3_PWMSR            0x04    /* PWM Status Register */
#define MX3_PWMIR            0x08    /* PWM Interrupt Register */
#define MX3_PWMSAR            0x0C    /* PWM Sample Register */
#define MX3_PWMPR            0x10    /* PWM Period Register */
#define MX3_PWMCR_PRESCALER(x)        ((((x) - 1) & 0xFFF) << 4)
#define MX3_PWMCR_DOZEEN        (1 << 24)
#define MX3_PWMCR_WAITEN        (1 << 23)
#define MX3_PWMCR_DBGEN            (1 << 22)
#define MX3_PWMCR_CLKSRC_IPG_HIGH    (2 << 16)
#define MX3_PWMCR_CLKSRC_IPG        (1 << 16)
#define MX3_PWMCR_SWR            (1 << 3)
#define MX3_PWMCR_EN            (1 << 0)

#define MX3_PWMIR_COMPAREIE            (1<<2)
#define MX3_PWMIR_ROLLOVERIE        (1<<1)
#define MX3_PWMIR_FIFOEMPTYIE        (1<<0)

#define MX3_PWMSR_ROLLOVER_STATUS    (1<<4)

irqreturn_t step_pwm_handler(int irq, void *handle)
{
    //Clear rollover interrupt flag
    writel(MX3_PWMSR_ROLLOVER_STATUS, (void volatile*)(PWM2_BASE_ADDRESS + MX3_PWMSR));
    //set period
    writel(pwm_per[period_cnt], (void volatile*)(PWM2_BASE_ADDRESS + MX3_PWMPR));
    //set sample
    writel(pwm_sam[period_cnt], (void volatile*)(PWM2_BASE_ADDRESS + MX3_PWMSAR));

    //Do what you need

    return IRQ_HANDLED;
}

/*
    Init PWM2 of iMX6
    Setup Control Register with prescale calculated above in defines
    Request irq. Rollover interrupt enable set in drivers/pwm/pwm-imx.c
    in imx_pwm_enable function:
    ...
        imx->rei_enable(chip, true);
    ...
    imx_pwm_enable calls in drivers/tvhelp/pwm.pwm.c probe function.
    params:
        cb - callback function which call in interrupt handler
*/
int step_pwm_init(void *cb)
{
    int ret;
    u32 cr;
    if(opened) return EALREADY;

    DBG("%s %s\n",TAG, __func__);

    cr = MX3_PWMCR_PRESCALER(PRESCALE) |
        MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN |
        MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH | MX3_PWMCR_EN;
    writel(cr,(void volatile*)(PWM2_BASE_ADDRESS + MX3_PWMCR));

    ret = request_irq(STEP_PWM2_IRQ, step_pwm_handler, IRQF_DISABLED, "step pwm irq", NULL);

    DBG("%s request step pwm irq %d return %d\n", TAG, STEP_PWM2_IRQ, ret);

    if(cb) callback = cb;
    if(ret == 0)
        opened = 1;
    return ret;
}

/*
    Free PWM2 irq.
*/
int step_pwm_exit(void)
{
    if(!opened) return 0;
    free_irq(STEP_PWM2_IRQ, NULL);
    DBG("%s free step pwm irq %d\n", TAG, STEP_PWM2_IRQ);
    opened = 0;
    return 0;
}

Good luck)

1,900 Views
sandeepajesh
Contributor III

Thanks Evgeny....It was really helpful.

Thanks a lot. (y)

0 Kudos

1,900 Views
igorpadykov
NXP Employee
NXP Employee

Hi Evgeny

one can look at pwm driver (working on interrupts), it is decribed

in attached Linux Manual  Chapter 49 Pulse-Width Modulator (PWM) Driver

and below links

http://git.freescale.com/git/cgit.cgi/imx/linux-2.6-imx.git/tree/Documentation/devicetree/bindings/p...

http://git.freescale.com/git/cgit.cgi/imx/linux-2.6-imx.git/tree/arch/arm/mach-imx/devices/platform-...

Best regards

igor

-----------------------------------------------------------------------------------------------------------------------

Note: If this post answers your question, please click the Correct Answer button. Thank you!

-----------------------------------------------------------------------------------------------------------------------

0 Kudos

1,900 Views
evgenymolchanov
Contributor III

Hi Igor.

Sorry, I must to correct my question.

I use Android 4.4.3 with kernel 3.10.53.

I need periodical timer without any jitter.

I found ​what GPT timer used by kernel.

I try to use hrtimer, but it not so accurate as i need.

EPIT too very depends on CPU load.

Now I want to use PWM, but without GPIO, only interrupt. Because in my custom board appropriate pins not allowed.

In kernel sources drivers/pwm/pwm-imx.c in probe function there is no any parsing of dts and request interrupt.

arch/arm/mach-imx/epit.c probe function parse dts and call request irq.

I can add parsing of dts and add irq request in pwm-imx.c, but is anybody tested working interrupt from PWM?

Or maybe you can give me another way to get periodic events?

0 Kudos