| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Copyright (c) 2019 Amlogic, Inc. All rights reserved. |
| */ |
| |
| #include <linux/kernel.h> |
| #include <linux/device.h> |
| #include <linux/init.h> |
| #include <linux/interrupt.h> |
| #include <linux/irq.h> |
| #include <linux/io.h> |
| #include <linux/clockchips.h> |
| #include <linux/clocksource.h> |
| #include <linux/stat.h> |
| #include <asm/memory.h> |
| #include <linux/sched_clock.h> |
| #include <linux/of.h> |
| #include <linux/of_irq.h> |
| #include <linux/of_address.h> |
| #include <linux/arm-smccc.h> |
| #include <linux/amlogic/cpu_version.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/of_platform.h> |
| |
| #undef pr_fmt |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| |
| #define TIMER_E_RESOLUTION_BIT 8 |
| #define TIMER_E_ENABLE_BIT 20 |
| #define TIMER_E_RESOLUTION_MASK (7UL << TIMER_E_RESOLUTION_BIT) |
| |
| #define TIMER_RESOLUTION_1us 0 |
| #define TIMER_RESOLUTION_10us 1 |
| #define TIMER_RESOLUTION_100us 2 |
| #define TIMER_RESOLUTION_1ms 3 |
| |
| /********** Clock Event Device, Timer-ABCD/FGHI *********/ |
| struct meson_clock { |
| const char *name; /*A,B,C,D,F,G,H,I*/ |
| int bit_enable; |
| int bit_mode; |
| int bit_resolution; |
| int min_delta_ns; |
| void __iomem *reg; |
| unsigned int init_flag; |
| }; |
| |
| static struct meson_clock bc_clock; |
| static DEFINE_SPINLOCK(time_lock); |
| |
| static inline void aml_set_reg32_mask(void __iomem *_reg, const u32 _mask) |
| { |
| u32 _val; |
| |
| _val = readl_relaxed(_reg) | _mask; |
| |
| writel_relaxed(_val, _reg); |
| } |
| |
| static inline void |
| aml_set_reg32_bits(void __iomem *_reg, const u32 _value, |
| const u32 _start, const u32 _len) |
| { |
| writel_relaxed(((readl_relaxed(_reg) & |
| ~(((1L << (_len)) - 1) << (_start))) | |
| (((_value) & ((1L << (_len)) - 1)) << (_start))), _reg); |
| } |
| |
| static int meson_set_next_event(unsigned long evt, |
| struct clock_event_device *dev) |
| { |
| struct meson_clock *clk = &bc_clock; |
| |
| if (clk->min_delta_ns != 1) { |
| if ((evt & 0xffff) <= 50) { |
| /*pr_info("timer counter is too small!\n");*/ |
| return -1; |
| } |
| } |
| /* use a big number to clear previous trigger cleanly */ |
| aml_set_reg32_mask(clk->reg + 4, evt & 0xffff); |
| /* then set next event */ |
| aml_set_reg32_bits(clk->reg + 4, evt, 0, 16); |
| return 0; |
| } |
| |
| static int meson_clkevt_shutdown(struct clock_event_device *dev) |
| { |
| struct meson_clock *clk = &bc_clock; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&time_lock, flags); |
| /* pr_info("Disable timer %p %s\n",dev,dev->name); */ |
| aml_set_reg32_bits(clk->reg, 0, clk->bit_enable, 1); |
| spin_unlock_irqrestore(&time_lock, flags); |
| return 0; |
| } |
| |
| static int meson_clkevt_set_periodic(struct clock_event_device *dev) |
| { |
| struct meson_clock *clk = &bc_clock; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&time_lock, flags); |
| aml_set_reg32_bits(clk->reg, 1, clk->bit_mode, 1); |
| aml_set_reg32_bits(clk->reg, 1, clk->bit_enable, 1); |
| /* pr_info("Periodic timer %s!,reg=%x\n",*/ |
| /* dev->name,readl_relaxed(clk->reg)); */ |
| spin_unlock_irqrestore(&time_lock, flags); |
| return 0; |
| } |
| |
| static int meson_clkevt_set_oneshot(struct clock_event_device *dev) |
| { |
| struct meson_clock *clk = &bc_clock; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&time_lock, flags); |
| aml_set_reg32_bits(clk->reg, 0, clk->bit_mode, 1); |
| aml_set_reg32_bits(clk->reg, 1, clk->bit_enable, 1); |
| /* pr_info("One shot timer %s!reg=%x\n",*/ |
| /* dev->name,readl_relaxed(clk->reg)); */ |
| spin_unlock_irqrestore(&time_lock, flags); |
| return 0; |
| } |
| |
| static int meson_clkevt_resume(struct clock_event_device *dev) |
| { |
| struct meson_clock *clk = &bc_clock; |
| unsigned long flags; |
| |
| spin_lock_irqsave(&time_lock, flags); |
| /* pr_info("Resume timer%s\n", dev->name); */ |
| aml_set_reg32_bits(clk->reg, 1, clk->bit_enable, 1); |
| spin_unlock_irqrestore(&time_lock, flags); |
| return 0; |
| } |
| |
| /* Clock event timer interrupt handler */ |
| static irqreturn_t meson_timer_interrupt(int irq, void *dev_id) |
| { |
| struct clock_event_device *evt = dev_id; |
| struct arm_smccc_res res __attribute__((unused)); |
| |
| if (!evt || !evt->event_handler) { |
| WARN_ONCE(!evt || !evt->event_handler, |
| "%p %s %p %d", |
| evt, evt ? evt->name : NULL, |
| evt ? evt->event_handler : NULL, irq); |
| return IRQ_HANDLED; |
| } |
| evt->event_handler(evt); |
| |
| return IRQ_HANDLED; |
| } |
| |
| static struct clock_event_device meson_clockevent = { |
| .cpumask = cpu_possible_mask, |
| .set_next_event = meson_set_next_event, |
| .set_state_shutdown = meson_clkevt_shutdown, |
| .set_state_periodic = meson_clkevt_set_periodic, |
| .set_state_oneshot = meson_clkevt_set_oneshot, |
| .tick_resume = meson_clkevt_resume, |
| }; |
| |
| static int meson_bc_timer_init(struct device_node *np) |
| { |
| struct meson_clock *mclk = &bc_clock; |
| int irq; |
| int ret; |
| unsigned int resolution_1us = TIMER_RESOLUTION_1us; |
| unsigned int min_delta_ns = 1; |
| |
| if (of_property_read_string(np, "timer_name", |
| &meson_clockevent.name)) { |
| pr_err("fail to read timer_name\n"); |
| return -1; |
| } |
| |
| if (of_property_read_u32(np, "clockevent-rating", |
| &meson_clockevent.rating)) { |
| pr_err("failed to read clockevent-rating\n"); |
| return -1; |
| } |
| |
| if (of_property_read_u32(np, "clockevent-shift", |
| &meson_clockevent.shift)) { |
| pr_err("failed to read clockevent-shift\n"); |
| return -1; |
| } |
| |
| if (of_property_read_u32(np, "clockevent-features", |
| &meson_clockevent.features)) { |
| pr_err("failed to read clockevent-features\n"); |
| return -1; |
| } |
| |
| if (of_property_read_u32(np, "bit_enable", &mclk->bit_enable)) { |
| pr_err("failed to read bit_enable\n"); |
| return -1; |
| } |
| |
| if (of_property_read_u32(np, "bit_mode", &mclk->bit_mode)) { |
| pr_err("failed to read bit_mode\n"); |
| return -1; |
| } |
| |
| if (of_property_read_u32(np, "bit_resolution", |
| &mclk->bit_resolution)) { |
| pr_err("failed to read bit_resolution\n"); |
| return -1; |
| } |
| /* A1 1us resolution value is changed from 0 to 1*/ |
| of_property_read_u32(np, "resolution_1us", &resolution_1us); |
| |
| /* A1/C1 min delta change to 10, default is 1*/ |
| of_property_read_u32(np, "min_delta_ns", &min_delta_ns); |
| mclk->min_delta_ns = min_delta_ns; |
| |
| mclk->reg = of_iomap(np, 0); |
| if (!mclk->reg) { |
| pr_err("failed to iomap mux reg\n"); |
| return -1; |
| } |
| |
| irq = irq_of_parse_and_map(np, 0); |
| if (!irq) { |
| pr_err("failed to map irq\n"); |
| iounmap(mclk->reg); |
| return -1; |
| } |
| |
| aml_set_reg32_mask(mclk->reg, |
| ((1 << mclk->bit_mode) |
| | (resolution_1us << mclk->bit_resolution))); |
| |
| meson_clockevent.mult = div_sc(1000000, NSEC_PER_SEC, 20); |
| meson_clockevent.max_delta_ns = |
| clockevent_delta2ns(0xfffe, &meson_clockevent); |
| meson_clockevent.min_delta_ns = |
| clockevent_delta2ns(min_delta_ns, &meson_clockevent); |
| meson_clockevent.irq = irq; |
| |
| ret = request_irq(irq, |
| meson_timer_interrupt, |
| IRQF_TIMER | IRQF_IRQPOLL | IRQF_TRIGGER_RISING, |
| meson_clockevent.name, |
| &meson_clockevent); |
| if (ret) { |
| pr_err("request irq:%d failed=%d\n", irq, ret); |
| iounmap(mclk->reg); |
| return -1; |
| } |
| |
| clockevents_register_device(&meson_clockevent); |
| |
| return 0; |
| } |
| |
| #if !defined(MODULE) |
| TIMER_OF_DECLARE(meson_bc_timer, "amlogic,bc-timer", meson_bc_timer_init); |
| #else |
| static int bc_timer_probe(struct platform_device *pdev) |
| { |
| return meson_bc_timer_init(pdev->dev.of_node); |
| } |
| |
| static const struct of_device_id bc_timer_dt_match[] = { |
| { |
| .compatible = "amlogic,bc-timer", |
| }, |
| {}, |
| }; |
| |
| static struct platform_driver bc_timer_driver = { |
| .driver = { |
| .name = "meson-bc-timer", |
| .owner = THIS_MODULE, |
| .of_match_table = bc_timer_dt_match, |
| }, |
| .probe = bc_timer_probe, |
| }; |
| |
| module_platform_driver(bc_timer_driver); |
| |
| MODULE_DESCRIPTION("Meson broadcast timer Driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Amlogic, Inc."); |
| #endif |