This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

Linux/AM5718: dmtimer use question

Part Number: AM5718

Tool/software: Linux

SDK = ti-processor-sdk-linux-rt-am57xx-evm-05.03.00.07

u-boot = u-boot-2018.01+gitAUTOINC+313dcd69c2-ge2bc936055

kernel = linux-rt-4.14.79+gitAUTOINC+a72bf1418c-ga72bf1418c

cross compiler = arm-linux-gnueabihf-gcc

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

HI,

I have a AM5718 custom board.

I would like to check the chip performance by toggling the LED through the precision chip internal timer.

While programming the internal Timer module, I noticed that there is no dmtimer.h in the SDK.

I want to use the function in dmtimer.h. Do you have any other alternatives?

Example) omap_dm_timer_start, omap_dm_timer_set_source ...

In addition, we would appreciate if you could give us a timer example.

  • Hi Sunmin,

    AM57x timers drivers are at below locations:

    linux-4.14.79/arch/arm/mach-omap2/timer.c

    linux-4.14.79/drivers/clocksource/timer-ti-dm.c

    header files are:

    <linux/platform_data/dmtimer-omap.h>

    <clocksource/timer-ti-dm.h>

    For examples in can refer to the below pointers:

    {PSDK}/example-applications/omapconf-1.73/linux/timer_stats.c

    {PSDK}/board-support/extra-drivers/jailhouse-0.8/inmates/lib/arm-common/timer.c


    software-dl.ti.com/processor-sdk-linux/esd/docs/latest/linux/Foundational_Components_Virtualization.html#jailhouse-hypervisor

    software-dl.ti.com/processor-sdk-linux/esd/docs/latest/linux/Foundational_Components_Kernel_Drivers.html#cpsw

    Regards,
    Pavel

  • Hi, 

    I am trying to compile through the link below.
    e2e.ti.com/.../415079

    However, the following error occurs.

    WARNING: "omap_dm_timer_start" [/home/ksm/ksmwork/Platform/study/TI-driver/timer_modules/study_timer_module.ko] undefined!
    WARNING: "omap_dm_timer_set_match" [/home/ksm/ksmwork/Platform/study/TI-driver/timer_modules/study_timer_module.ko] undefined!
    WARNING: "omap_dm_timer_set_load" [/home/ksm/ksmwork/Platform/study/TI-driver/timer_modules/study_timer_module.ko] undefined!
    WARNING: "omap_dm_timer_write_counter" [/home/ksm/ksmwork/Platform/study/TI-driver/timer_modules/study_timer_module.ko] undefined!
    WARNING: "omap_dm_timer_enable" [/home/ksm/ksmwork/Platform/study/TI-driver/timer_modules/study_timer_module.ko] undefined!
    WARNING: "omap_dm_timer_set_pwm" [/home/ksm/ksmwork/Platform/study/TI-driver/timer_modules/study_timer_module.ko] undefined!
    WARNING: "omap_dm_timer_get_fclk" [/home/ksm/ksmwork/Platform/study/TI-driver/timer_modules/study_timer_module.ko] undefined!
    WARNING: "omap_dm_timer_set_int_enable" [/home/ksm/ksmwork/Platform/study/TI-driver/timer_modules/study_timer_module.ko] undefined!
    WARNING: "omap_dm_timer_get_irq" [/home/ksm/ksmwork/Platform/study/TI-driver/timer_modules/study_timer_module.ko] undefined!
    WARNING: "omap_dm_timer_set_source" [/home/ksm/ksmwork/Platform/study/TI-driver/timer_modules/study_timer_module.ko] undefined!
    WARNING: "omap_dm_timer_request_by_node" [/home/ksm/ksmwork/Platform/study/TI-driver/timer_modules/study_timer_module.ko] undefined!
    WARNING: "omap_dm_timer_write_status" [/home/ksm/ksmwork/Platform/study/TI-driver/timer_modules/study_timer_module.ko] undefined!
    WARNING: "omap_dm_timer_read_status" [/home/ksm/ksmwork/Platform/study/TI-driver/timer_modules/study_timer_module.ko] undefined!
    WARNING: "omap_dm_timer_free" [/home/ksm/ksmwork/Platform/study/TI-driver/timer_modules/study_timer_module.ko] undefined!
    WARNING: "omap_dm_timer_stop" [/home/ksm/ksmwork/Platform/study/TI-driver/timer_modules/study_timer_module.ko] undefined!

    My c code and Makefile are as follows.

    Do you know the problem?

    1. study_timer_module.c

    /*
     * 표준 버스에 없는 디바이스 정보 영역을 사용하기위해 탄생한것이 플렛폼 버스
     * 플랫폼 디바이스는 플렛봄 버스의 설정영역에 있는 구조와 비슷하게 디바이스 이름 인터럽트나 메모리 정보를 가지고있어야함
     *
     * #include<platform_device.h>
     * struct platform_device {
     *   const char  *name;                                 // 플렛폼 디바이스 이름 timer6은 am571x_Timer6으로 지정했었쥐~~
     *   int     id;                                        // 디바이스를 구분하기 위한 번호 일반적으로 -1로 설정하면 커널에서 알아서 할당(이커널 해더파일보면 auto는 -2)
     *   bool        id_auto;
     *   struct device   dev;                               // 디바이스 필드는 디바이스에 별도로 필요한 정보가 있을때 지정한다.
     *   u32     num_resources;                             // 사용하는 리소스 갯수
     *   struct resource *resource;                         // 리소스 구조체 포인터(리소스 구조체는 하드웨어의 주소나 인터럽트 정보가 나열됨)
     *
     *   const struct platform_device_id *id_entry;
     *   char *driver_override;
     *
     *   struct mfd_cell *mfd_cell;
     *
     *   struct pdev_archdata    archdata;
     * };
     *
     * 디바이스 등록해지는 |  등록 : int platform_device_register(struct platform_device *);
     *                     해제 : void platform_device_unregister(struct platform_device *);
     *
     *-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
     *
     * #include<ioport.h> 리소스 해더파일
     *   struct resource {
     *      resource_size_t start;                      // 리소스 구조체는 start , end 시작과 끝 을 지정함 ex)메모리 주소의 시작 끝 지정
     *      resource_size_t end;                        // 설정할수있는것은 해더파일 가면 겁나많이있음Y
     *      const char *name;
     *      unsigned long flags;
     *      unsigned long desc;
     *      struct resource *parent, *sibling, *child;
     *  };
     *
     *
     *------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
     *  ! 플랫폼 드라이버는 문자디바이스드라이버 , 블록 디바이스 드라이버 , 네트워크 인터페이스 드라이버 비롯한 어떤것도 가능
     *  ! 플랫폼 드라이버를 구현할때 모든 필드에 해당하는 함수를 구현할 필요는 없다.
     *
     *  #include<platform_device.h>
     *
     *  struct platform_driver {
     *      int (*probe)(struct platform_device *);                                     // 디바이스 검색시 사용되는 함수
     *      int (*remove)(struct platform_device *);                                    // 디바이스를 제거할때 실행되는 함수
     *      void (*shutdown)(struct platform_device *);                                 // 전원 차단시 실행되는 함수
     *      int (*suspend)(struct platform_device *, pm_message_t state);               // 전력을 줄이기 위해 사용되는 함수
     *      int (*resume)(struct platform_device *);                                    // 전력을 줄이기 위해 사용되는 함수
     *      struct device_driver driver;                                                // 디바이스 드라이버를 지정하는 드라이버필드는 이 드라이버를 위한 구조체를 지정하데 이구조체에 name이 있는데 이것은 반드시 등록한 플랫폼 디바이스 이름과 같아야 한다.
     *      const struct platform_device_id *id_table;
     *      bool prevent_deferred_probe;
     * };
     */
    #include <linux/module.h>     /* Needed by all Linux Kernel Modules */
    #include <linux/platform_device.h>    /* Needed to query the plaform configuration for addresses, interrupts,etc */
    #include <linux/kernel.h>     /* Needed for KERN_INFO */
    #include <linux/init.h>
    #include <linux/of_device.h>
    #include <linux/gpio.h>
    
    /* ARM DM Timer Header */
    //#include <plat/dmtimer.h>
    #include <clocksource/timer-ti-dm.h>
    #include <linux/platform_data/dmtimer-omap.h>
    
    /* Needed for clk functions */
    #include <linux/clk.h>
    
    /* Interrupt Header */
    #include <linux/interrupt.h>
    
    #if 0
    /**************************************************************
     * ARM DM Timer Header
     * skaskawl - Timer Modules Setting
     * 2019.07.18
     **************************************************************/
    static u32 omap_reserved_systimers;
    static LIST_HEAD(omap_timer_list);
    static DEFINE_SPINLOCK(dm_timer_lock);
    
    enum {
        REQUEST_ANY = 0,
        REQUEST_BY_ID,
        REQUEST_BY_CAP,
        REQUEST_BY_NODE,
    };
    
    /**
     * omap_dm_timer_read_reg - read timer registers in posted and non-posted mode
     * @timer:      timer pointer over which read operation to perform
     * @reg:        lowest byte holds the register offset
     *
     * The posted mode bit is encoded in reg. Note that in posted mode write
     * pending bit must be checked. Otherwise a read of a non completed write
     * will produce an error.
     */
    static inline u32 omap_dm_timer_read_reg(struct omap_dm_timer *timer, u32 reg)
    {
        WARN_ON((reg & 0xff) < _OMAP_TIMER_WAKEUP_EN_OFFSET);
        return __omap_dm_timer_read(timer, reg, timer->posted);
    }
    
    /**
     * omap_dm_timer_write_reg - write timer registers in posted and non-posted mode
     * @timer:      timer pointer over which write operation is to perform
     * @reg:        lowest byte holds the register offset
     * @value:      data to write into the register
     *
     * The posted mode bit is encoded in reg. Note that in posted mode the write
     * pending bit must be checked. Otherwise a write on a register which has a
     * pending write will be lost.
     */
    static void omap_dm_timer_write_reg(struct omap_dm_timer *timer, u32 reg,
                            u32 value)
    {
        WARN_ON((reg & 0xff) < _OMAP_TIMER_WAKEUP_EN_OFFSET);
        __omap_dm_timer_write(timer, reg, value, timer->posted);
    }
    
    static void omap_timer_restore_context(struct omap_dm_timer *timer)
    {
        omap_dm_timer_write_reg(timer, OMAP_TIMER_WAKEUP_EN_REG,
                    timer->context.twer);
        omap_dm_timer_write_reg(timer, OMAP_TIMER_COUNTER_REG,
                    timer->context.tcrr);
        omap_dm_timer_write_reg(timer, OMAP_TIMER_LOAD_REG,
                    timer->context.tldr);
        omap_dm_timer_write_reg(timer, OMAP_TIMER_MATCH_REG,
                    timer->context.tmar);
        omap_dm_timer_write_reg(timer, OMAP_TIMER_IF_CTRL_REG,
                    timer->context.tsicr);
        writel_relaxed(timer->context.tier, timer->irq_ena);
        omap_dm_timer_write_reg(timer, OMAP_TIMER_CTRL_REG,
                    timer->context.tclr);
    }
    
    static int omap_dm_timer_reset(struct omap_dm_timer *timer)
    {
        u32 l, timeout = 100000;
    
        if (timer->revision != 1)
            return -EINVAL;
    
        omap_dm_timer_write_reg(timer, OMAP_TIMER_IF_CTRL_REG, 0x06);
    
        do {
            l = __omap_dm_timer_read(timer,
                         OMAP_TIMER_V1_SYS_STAT_OFFSET, 0);
        } while (!l && timeout--);
    
        if (!timeout) {
            dev_err(&timer->pdev->dev, "Timer failed to reset\n");
            return -ETIMEDOUT;
        }
    
        /* Configure timer for smart-idle mode */
        l = __omap_dm_timer_read(timer, OMAP_TIMER_OCP_CFG_OFFSET, 0);
        l |= 0x2 << 0x3;
        __omap_dm_timer_write(timer, OMAP_TIMER_OCP_CFG_OFFSET, l, 0);
    
        timer->posted = 0;
    
        return 0;
    }
    
    static int omap_dm_timer_of_set_source(struct omap_dm_timer *timer)
    {
        int ret;
        struct clk *parent;
    
        /*
         * FIXME: OMAP1 devices do not use the clock framework for dmtimers so
         * do not call clk_get() for these devices.
         */
        if (!timer->fclk)
            return -ENODEV;
    
        parent = clk_get(&timer->pdev->dev, NULL);
        if (IS_ERR(parent))
            return -ENODEV;
    
        ret = clk_set_parent(timer->fclk, parent);
        if (ret < 0)
            pr_err("%s: failed to set parent\n", __func__);
    
        clk_put(parent);
    
        return ret;
    }
    
    static int omap_dm_timer_prepare(struct omap_dm_timer *timer)
    {
        int rc;
    
        /*
         * FIXME: OMAP1 devices do not use the clock framework for dmtimers so
         * do not call clk_get() for these devices.
         */
        if (!(timer->capability & OMAP_TIMER_NEEDS_RESET)) {
            timer->fclk = clk_get(&timer->pdev->dev, "fck");
            if (WARN_ON_ONCE(IS_ERR(timer->fclk))) {
                dev_err(&timer->pdev->dev, ": No fclk handle.\n");
                return -EINVAL;
            }
        }
    
        omap_dm_timer_enable(timer);
    
        if (timer->capability & OMAP_TIMER_NEEDS_RESET) {
            rc = omap_dm_timer_reset(timer);
            if (rc) {
                omap_dm_timer_disable(timer);
                return rc;
            }
        }
    
        __omap_dm_timer_enable_posted(timer);
        omap_dm_timer_disable(timer);
    
        rc = omap_dm_timer_of_set_source(timer);
        if (rc == -ENODEV)
            return omap_dm_timer_set_source(timer, OMAP_TIMER_SRC_32_KHZ);
    
        return rc;
    }
    
    static inline u32 omap_dm_timer_reserved_systimer(int id)
    {
        return (omap_reserved_systimers & (1 << (id - 1))) ? 1 : 0;
    }
    
    int omap_dm_timer_reserve_systimer(int id)
    {
        if (omap_dm_timer_reserved_systimer(id))
            return -ENODEV;
    
        omap_reserved_systimers |= (1 << (id - 1));
    
        return 0;
    }
    
    static struct omap_dm_timer *_omap_dm_timer_request(int req_type, void *data)
    {
        struct omap_dm_timer *timer = NULL, *t;
        struct device_node *np = NULL;
        unsigned long flags;
        u32 cap = 0;
        int id = 0;
    
        switch (req_type) {
        case REQUEST_BY_ID:
            id = *(int *)data;
            break;
        case REQUEST_BY_CAP:
            cap = *(u32 *)data;
            break;
        case REQUEST_BY_NODE:
            np = (struct device_node *)data;
            break;
        default:
            /* REQUEST_ANY */
            break;
        }
    
        spin_lock_irqsave(&dm_timer_lock, flags);
        list_for_each_entry(t, &omap_timer_list, node) {
            if (t->reserved)
                continue;
    
            switch (req_type) {
            case REQUEST_BY_ID:
                if (id == t->pdev->id) {
                    timer = t;
                    timer->reserved = 1;
                    goto found;
                }
                break;
            case REQUEST_BY_CAP:
                if (cap == (t->capability & cap)) {
                    /*
                     * If timer is not NULL, we have already found
                     * one timer but it was not an exact match
                     * because it had more capabilites that what
                     * was required. Therefore, unreserve the last
                     * timer found and see if this one is a better
                     * match.
                     */
                    if (timer)
                        timer->reserved = 0;
                    timer = t;
                    timer->reserved = 1;
    
                    /* Exit loop early if we find an exact match */
                    if (t->capability == cap)
                        goto found;
                }
                break;
            case REQUEST_BY_NODE:
                if (np == t->pdev->dev.of_node) {
                    timer = t;
                    timer->reserved = 1;
                    goto found;
                }
                break;
            default:
                /* REQUEST_ANY */
                timer = t;
                timer->reserved = 1;
                goto found;
            }
        }
    found:
        spin_unlock_irqrestore(&dm_timer_lock, flags);
    
        if (timer && omap_dm_timer_prepare(timer)) {
            timer->reserved = 0;
            timer = NULL;
        }
    
        if (!timer)
            pr_debug("%s: timer request failed!\n", __func__);
    
        return timer;
    }
    
    struct omap_dm_timer *omap_dm_timer_request(void)
    {
        return _omap_dm_timer_request(REQUEST_ANY, NULL);
    }
    
    struct omap_dm_timer *omap_dm_timer_request_specific(int id)
    {
        /* Requesting timer by ID is not supported when device tree is used */
        if (of_have_populated_dt()) {
            pr_warn("%s: Please use omap_dm_timer_request_by_cap/node()\n",
                __func__);
            return NULL;
        }
    
        return _omap_dm_timer_request(REQUEST_BY_ID, &id);
    }
    
    /**
     * omap_dm_timer_request_by_cap - Request a timer by capability
     * @cap:    Bit mask of capabilities to match
     *
     * Find a timer based upon capabilities bit mask. Callers of this function
     * should use the definitions found in the plat/dmtimer.h file under the
     * comment "timer capabilities used in hwmod database". Returns pointer to
     * timer handle on success and a NULL pointer on failure.
     */
    struct omap_dm_timer *omap_dm_timer_request_by_cap(u32 cap)
    {
        return _omap_dm_timer_request(REQUEST_BY_CAP, &cap);
    }
    
    /**
     * omap_dm_timer_request_by_node - Request a timer by device-tree node
     * @np:     Pointer to device-tree timer node
     *
     * Request a timer based upon a device node pointer. Returns pointer to
     * timer handle on success and a NULL pointer on failure.
     */
    struct omap_dm_timer *omap_dm_timer_request_by_node(struct device_node *np)
    {
        if (!np)
            return NULL;
    
        return _omap_dm_timer_request(REQUEST_BY_NODE, np);
    }
    
    int omap_dm_timer_free(struct omap_dm_timer *timer)
    {
        if (unlikely(!timer))
            return -EINVAL;
    
        clk_put(timer->fclk);
    
        WARN_ON(!timer->reserved);
        timer->reserved = 0;
        return 0;
    }
    
    void omap_dm_timer_enable(struct omap_dm_timer *timer)
    {
        int c;
    
        pm_runtime_get_sync(&timer->pdev->dev);
    
        if (!(timer->capability & OMAP_TIMER_ALWON)) {
            if (timer->get_context_loss_count) {
                c = timer->get_context_loss_count(&timer->pdev->dev);
                if (c != timer->ctx_loss_count) {
                    omap_timer_restore_context(timer);
                    timer->ctx_loss_count = c;
                }
            } else {
                omap_timer_restore_context(timer);
            }
        }
    }
    
    void omap_dm_timer_disable(struct omap_dm_timer *timer)
    {
        pm_runtime_put_sync(&timer->pdev->dev);
    }
    
    int omap_dm_timer_get_irq(struct omap_dm_timer *timer)
    {
        if (timer)
            return timer->irq;
        return -EINVAL;
    }
    
    #if defined(CONFIG_ARCH_OMAP1)
    #include <mach/hardware.h>
    /**
     * omap_dm_timer_modify_idlect_mask - Check if any running timers use ARMXOR
     * @inputmask: current value of idlect mask
     */
    __u32 omap_dm_timer_modify_idlect_mask(__u32 inputmask)
    {
        int i = 0;
        struct omap_dm_timer *timer = NULL;
        unsigned long flags;
    
        /* If ARMXOR cannot be idled this function call is unnecessary */
        if (!(inputmask & (1 << 1)))
            return inputmask;
    
        /* If any active timer is using ARMXOR return modified mask */
        spin_lock_irqsave(&dm_timer_lock, flags);
        list_for_each_entry(timer, &omap_timer_list, node) {
            u32 l;
    
            l = omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG);
            if (l & OMAP_TIMER_CTRL_ST) {
                if (((omap_readl(MOD_CONF_CTRL_1) >> (i * 2)) & 0x03) == 0)
                    inputmask &= ~(1 << 1);
                else
                    inputmask &= ~(1 << 2);
            }
            i++;
        }
        spin_unlock_irqrestore(&dm_timer_lock, flags);
    
        return inputmask;
    }
    
    #else
    
    struct clk *omap_dm_timer_get_fclk(struct omap_dm_timer *timer)
    {
        if (timer && !IS_ERR(timer->fclk))
            return timer->fclk;
        return NULL;
    }
    
    __u32 omap_dm_timer_modify_idlect_mask(__u32 inputmask)
    {
        BUG();
    
        return 0;
    }
    
    #endif
    
    int omap_dm_timer_trigger(struct omap_dm_timer *timer)
    {
        if (unlikely(!timer || pm_runtime_suspended(&timer->pdev->dev))) {
            pr_err("%s: timer not available or enabled.\n", __func__);
            return -EINVAL;
        }
    
        omap_dm_timer_write_reg(timer, OMAP_TIMER_TRIGGER_REG, 0);
        return 0;
    }
    
    int omap_dm_timer_start(struct omap_dm_timer *timer)
    {
        u32 l;
    
        if (unlikely(!timer))
            return -EINVAL;
    
        omap_dm_timer_enable(timer);
    
        l = omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG);
        if (!(l & OMAP_TIMER_CTRL_ST)) {
            l |= OMAP_TIMER_CTRL_ST;
            omap_dm_timer_write_reg(timer, OMAP_TIMER_CTRL_REG, l);
        }
    
        /* Save the context */
        timer->context.tclr = l;
        return 0;
    }
    
    int omap_dm_timer_stop(struct omap_dm_timer *timer)
    {
        unsigned long rate = 0;
    
        if (unlikely(!timer))
            return -EINVAL;
    
        if (!(timer->capability & OMAP_TIMER_NEEDS_RESET))
            rate = clk_get_rate(timer->fclk);
    
        __omap_dm_timer_stop(timer, timer->posted, rate);
    
        /*
         * Since the register values are computed and written within
         * __omap_dm_timer_stop, we need to use read to retrieve the
         * context.
         */
        timer->context.tclr =
                omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG);
        omap_dm_timer_disable(timer);
        return 0;
    }
    
    int omap_dm_timer_set_source(struct omap_dm_timer *timer, int source)
    {
        int ret;
        char *parent_name = NULL;
        struct clk *parent;
        struct dmtimer_platform_data *pdata;
    
        if (unlikely(!timer))
            return -EINVAL;
    
        pdata = timer->pdev->dev.platform_data;
    
        if (source < 0 || source >= 3)
            return -EINVAL;
    
        /*
         * FIXME: Used for OMAP1 devices only because they do not currently
         * use the clock framework to set the parent clock. To be removed
         * once OMAP1 migrated to using clock framework for dmtimers
         */
        if (pdata && pdata->set_timer_src)
            return pdata->set_timer_src(timer->pdev, source);
    
        if (IS_ERR(timer->fclk))
            return -EINVAL;
    
    
        /* Check if the clock has configurable parents */
        if (clk_hw_get_num_parents(__clk_get_hw(timer->fclk)) < 2)
            return 0;
    
        switch (source) {
        case OMAP_TIMER_SRC_SYS_CLK:
            parent_name = "timer_sys_ck";
            break;
    
        case OMAP_TIMER_SRC_32_KHZ:
            parent_name = "timer_32k_ck";
            break;
    
        case OMAP_TIMER_SRC_EXT_CLK:
            parent_name = "timer_ext_ck";
            break;
        }
    
        parent = clk_get(&timer->pdev->dev, parent_name);
        if (IS_ERR(parent)) {
            pr_err("%s: %s not found\n", __func__, parent_name);
            return -EINVAL;
        }
    
        ret = clk_set_parent(timer->fclk, parent);
        if (ret < 0)
            pr_err("%s: failed to set %s as parent\n", __func__,
                parent_name);
    
        clk_put(parent);
    
        return ret;
    }
    
    int omap_dm_timer_set_load(struct omap_dm_timer *timer, int autoreload,
                    unsigned int load)
    {
        u32 l;
    
        if (unlikely(!timer))
            return -EINVAL;
    
        omap_dm_timer_enable(timer);
        l = omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG);
        if (autoreload)
            l |= OMAP_TIMER_CTRL_AR;
        else
            l &= ~OMAP_TIMER_CTRL_AR;
        omap_dm_timer_write_reg(timer, OMAP_TIMER_CTRL_REG, l);
        omap_dm_timer_write_reg(timer, OMAP_TIMER_LOAD_REG, load);
    
        omap_dm_timer_write_reg(timer, OMAP_TIMER_TRIGGER_REG, 0);
        /* Save the context */
        timer->context.tclr = l;
        timer->context.tldr = load;
        omap_dm_timer_disable(timer);
        return 0;
    }
    
    /* Optimized set_load which removes costly spin wait in timer_start */
    int omap_dm_timer_set_load_start(struct omap_dm_timer *timer, int autoreload,
                                unsigned int load)
    {
        u32 l;
    
        if (unlikely(!timer))
            return -EINVAL;
    
        omap_dm_timer_enable(timer);
    
        l = omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG);
        if (autoreload) {
            l |= OMAP_TIMER_CTRL_AR;
            omap_dm_timer_write_reg(timer, OMAP_TIMER_LOAD_REG, load);
        } else {
            l &= ~OMAP_TIMER_CTRL_AR;
        }
        l |= OMAP_TIMER_CTRL_ST;
    
        __omap_dm_timer_load_start(timer, l, load, timer->posted);
    
        /* Save the context */
        timer->context.tclr = l;
        timer->context.tldr = load;
        timer->context.tcrr = load;
        return 0;
    }
    
    int omap_dm_timer_set_match(struct omap_dm_timer *timer, int enable,
                     unsigned int match)
    {
        u32 l;
    
        if (unlikely(!timer))
            return -EINVAL;
    
        omap_dm_timer_enable(timer);
        l = omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG);
        if (enable)
            l |= OMAP_TIMER_CTRL_CE;
        else
            l &= ~OMAP_TIMER_CTRL_CE;
        omap_dm_timer_write_reg(timer, OMAP_TIMER_MATCH_REG, match);
        omap_dm_timer_write_reg(timer, OMAP_TIMER_CTRL_REG, l);
    
        /* Save the context */
        timer->context.tclr = l;
        timer->context.tmar = match;
        omap_dm_timer_disable(timer);
        return 0;
    }
    
    int omap_dm_timer_set_pwm(struct omap_dm_timer *timer, int def_on,
                   int toggle, int trigger)
    {
        u32 l;
    
        if (unlikely(!timer))
            return -EINVAL;
    
        omap_dm_timer_enable(timer);
        l = omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG);
        l &= ~(OMAP_TIMER_CTRL_GPOCFG | OMAP_TIMER_CTRL_SCPWM |
               OMAP_TIMER_CTRL_PT | (0x03 << 10));
        if (def_on)
            l |= OMAP_TIMER_CTRL_SCPWM;
        if (toggle)
            l |= OMAP_TIMER_CTRL_PT;
        l |= trigger << 10;
        omap_dm_timer_write_reg(timer, OMAP_TIMER_CTRL_REG, l);
    
        /* Save the context */
        timer->context.tclr = l;
        omap_dm_timer_disable(timer);
        return 0;
    }
    
    int omap_dm_timer_set_prescaler(struct omap_dm_timer *timer, int prescaler)
    {
        u32 l;
    
        if (unlikely(!timer))
            return -EINVAL;
    
        omap_dm_timer_enable(timer);
        l = omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG);
        l &= ~(OMAP_TIMER_CTRL_PRE | (0x07 << 2));
        if (prescaler >= 0x00 && prescaler <= 0x07) {
            l |= OMAP_TIMER_CTRL_PRE;
            l |= prescaler << 2;
        }
        omap_dm_timer_write_reg(timer, OMAP_TIMER_CTRL_REG, l);
    
        /* Save the context */
        timer->context.tclr = l;
        omap_dm_timer_disable(timer);
        return 0;
    }
    
    int omap_dm_timer_set_int_enable(struct omap_dm_timer *timer,
                      unsigned int value)
    {
        if (unlikely(!timer))
            return -EINVAL;
    
        omap_dm_timer_enable(timer);
        __omap_dm_timer_int_enable(timer, value);
    
        /* Save the context */
        timer->context.tier = value;
        timer->context.twer = value;
        omap_dm_timer_disable(timer);
        return 0;
    }
    
    /**
     * omap_dm_timer_set_int_disable - disable timer interrupts
     * @timer:  pointer to timer handle
     * @mask:   bit mask of interrupts to be disabled
     *
     * Disables the specified timer interrupts for a timer.
     */
    int omap_dm_timer_set_int_disable(struct omap_dm_timer *timer, u32 mask)
    {
        u32 l = mask;
    
        if (unlikely(!timer))
            return -EINVAL;
    
        omap_dm_timer_enable(timer);
    
        if (timer->revision == 1)
            l = readl_relaxed(timer->irq_ena) & ~mask;
    
        writel_relaxed(l, timer->irq_dis);
        l = omap_dm_timer_read_reg(timer, OMAP_TIMER_WAKEUP_EN_REG) & ~mask;
        omap_dm_timer_write_reg(timer, OMAP_TIMER_WAKEUP_EN_REG, l);
    
        /* Save the context */
        timer->context.tier &= ~mask;
        timer->context.twer &= ~mask;
        omap_dm_timer_disable(timer);
        return 0;
    }
    
    unsigned int omap_dm_timer_read_status(struct omap_dm_timer *timer)
    {
        unsigned int l;
    
        if (unlikely(!timer || pm_runtime_suspended(&timer->pdev->dev))) {
            pr_err("%s: timer not available or enabled.\n", __func__);
            return 0;
        }
    
        l = readl_relaxed(timer->irq_stat);
    
        return l;
    }
    
    int omap_dm_timer_write_status(struct omap_dm_timer *timer, unsigned int value)
    {
        if (unlikely(!timer || pm_runtime_suspended(&timer->pdev->dev)))
            return -EINVAL;
    
        __omap_dm_timer_write_status(timer, value);
    
        return 0;
    }
    
    unsigned int omap_dm_timer_read_counter(struct omap_dm_timer *timer)
    {
        if (unlikely(!timer || pm_runtime_suspended(&timer->pdev->dev))) {
            pr_err("%s: timer not iavailable or enabled.\n", __func__);
            return 0;
        }
    
        return __omap_dm_timer_read_counter(timer, timer->posted);
    }
    
    int omap_dm_timer_write_counter(struct omap_dm_timer *timer, unsigned int value)
    {
        if (unlikely(!timer || pm_runtime_suspended(&timer->pdev->dev))) {
            pr_err("%s: timer not available or enabled.\n", __func__);
            return -EINVAL;
        }
    
        omap_dm_timer_write_reg(timer, OMAP_TIMER_COUNTER_REG, value);
    
        /* Save the context */
        timer->context.tcrr = value;
        return 0;
    }
    
    int omap_dm_timers_active(void)
    {
        struct omap_dm_timer *timer;
    
        list_for_each_entry(timer, &omap_timer_list, node) {
            if (!timer->reserved)
                continue;
    
            if (omap_dm_timer_read_reg(timer, OMAP_TIMER_CTRL_REG) &
                OMAP_TIMER_CTRL_ST) {
                return 1;
            }
        }
        return 0;
    }
    
    static const struct of_device_id omap_timer_match[];
    
    /**
     * omap_dm_timer_probe - probe function called for every registered device
     * @pdev:   pointer to current timer platform device
     *
     * Called by driver framework at the end of device registration for all
     * timer devices.
     */
    static int omap_dm_timer_probe(struct platform_device *pdev)
    {
        unsigned long flags;
        struct omap_dm_timer *timer;
        struct resource *mem, *irq;
        struct device *dev = &pdev->dev;
        const struct dmtimer_platform_data *pdata;
        int ret;
    
        pdata = of_device_get_match_data(dev);
        if (!pdata)
            pdata = dev_get_platdata(dev);
        else
            dev->platform_data = (void *)pdata;
    
        if (!pdata) {
            dev_err(dev, "%s: no platform data.\n", __func__);
            return -ENODEV;
        }
    
        irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
        if (unlikely(!irq)) {
            dev_err(dev, "%s: no IRQ resource.\n", __func__);
            return -ENODEV;
        }
    
        mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (unlikely(!mem)) {
            dev_err(dev, "%s: no memory resource.\n", __func__);
            return -ENODEV;
        }
    
        timer = devm_kzalloc(dev, sizeof(struct omap_dm_timer), GFP_KERNEL);
        if (!timer) {
            dev_err(dev, "%s: memory alloc failed!\n", __func__);
            return  -ENOMEM;
        }
    
        timer->fclk = ERR_PTR(-ENODEV);
        timer->io_base = devm_ioremap_resource(dev, mem);
        if (IS_ERR(timer->io_base))
            return PTR_ERR(timer->io_base);
    
        if (dev->of_node) {
            if (of_find_property(dev->of_node, "ti,timer-alwon", NULL))
                timer->capability |= OMAP_TIMER_ALWON;
            if (of_find_property(dev->of_node, "ti,timer-dsp", NULL))
                timer->capability |= OMAP_TIMER_HAS_DSP_IRQ;
            if (of_find_property(dev->of_node, "ti,timer-pwm", NULL))
                timer->capability |= OMAP_TIMER_HAS_PWM;
            if (of_find_property(dev->of_node, "ti,timer-secure", NULL))
                timer->capability |= OMAP_TIMER_SECURE;
        } else {
            timer->id = pdev->id;
            timer->capability = pdata->timer_capability;
            timer->reserved = omap_dm_timer_reserved_systimer(timer->id);
            timer->get_context_loss_count = pdata->get_context_loss_count;
        }
    
        if (pdata)
            timer->errata = pdata->timer_errata;
    
        timer->irq = irq->start;
        timer->pdev = pdev;
    
        pm_runtime_enable(dev);
        pm_runtime_irq_safe(dev);
    
        if (!timer->reserved) {
            ret = pm_runtime_get_sync(dev);
            if (ret < 0) {
                dev_err(dev, "%s: pm_runtime_get_sync failed!\n",
                    __func__);
                goto err_get_sync;
            }
            __omap_dm_timer_init_regs(timer);
            pm_runtime_put(dev);
        }
    
        /* add the timer element to the list */
        spin_lock_irqsave(&dm_timer_lock, flags);
        list_add_tail(&timer->node, &omap_timer_list);
        spin_unlock_irqrestore(&dm_timer_lock, flags);
    
        dev_dbg(dev, "Device Probed.\n");
    
        return 0;
    
    err_get_sync:
        pm_runtime_put_noidle(dev);
        pm_runtime_disable(dev);
        return ret;
    }
    
    /**
     * omap_dm_timer_remove - cleanup a registered timer device
     * @pdev:   pointer to current timer platform device
     *
     * Called by driver framework whenever a timer device is unregistered.
     * In addition to freeing platform resources it also deletes the timer
     * entry from the local list.
     */
    static int omap_dm_timer_remove(struct platform_device *pdev)
    {
        struct omap_dm_timer *timer;
        unsigned long flags;
        int ret = -EINVAL;
    
        spin_lock_irqsave(&dm_timer_lock, flags);
        list_for_each_entry(timer, &omap_timer_list, node)
            if (!strcmp(dev_name(&timer->pdev->dev),
                    dev_name(&pdev->dev))) {
                list_del(&timer->node);
                ret = 0;
                break;
            }
        spin_unlock_irqrestore(&dm_timer_lock, flags);
    
        pm_runtime_disable(&pdev->dev);
    
        return ret;
    }
    
    const static struct omap_dm_timer_ops dmtimer_ops = {
        .request_by_node = omap_dm_timer_request_by_node,
        .request_specific = omap_dm_timer_request_specific,
        .request = omap_dm_timer_request,
        .set_source = omap_dm_timer_set_source,
        .get_irq = omap_dm_timer_get_irq,
        .set_int_enable = omap_dm_timer_set_int_enable,
        .set_int_disable = omap_dm_timer_set_int_disable,
        .free = omap_dm_timer_free,
        .enable = omap_dm_timer_enable,
        .disable = omap_dm_timer_disable,
        .get_fclk = omap_dm_timer_get_fclk,
        .start = omap_dm_timer_start,
        .stop = omap_dm_timer_stop,
        .set_load = omap_dm_timer_set_load,
        .set_match = omap_dm_timer_set_match,
        .set_pwm = omap_dm_timer_set_pwm,
        .set_prescaler = omap_dm_timer_set_prescaler,
        .read_counter = omap_dm_timer_read_counter,
        .write_counter = omap_dm_timer_write_counter,
        .read_status = omap_dm_timer_read_status,
        .write_status = omap_dm_timer_write_status,
    };
    
    /*****************************************
     * skaskawl - end
     * 2019.07.18
     ****************************************/
    #endif
    
    #define DRIVER_AUTHOR   "skaskawl"
    #define DRIVER_DESC "Timer Module"
    #define DRIVER_NAME "study_module_timer"
    
    
    static struct omap_dm_timer *timer_ptr = NULL;
    static int32_t timer_irq;
    static uint32_t timer_rate;
    
    
    static irqreturn_t timer_irq_handler( int irq, void * dev_id)
    {
        int status = 0;
        int irq_count = 0;
    
        switch(irq_count)
        {
            case 0:
                irq_count = 1;
                break;
            case 1:
                irq_count = 0;
                break;
            default:
                break;
        }
    
        gpio_set_value(215,irq_count);
    
        /* Read the current Status */
        status = omap_dm_timer_read_status(timer_ptr);
    
        /* Clear the timer interrupt */
        if (status == OMAP_TIMER_INT_MATCH)
        {
            omap_dm_timer_write_status(timer_ptr,  OMAP_TIMER_INT_MATCH);
        }
    
        omap_dm_timer_write_status(timer_ptr,OMAP_TIMER_INT_MATCH | OMAP_TIMER_INT_CAPTURE);
    
        omap_dm_timer_read_status(timer_ptr);
    
    #if 0
        /* Stop the Timer */
        omap_dm_timer_stop(timer_ptr);
    #endif
    
        /* Indicate the Interrupt was handled */
        return IRQ_HANDLED;
    }
    
    static int test_module_init(struct platform_device *pdev)
    {
        int ret = 1;
        struct clk *timer_clk;
    
        struct device_node *timer_device_node;
    
        gpio_request(215,"led start");
        gpio_export(215,1);
        gpio_direction_output(215,1);
    
       /* Attempt to request a timer based on the device node */
       timer_device_node = of_parse_phandle(pdev->dev.of_node, "DMtimer", 0);
       timer_ptr = omap_dm_timer_request_by_node(timer_device_node);
    #if 0
       if(timer_ptr == NULL){
        /* no timers available */
        printk(KERN_INFO "Can't request specified DM Timer\n");
        return -1;
        }
    #endif
    
        /* Set the Clock source to the System Clock */
        ret = omap_dm_timer_set_source(timer_ptr, OMAP_TIMER_SRC_SYS_CLK);
    
        /* Determine what IRQ the timer triggers */
        timer_irq = omap_dm_timer_get_irq(timer_ptr);
    
        /* Setup the IRQ handler */
        ret = request_irq(timer_irq, timer_irq_handler, IRQF_TIMER, DRIVER_NAME, NULL);
    
        /* Setup the timer to trigger the IRQ on the match event */
        omap_dm_timer_set_int_enable(timer_ptr, OMAP_TIMER_INT_MATCH);
    
        /* Get the Clock rate in Hz */
        timer_clk = omap_dm_timer_get_fclk(timer_ptr);
        timer_rate = clk_get_rate(timer_clk);
    
        /* Setup the Output for the Timer pin */
        /* Setup to toggle on the overflow and the compare match event */
        omap_dm_timer_set_pwm(timer_ptr, 0, 1, OMAP_TIMER_TRIGGER_OVERFLOW_AND_COMPARE);
    
        /* Enable the Timer */
        /* Needs to be done before we can write to the counter */
        omap_dm_timer_enable(timer_ptr);
    
        /* Set the initial Count */
        /* According to section 20.1.3.5 Pulse-Width Modulation, an overflow or match be used to toggle when a compare condition occurs*/
        /* Therefore it we will trigger the overflow event almost immediately to ensure our toggle will be generated quickly */
        omap_dm_timer_write_counter(timer_ptr, (0xFFFFFFFF - 5));
    
        /* Setup the Load Register */
        /* Setup as autoload to set the the counter back to 0 on an overflow */
        omap_dm_timer_set_load(timer_ptr, 1, 0);
    
        /* Set the the compare register to 1 second */
        /* This will ensure the signal toggle of 1 second after the overflow event*/
        omap_dm_timer_set_match(timer_ptr, 1, timer_rate);
    
        /* Start the timer */
        ret = omap_dm_timer_start(timer_ptr);
        printk(KERN_INFO "Timer Start ret %d\n", ret);
    
        /*
        * A non 0 returns means the call to init_modules failed; module can't be loaded
        */
    
    }
    
    
    static int test_module_exit(struct platform_device *pdev)
    {
        int ret = 1;
    
        gpio_free(4);
        /* stop the timer */
        ret = omap_dm_timer_stop(timer_ptr);
        printk(KERN_INFO "Timer Stop ret = %d\n", ret);
    
        /* Release the IRQ handler */
        free_irq(timer_irq, NULL);
        printk(KERN_INFO "Free IRQ Done\n");
    
        /* Release the timer */
        ret = omap_dm_timer_free(timer_ptr);
        printk(KERN_INFO "Timer Free ret = %d\n", ret);
    
    
        return 0;
    }
    
    static const struct of_device_id test_module_timer_of_match[] = {
        { .compatible   = "test,study_module_timer" },
        {},
    };
    
    static struct platform_driver test_module_timer_driver = {
        .driver = {
            .name   = DRIVER_NAME,
            .owner  = THIS_MODULE,
            .of_match_table = test_module_timer_of_match,
        },
        .probe  = test_module_init,
        .remove = test_module_exit,
    };
    module_platform_driver(test_module_timer_driver);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR(DRIVER_AUTHOR);       /* Who wrote this module */
    MODULE_DESCRIPTION(DRIVER_DESC);    /* What is this module */
    
    

    2. Makefile


    #skaskawl-190716 : Timer module makefile
    PWD :=$(shell pwd)
    KDIR :=/home/ksm/ksmwork/Platform/study/Kernel
    KINCLUDE := -I/home/ksm/ksmwork/Platform/study/Kernel/include/
    KINCLUDE += -I/home/ksm/ksmwork/Platform/study/Kernel/arch/arm/plat-omap/include/


    obj-m += study_timer_module.o study2_timer_module.o study3_timer_module.o

    compile :
    make -C $(KDIR) ARCH=arm CROSS_COMPILE=../Toolchain/bin/arm-linux-gnueabihf- SUBDIRS=$(PWD) modules ${KINCLUDE}


    clean :
    make -C $(KDIR) ARCH=arm CROSS_COMPILE=../Toolchain/bin/arm-linux-gnueabihf- SUBDIRS=$(PWD) clean

  • Sunmin,

    I would suggest you to make fresh install of PSDK Linux-RT into below folder:

    /home/ksm/ksmwork/Platform/study/

    Thus you will have:

    /home/ksm/ksmwork/Platform/study/ti-processor-sdk-linux-rt-am57xx-evm-05.03.00.07/

    /home/ksm/ksmwork/Platform/study/ti-processor-sdk-linux-rt-am57xx-evm-05.03.00.07/board-support/linux-rt-4.14.79+gitAUTOINC+a72bf1418c-ga72bf1418c/

    /home/ksm/ksmwork/Platform/study/ti-processor-sdk-linux-rt-am57xx-evm-05.03.00.07/board-support/extra-drivers/

    Then you can place your external kernel timer module into below folder:

    /home/ksm/ksmwork/Platform/study/ti-processor-sdk-linux-rt-am57xx-evm-05.03.00.07/board-support/extra-drivers/study_timer_module/

    Place your C, H and makefile there. You can also explore how debugss, gdbserverproxy and uio external modules are created, check about C/H and makefiles.

    Regards,
    Pavel

  • HI, Pavel Botev

    I solved this problem.

    The problem is that the functions of timer-ti-dm.c are not declared EXPORT_SYMBOL.

    Thank you very much for your help.