diff options
Diffstat (limited to 'arch/arm/mach-imx/epit.c')
-rw-r--r-- | arch/arm/mach-imx/epit.c | 457 |
1 files changed, 305 insertions, 152 deletions
diff --git a/arch/arm/mach-imx/epit.c b/arch/arm/mach-imx/epit.c index e02de188ae83..af9299dae573 100644 --- a/arch/arm/mach-imx/epit.c +++ b/arch/arm/mach-imx/epit.c @@ -1,4 +1,14 @@ /* + * Copyright (C) 2005-2014 Freescale Semiconductor, Inc. All Rights Reserved. + */ +/* + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + * * linux/arch/arm/plat-mxc/epit.c * * Copyright (C) 2010 Sascha Hauer <s.hauer@pengutronix.de> @@ -11,215 +21,358 @@ * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ - -#define EPITCR 0x00 -#define EPITSR 0x04 -#define EPITLR 0x08 -#define EPITCMPR 0x0c -#define EPITCNR 0x10 - -#define EPITCR_EN (1 << 0) -#define EPITCR_ENMOD (1 << 1) -#define EPITCR_OCIEN (1 << 2) -#define EPITCR_RLD (1 << 3) -#define EPITCR_PRESC(x) (((x) & 0xfff) << 4) -#define EPITCR_SWR (1 << 16) -#define EPITCR_IOVW (1 << 17) -#define EPITCR_DBGEN (1 << 18) -#define EPITCR_WAITEN (1 << 19) -#define EPITCR_RES (1 << 20) -#define EPITCR_STOPEN (1 << 21) -#define EPITCR_OM_DISCON (0 << 22) -#define EPITCR_OM_TOGGLE (1 << 22) -#define EPITCR_OM_CLEAR (2 << 22) -#define EPITCR_OM_SET (3 << 22) -#define EPITCR_CLKSRC_OFF (0 << 24) -#define EPITCR_CLKSRC_PERIPHERAL (1 << 24) -#define EPITCR_CLKSRC_REF_HIGH (1 << 24) -#define EPITCR_CLKSRC_REF_LOW (3 << 24) - -#define EPITSR_OCIF (1 << 0) - +#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/of_device.h> +#include <linux/fsl_devices.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/clockchips.h> -#include <linux/clk.h> -#include <linux/err.h> #include <asm/mach/time.h> +#include "epit.h" + +struct epit_device { + struct list_head node; + struct platform_device *pdev; + const char *label; + struct clk *clk_per; + struct clk *clk_ipg; + int clk_enabled; + void __iomem *mmio_base; + unsigned int use_count; + unsigned int id; + unsigned int irq; + int mode; + void (*cb)(void *); + void *cb_para; +}; -#include "common.h" -#include "hardware.h" - -static struct clock_event_device clockevent_epit; -static enum clock_event_mode clockevent_mode = CLOCK_EVT_MODE_UNUSED; -static void __iomem *timer_base; +static DEFINE_MUTEX(epit_lock); +static LIST_HEAD(epit_list); -static inline void epit_irq_disable(void) +static inline void epit_irq_disable(struct epit_device *epit) { u32 val; - val = __raw_readl(timer_base + EPITCR); + val = readl(epit->mmio_base + EPITCR); val &= ~EPITCR_OCIEN; - __raw_writel(val, timer_base + EPITCR); + writel(val, epit->mmio_base + EPITCR); } -static inline void epit_irq_enable(void) +static inline void epit_irq_enable(struct epit_device *epit) { u32 val; - val = __raw_readl(timer_base + EPITCR); + val = readl(epit->mmio_base + EPITCR); val |= EPITCR_OCIEN; - __raw_writel(val, timer_base + EPITCR); + writel(val, epit->mmio_base + EPITCR); } -static void epit_irq_acknowledge(void) +static inline void epit_irq_acknowledge(struct epit_device *epit) { - __raw_writel(EPITSR_OCIF, timer_base + EPITSR); + writel(EPITSR_OCIF, epit->mmio_base + EPITSR); } -static int __init epit_clocksource_init(struct clk *timer_clk) +static irqreturn_t epit_timer_interrupt(int irq, void *epit) { - unsigned int c = clk_get_rate(timer_clk); - - return clocksource_mmio_init(timer_base + EPITCNR, "epit", c, 200, 32, - clocksource_mmio_readl_down); + struct epit_device *epit_dev = epit; + u32 cr = 0; + + + if (epit_dev) { + /* stop EPIT timer */ + cr = readl(epit_dev->mmio_base + EPITCR); + cr &= ~(EPITCR_EN); + writel(cr, epit_dev->mmio_base + EPITCR); + /* clear EPIT interrupt flag */ + epit_irq_acknowledge(epit_dev); + if (epit_dev->cb) + epit_dev->cb(epit_dev->cb_para); + } + return IRQ_HANDLED; } -/* clock event */ - -static int epit_set_next_event(unsigned long evt, - struct clock_event_device *unused) +int epit_config(struct epit_device *epit, int mode, void *cb, void *para) { - unsigned long tcmp; - - tcmp = __raw_readl(timer_base + EPITCNR); - - __raw_writel(tcmp - evt, timer_base + EPITCMPR); + u32 cr; + unsigned int rc; + + if (epit == NULL) + return -EINVAL; + + rc = clk_prepare_enable(epit->clk_ipg); + if (rc) + return rc; + + epit->cb = cb; + epit->cb_para = para; + + /*SW Reset EPIT */ + writel(EPITCR_SWR, epit->mmio_base + EPITCR); + while (readl(epit->mmio_base + EPITCR) & EPITCR_SWR) + ; + + /* reset EPIT register */ + writel(0x0, epit->mmio_base + EPITCR); + /* clear EPIT interrupt flag */ + writel(EPITSR_OCIF, epit->mmio_base + EPITSR); + /* set EPIT mode and clk src */ + cr = (mode << 3) | EPITCR_CLKSRC_REF_HIGH; + cr |= (EPITCR_WAITEN | EPITCR_OCIEN | EPITCR_RLD); + writel(cr, epit->mmio_base + EPITCR); + /* write load counter */ + writel(0xFFFFFFFF, epit->mmio_base + EPITLR); + + epit->mode = mode; + + if (!epit->clk_enabled) { + rc = clk_prepare_enable(epit->clk_per); + if (!rc) + epit->clk_enabled = 1; + } + /* Enable EPIT IRQ */ + epit_irq_enable(epit); + clk_disable_unprepare(epit->clk_ipg); return 0; } +EXPORT_SYMBOL(epit_config); -static void epit_set_mode(enum clock_event_mode mode, - struct clock_event_device *evt) +void epit_start(struct epit_device *epit, int time_ns) { - unsigned long flags; - - /* - * The timer interrupt generation is disabled at least - * for enough time to call epit_set_next_event() - */ - local_irq_save(flags); - - /* Disable interrupt in GPT module */ - epit_irq_disable(); - - if (mode != clockevent_mode) { - /* Set event time into far-far future */ - - /* Clear pending interrupt */ - epit_irq_acknowledge(); - } - - /* Remember timer mode */ - clockevent_mode = mode; - local_irq_restore(flags); - - switch (mode) { - case CLOCK_EVT_MODE_PERIODIC: - printk(KERN_ERR "epit_set_mode: Periodic mode is not " - "supported for i.MX EPIT\n"); - break; - case CLOCK_EVT_MODE_ONESHOT: - /* - * Do not put overhead of interrupt enable/disable into - * epit_set_next_event(), the core has about 4 minutes - * to call epit_set_next_event() or shutdown clock after - * mode switching - */ - local_irq_save(flags); - epit_irq_enable(); - local_irq_restore(flags); - break; - case CLOCK_EVT_MODE_SHUTDOWN: - case CLOCK_EVT_MODE_UNUSED: - case CLOCK_EVT_MODE_RESUME: - /* Left event sources disabled, no more interrupts appear */ - break; + u32 compare_count = 0; + unsigned long long int c; + unsigned int cycles, prescale; + u32 cr; + + c = clk_get_rate(epit->clk_per); + c = c * time_ns; + do_div(c, 1000000000); + cycles = c; + prescale = cycles / 0x10000 + 1; + cycles /= (prescale + 1); + /* select prescale */ + cr = readl(epit->mmio_base + EPITCR); + cr &= ~(EPITCR_PRESC(0xFFF)); + cr |= EPITCR_PRESC(prescale) ; + writel(cr, epit->mmio_base + EPITCR); + /* write compare count */ + if (EPIT_FREE_RUN_MODE == epit->mode) { + compare_count = (0xFFFFFFFF - cycles + 1); /* down counter */ + } else { + cr = readl(epit->mmio_base + EPITLR); + compare_count = cr - cycles + 1; } + writel(compare_count, epit->mmio_base + EPITCMPR); + /* set EPIT Timer Mode */ + cr = readl(epit->mmio_base + EPITCR); + if (EPIT_FREE_RUN_MODE == epit->mode) + cr |= EPITCR_ENMOD; + + writel(cr, epit->mmio_base + EPITCR); + /* start EPIT timer */ + cr = readl(epit->mmio_base + EPITCR); + cr |= EPITCR_EN; + writel(cr, epit->mmio_base + EPITCR); } +EXPORT_SYMBOL(epit_start); -/* - * IRQ handler for the timer - */ -static irqreturn_t epit_timer_interrupt(int irq, void *dev_id) +void epit_stop(struct epit_device *epit) { - struct clock_event_device *evt = &clockevent_epit; - - epit_irq_acknowledge(); + u32 cr = 0; + + epit_irq_disable(epit); + cr = readl(epit->mmio_base + EPITCR); + cr &= ~EPITCR_EN; + writel(cr, epit->mmio_base + EPITCR); + /* reset EPIT register */ + writel(EPITCR_SWR, epit->mmio_base + EPITCR); + while (readl(epit->mmio_base + EPITCR) & EPITCR_SWR) + ; + + if (epit->clk_enabled) { + clk_disable_unprepare(epit->clk_per); + epit->clk_enabled = 0; + } +} +EXPORT_SYMBOL(epit_stop); - evt->event_handler(evt); +struct epit_device *epit_request(int epit_id, const char *label) +{ + struct epit_device *epit; + int found = 0; + + mutex_lock(&epit_lock); + list_for_each_entry(epit, &epit_list, node) { + if (epit->id == epit_id) { + found = 1; + break; + } + } + if (found) { + if (epit->use_count == 0) { + epit->use_count++; + epit->label = label; + } else { + epit = ERR_PTR(-EBUSY); + } + } else { + epit = ERR_PTR(-ENOENT); + } - return IRQ_HANDLED; + mutex_unlock(&epit_lock); + return epit; } +EXPORT_SYMBOL(epit_request); -static struct irqaction epit_timer_irq = { - .name = "i.MX EPIT Timer Tick", - .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL, - .handler = epit_timer_interrupt, -}; +void epit_free(struct epit_device *epit) +{ + mutex_lock(&epit_lock); + if (epit->use_count) { + epit->use_count--; + epit->label = NULL; + } else { + pr_warning("EPIT device already freed\n"); + } + mutex_unlock(&epit_lock); +} +EXPORT_SYMBOL(epit_free); -static struct clock_event_device clockevent_epit = { - .name = "epit", - .features = CLOCK_EVT_FEAT_ONESHOT, - .set_mode = epit_set_mode, - .set_next_event = epit_set_next_event, - .rating = 200, +#if defined(CONFIG_OF) +static const struct of_device_id mxc_epit_dt_ids[] = { + { .compatible = "fsl,imx6q-epit", }, + { /* sentinel */ } }; +MODULE_DEVICE_TABLE(of, mxc_epit_dt_ids); +#endif -static int __init epit_clockevent_init(struct clk *timer_clk) +static int mxc_epit_probe(struct platform_device *pdev) { - clockevent_epit.cpumask = cpumask_of(0); - clockevents_config_and_register(&clockevent_epit, - clk_get_rate(timer_clk), - 0x800, 0xfffffffe); + struct epit_device *epit; + struct resource *r; + int ret = 0; + struct device_node *np = pdev->dev.of_node; + + epit = kzalloc(sizeof(struct epit_device), GFP_KERNEL); + if (epit == NULL) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + epit->clk_per = devm_clk_get(&pdev->dev, "per"); + if (IS_ERR(epit->clk_per)) { + dev_err(&pdev->dev, "getting per clock failed with %ld\n", + PTR_ERR(epit->clk_per)); + return PTR_ERR(epit->clk_per); + } + + epit->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(epit->clk_ipg)) { + dev_err(&pdev->dev, "getting ipg clock failed with %ld\n", + PTR_ERR(epit->clk_ipg)); + return PTR_ERR(epit->clk_ipg); + } + + epit->clk_enabled = 0; + epit->use_count = 0; + ret = of_property_read_u32(np, "epit-id", &(epit->id)); + if (ret < 0) { + dev_dbg(&pdev->dev, "can not get epit id\n"); + goto err_free; + } + epit->pdev = pdev; + + r = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + epit->irq = (unsigned int)(r->start); + if (!epit->irq) { + dev_err(&pdev->dev, "no irq resource?\n"); + ret = -ENODEV; + goto err_free; + } + + /* setup IRQ */ + if (request_irq(epit->irq, epit_timer_interrupt, IRQF_DISABLED, "epit", + (void *)epit)) { + printk(KERN_ERR "rtc: cannot register IRQ %d\n", epit->irq); + ret = -EIO; + goto err_free; + } + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) { + dev_err(&pdev->dev, "no memory resource defined\n"); + ret = -ENODEV; + goto err_free; + } + + r = request_mem_region(r->start, r->end - r->start + 1, pdev->name); + if (r == NULL) { + dev_err(&pdev->dev, "failed to request memory resource\n"); + ret = -EBUSY; + goto err_free; + } + epit->mmio_base = ioremap(r->start, r->end - r->start + 1); + if (epit->mmio_base == NULL) { + dev_err(&pdev->dev, "failed to ioremap() registers\n"); + ret = -ENODEV; + goto err_free_mem; + } + + mutex_lock(&epit_lock); + list_add_tail(&epit->node, &epit_list); + mutex_unlock(&epit_lock); + platform_set_drvdata(pdev, epit); return 0; +err_free_mem: + release_mem_region(r->start, r->end - r->start + 1); +err_free: + kfree(epit); + return ret; } -void __init epit_timer_init(void __iomem *base, int irq) +static int mxc_epit_remove(struct platform_device *pdev) { - struct clk *timer_clk; + struct epit_device *epit; + struct resource *r; - timer_clk = clk_get_sys("imx-epit.0", NULL); - if (IS_ERR(timer_clk)) { - pr_err("i.MX epit: unable to get clk\n"); - return; - } + epit = platform_get_drvdata(pdev); + if (epit == NULL) + return -ENODEV; - clk_prepare_enable(timer_clk); + mutex_lock(&epit_lock); + list_del(&epit->node); + mutex_unlock(&epit_lock); - timer_base = base; + iounmap(epit->mmio_base); + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(r->start, r->end - r->start + 1); + kfree(epit); + return 0; +} - /* - * Initialise to a known state (all timers off, and timing reset) - */ - __raw_writel(0x0, timer_base + EPITCR); +static struct platform_driver mxc_epit_driver = { + .probe = mxc_epit_probe, + .remove = mxc_epit_remove, + .driver = { + .name = "mxc_epit", + .owner = THIS_MODULE, + .of_match_table = mxc_epit_dt_ids, + }, +}; - __raw_writel(0xffffffff, timer_base + EPITLR); - __raw_writel(EPITCR_EN | EPITCR_CLKSRC_REF_HIGH | EPITCR_WAITEN, - timer_base + EPITCR); +module_platform_driver(mxc_epit_driver) - /* init and register the timer to the framework */ - epit_clocksource_init(timer_clk); - epit_clockevent_init(timer_clk); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("IMX6 Enhance PIT driver"); - /* Make irqs happen */ - setup_irq(irq, &epit_timer_irq); -} |