Path: blob/master/drivers/clocksource/samsung_pwm_timer.c
26278 views
// SPDX-License-Identifier: GPL-2.0-only1/*2* Copyright (c) 2011 Samsung Electronics Co., Ltd.3* http://www.samsung.com/4*5* samsung - Common hr-timer support (s3c and s5p)6*/78#include <linux/interrupt.h>9#include <linux/irq.h>10#include <linux/err.h>11#include <linux/clk.h>12#include <linux/clockchips.h>13#include <linux/list.h>14#include <linux/module.h>15#include <linux/of.h>16#include <linux/of_address.h>17#include <linux/of_irq.h>18#include <linux/platform_device.h>19#include <linux/slab.h>20#include <linux/sched_clock.h>2122#include <clocksource/samsung_pwm.h>2324/*25* Clocksource driver26*/2728#define REG_TCFG0 0x0029#define REG_TCFG1 0x0430#define REG_TCON 0x0831#define REG_TINT_CSTAT 0x443233#define REG_TCNTB(chan) (0x0c + 12 * (chan))34#define REG_TCMPB(chan) (0x10 + 12 * (chan))3536#define TCFG0_PRESCALER_MASK 0xff37#define TCFG0_PRESCALER1_SHIFT 83839#define TCFG1_SHIFT(x) ((x) * 4)40#define TCFG1_MUX_MASK 0xf4142/*43* Each channel occupies 4 bits in TCON register, but there is a gap of 444* bits (one channel) after channel 0, so channels have different numbering45* when accessing TCON register.46*47* In addition, the location of autoreload bit for channel 4 (TCON channel 5)48* in its set of bits is 2 as opposed to 3 for other channels.49*/50#define TCON_START(chan) (1 << (4 * (chan) + 0))51#define TCON_MANUALUPDATE(chan) (1 << (4 * (chan) + 1))52#define TCON_INVERT(chan) (1 << (4 * (chan) + 2))53#define _TCON_AUTORELOAD(chan) (1 << (4 * (chan) + 3))54#define _TCON_AUTORELOAD4(chan) (1 << (4 * (chan) + 2))55#define TCON_AUTORELOAD(chan) \56((chan < 5) ? _TCON_AUTORELOAD(chan) : _TCON_AUTORELOAD4(chan))5758DEFINE_SPINLOCK(samsung_pwm_lock);59EXPORT_SYMBOL(samsung_pwm_lock);6061struct samsung_pwm_clocksource {62void __iomem *base;63const void __iomem *source_reg;64unsigned int irq[SAMSUNG_PWM_NUM];65struct samsung_pwm_variant variant;6667struct clk *timerclk;6869unsigned int event_id;70unsigned int source_id;71unsigned int tcnt_max;72unsigned int tscaler_div;73unsigned int tdiv;7475unsigned long clock_count_per_tick;76};7778static struct samsung_pwm_clocksource pwm;7980static void samsung_timer_set_prescale(unsigned int channel, u16 prescale)81{82unsigned long flags;83u8 shift = 0;84u32 reg;8586if (channel >= 2)87shift = TCFG0_PRESCALER1_SHIFT;8889spin_lock_irqsave(&samsung_pwm_lock, flags);9091reg = readl(pwm.base + REG_TCFG0);92reg &= ~(TCFG0_PRESCALER_MASK << shift);93reg |= (prescale - 1) << shift;94writel(reg, pwm.base + REG_TCFG0);9596spin_unlock_irqrestore(&samsung_pwm_lock, flags);97}9899static void samsung_timer_set_divisor(unsigned int channel, u8 divisor)100{101u8 shift = TCFG1_SHIFT(channel);102unsigned long flags;103u32 reg;104u8 bits;105106bits = (fls(divisor) - 1) - pwm.variant.div_base;107108spin_lock_irqsave(&samsung_pwm_lock, flags);109110reg = readl(pwm.base + REG_TCFG1);111reg &= ~(TCFG1_MUX_MASK << shift);112reg |= bits << shift;113writel(reg, pwm.base + REG_TCFG1);114115spin_unlock_irqrestore(&samsung_pwm_lock, flags);116}117118static void samsung_time_stop(unsigned int channel)119{120unsigned long tcon;121unsigned long flags;122123if (channel > 0)124++channel;125126spin_lock_irqsave(&samsung_pwm_lock, flags);127128tcon = readl_relaxed(pwm.base + REG_TCON);129tcon &= ~TCON_START(channel);130writel_relaxed(tcon, pwm.base + REG_TCON);131132spin_unlock_irqrestore(&samsung_pwm_lock, flags);133}134135static void samsung_time_setup(unsigned int channel, unsigned long tcnt)136{137unsigned long tcon;138unsigned long flags;139unsigned int tcon_chan = channel;140141if (tcon_chan > 0)142++tcon_chan;143144spin_lock_irqsave(&samsung_pwm_lock, flags);145146tcon = readl_relaxed(pwm.base + REG_TCON);147148tcon &= ~(TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan));149tcon |= TCON_MANUALUPDATE(tcon_chan);150151writel_relaxed(tcnt, pwm.base + REG_TCNTB(channel));152writel_relaxed(tcnt, pwm.base + REG_TCMPB(channel));153writel_relaxed(tcon, pwm.base + REG_TCON);154155spin_unlock_irqrestore(&samsung_pwm_lock, flags);156}157158static void samsung_time_start(unsigned int channel, bool periodic)159{160unsigned long tcon;161unsigned long flags;162163if (channel > 0)164++channel;165166spin_lock_irqsave(&samsung_pwm_lock, flags);167168tcon = readl_relaxed(pwm.base + REG_TCON);169170tcon &= ~TCON_MANUALUPDATE(channel);171tcon |= TCON_START(channel);172173if (periodic)174tcon |= TCON_AUTORELOAD(channel);175else176tcon &= ~TCON_AUTORELOAD(channel);177178writel_relaxed(tcon, pwm.base + REG_TCON);179180spin_unlock_irqrestore(&samsung_pwm_lock, flags);181}182183static int samsung_set_next_event(unsigned long cycles,184struct clock_event_device *evt)185{186/*187* This check is needed to account for internal rounding188* errors inside clockevents core, which might result in189* passing cycles = 0, which in turn would not generate any190* timer interrupt and hang the system.191*192* Another solution would be to set up the clockevent device193* with min_delta = 2, but this would unnecessarily increase194* the minimum sleep period.195*/196if (!cycles)197cycles = 1;198199samsung_time_setup(pwm.event_id, cycles);200samsung_time_start(pwm.event_id, false);201202return 0;203}204205static int samsung_shutdown(struct clock_event_device *evt)206{207samsung_time_stop(pwm.event_id);208return 0;209}210211static int samsung_set_periodic(struct clock_event_device *evt)212{213samsung_time_stop(pwm.event_id);214samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick - 1);215samsung_time_start(pwm.event_id, true);216return 0;217}218219static void samsung_clockevent_resume(struct clock_event_device *cev)220{221samsung_timer_set_prescale(pwm.event_id, pwm.tscaler_div);222samsung_timer_set_divisor(pwm.event_id, pwm.tdiv);223224if (pwm.variant.has_tint_cstat) {225u32 mask = (1 << pwm.event_id);226227writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT);228}229}230231static struct clock_event_device time_event_device = {232.name = "samsung_event_timer",233.features = CLOCK_EVT_FEAT_PERIODIC |234CLOCK_EVT_FEAT_ONESHOT,235.rating = 200,236.set_next_event = samsung_set_next_event,237.set_state_shutdown = samsung_shutdown,238.set_state_periodic = samsung_set_periodic,239.set_state_oneshot = samsung_shutdown,240.tick_resume = samsung_shutdown,241.resume = samsung_clockevent_resume,242};243244static irqreturn_t samsung_clock_event_isr(int irq, void *dev_id)245{246struct clock_event_device *evt = dev_id;247248if (pwm.variant.has_tint_cstat) {249u32 mask = (1 << pwm.event_id);250251writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT);252}253254evt->event_handler(evt);255256return IRQ_HANDLED;257}258259static void __init samsung_clockevent_init(void)260{261unsigned long pclk;262unsigned long clock_rate;263unsigned int irq_number;264265pclk = clk_get_rate(pwm.timerclk);266267samsung_timer_set_prescale(pwm.event_id, pwm.tscaler_div);268samsung_timer_set_divisor(pwm.event_id, pwm.tdiv);269270clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv);271pwm.clock_count_per_tick = clock_rate / HZ;272273time_event_device.cpumask = cpumask_of(0);274clockevents_config_and_register(&time_event_device,275clock_rate, 1, pwm.tcnt_max);276277irq_number = pwm.irq[pwm.event_id];278if (request_irq(irq_number, samsung_clock_event_isr,279IRQF_TIMER | IRQF_IRQPOLL, "samsung_time_irq",280&time_event_device))281pr_err("%s: request_irq() failed\n", "samsung_time_irq");282283if (pwm.variant.has_tint_cstat) {284u32 mask = (1 << pwm.event_id);285286writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT);287}288}289290static void samsung_clocksource_suspend(struct clocksource *cs)291{292samsung_time_stop(pwm.source_id);293}294295static void samsung_clocksource_resume(struct clocksource *cs)296{297samsung_timer_set_prescale(pwm.source_id, pwm.tscaler_div);298samsung_timer_set_divisor(pwm.source_id, pwm.tdiv);299300samsung_time_setup(pwm.source_id, pwm.tcnt_max);301samsung_time_start(pwm.source_id, true);302}303304static u64 notrace samsung_clocksource_read(struct clocksource *c)305{306return ~readl_relaxed(pwm.source_reg);307}308309static struct clocksource samsung_clocksource = {310.name = "samsung_clocksource_timer",311.rating = 250,312.read = samsung_clocksource_read,313.suspend = samsung_clocksource_suspend,314.resume = samsung_clocksource_resume,315.flags = CLOCK_SOURCE_IS_CONTINUOUS,316};317318/*319* Override the global weak sched_clock symbol with this320* local implementation which uses the clocksource to get some321* better resolution when scheduling the kernel. We accept that322* this wraps around for now, since it is just a relative time323* stamp. (Inspired by U300 implementation.)324*/325static u64 notrace samsung_read_sched_clock(void)326{327return samsung_clocksource_read(NULL);328}329330static int __init samsung_clocksource_init(void)331{332unsigned long pclk;333unsigned long clock_rate;334335pclk = clk_get_rate(pwm.timerclk);336337samsung_timer_set_prescale(pwm.source_id, pwm.tscaler_div);338samsung_timer_set_divisor(pwm.source_id, pwm.tdiv);339340clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv);341342samsung_time_setup(pwm.source_id, pwm.tcnt_max);343samsung_time_start(pwm.source_id, true);344345if (pwm.source_id == 4)346pwm.source_reg = pwm.base + 0x40;347else348pwm.source_reg = pwm.base + pwm.source_id * 0x0c + 0x14;349350sched_clock_register(samsung_read_sched_clock,351pwm.variant.bits, clock_rate);352353samsung_clocksource.mask = CLOCKSOURCE_MASK(pwm.variant.bits);354return clocksource_register_hz(&samsung_clocksource, clock_rate);355}356357static void __init samsung_timer_resources(void)358{359clk_prepare_enable(pwm.timerclk);360361pwm.tcnt_max = (1UL << pwm.variant.bits) - 1;362if (pwm.variant.bits == 16) {363pwm.tscaler_div = 25;364pwm.tdiv = 2;365} else {366pwm.tscaler_div = 2;367pwm.tdiv = 1;368}369}370371/*372* PWM master driver373*/374static int __init _samsung_pwm_clocksource_init(void)375{376u8 mask;377int channel;378379mask = ~pwm.variant.output_mask & ((1 << SAMSUNG_PWM_NUM) - 1);380channel = fls(mask) - 1;381if (channel < 0) {382pr_crit("failed to find PWM channel for clocksource\n");383return -EINVAL;384}385pwm.source_id = channel;386387mask &= ~(1 << channel);388channel = fls(mask) - 1;389if (channel < 0) {390pr_crit("failed to find PWM channel for clock event\n");391return -EINVAL;392}393pwm.event_id = channel;394395samsung_timer_resources();396samsung_clockevent_init();397398return samsung_clocksource_init();399}400401void __init samsung_pwm_clocksource_init(void __iomem *base,402unsigned int *irqs,403const struct samsung_pwm_variant *variant)404{405pwm.base = base;406memcpy(&pwm.variant, variant, sizeof(pwm.variant));407memcpy(pwm.irq, irqs, SAMSUNG_PWM_NUM * sizeof(*irqs));408409pwm.timerclk = clk_get(NULL, "timers");410if (IS_ERR(pwm.timerclk))411panic("failed to get timers clock for timer");412413_samsung_pwm_clocksource_init();414}415416#ifdef CONFIG_TIMER_OF417static int __init samsung_pwm_alloc(struct device_node *np,418const struct samsung_pwm_variant *variant)419{420u32 val;421int i, ret;422423memcpy(&pwm.variant, variant, sizeof(pwm.variant));424for (i = 0; i < SAMSUNG_PWM_NUM; ++i)425pwm.irq[i] = irq_of_parse_and_map(np, i);426427of_property_for_each_u32(np, "samsung,pwm-outputs", val) {428if (val >= SAMSUNG_PWM_NUM) {429pr_warn("%s: invalid channel index in samsung,pwm-outputs property\n", __func__);430continue;431}432pwm.variant.output_mask |= 1 << val;433}434435pwm.base = of_iomap(np, 0);436if (!pwm.base) {437pr_err("%s: failed to map PWM registers\n", __func__);438return -ENXIO;439}440441pwm.timerclk = of_clk_get_by_name(np, "timers");442if (IS_ERR(pwm.timerclk)) {443pr_crit("failed to get timers clock for timer\n");444ret = PTR_ERR(pwm.timerclk);445goto err_clk;446}447448ret = _samsung_pwm_clocksource_init();449if (ret)450goto err_clocksource;451452return 0;453454err_clocksource:455clk_put(pwm.timerclk);456pwm.timerclk = NULL;457err_clk:458iounmap(pwm.base);459pwm.base = NULL;460461return ret;462}463464static const struct samsung_pwm_variant s3c24xx_variant = {465.bits = 16,466.div_base = 1,467.has_tint_cstat = false,468.tclk_mask = (1 << 4),469};470471static int __init s3c2410_pwm_clocksource_init(struct device_node *np)472{473return samsung_pwm_alloc(np, &s3c24xx_variant);474}475TIMER_OF_DECLARE(s3c2410_pwm, "samsung,s3c2410-pwm", s3c2410_pwm_clocksource_init);476477static const struct samsung_pwm_variant s3c64xx_variant = {478.bits = 32,479.div_base = 0,480.has_tint_cstat = true,481.tclk_mask = (1 << 7) | (1 << 6) | (1 << 5),482};483484static int __init s3c64xx_pwm_clocksource_init(struct device_node *np)485{486return samsung_pwm_alloc(np, &s3c64xx_variant);487}488TIMER_OF_DECLARE(s3c6400_pwm, "samsung,s3c6400-pwm", s3c64xx_pwm_clocksource_init);489490static const struct samsung_pwm_variant s5p64x0_variant = {491.bits = 32,492.div_base = 0,493.has_tint_cstat = true,494.tclk_mask = 0,495};496497static int __init s5p64x0_pwm_clocksource_init(struct device_node *np)498{499return samsung_pwm_alloc(np, &s5p64x0_variant);500}501TIMER_OF_DECLARE(s5p6440_pwm, "samsung,s5p6440-pwm", s5p64x0_pwm_clocksource_init);502503static const struct samsung_pwm_variant s5p_variant = {504.bits = 32,505.div_base = 0,506.has_tint_cstat = true,507.tclk_mask = (1 << 5),508};509510static int __init s5p_pwm_clocksource_init(struct device_node *np)511{512return samsung_pwm_alloc(np, &s5p_variant);513}514TIMER_OF_DECLARE(s5pc100_pwm, "samsung,s5pc100-pwm", s5p_pwm_clocksource_init);515#endif516517518