brintos

brintos / linux-shallow public Read only

0
0
Text · 12.6 KiB · b9561e3 Raw
517 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Copyright (c) 2011 Samsung Electronics Co., Ltd.4 *		http://www.samsung.com/5 *6 * samsung - Common hr-timer support (s3c and s5p)7 */8 9#include <linux/interrupt.h>10#include <linux/irq.h>11#include <linux/err.h>12#include <linux/clk.h>13#include <linux/clockchips.h>14#include <linux/list.h>15#include <linux/module.h>16#include <linux/of.h>17#include <linux/of_address.h>18#include <linux/of_irq.h>19#include <linux/platform_device.h>20#include <linux/slab.h>21#include <linux/sched_clock.h>22 23#include <clocksource/samsung_pwm.h>24 25/*26 * Clocksource driver27 */28 29#define REG_TCFG0			0x0030#define REG_TCFG1			0x0431#define REG_TCON			0x0832#define REG_TINT_CSTAT			0x4433 34#define REG_TCNTB(chan)			(0x0c + 12 * (chan))35#define REG_TCMPB(chan)			(0x10 + 12 * (chan))36 37#define TCFG0_PRESCALER_MASK		0xff38#define TCFG0_PRESCALER1_SHIFT		839 40#define TCFG1_SHIFT(x)			((x) * 4)41#define TCFG1_MUX_MASK			0xf42 43/*44 * Each channel occupies 4 bits in TCON register, but there is a gap of 445 * bits (one channel) after channel 0, so channels have different numbering46 * when accessing TCON register.47 *48 * In addition, the location of autoreload bit for channel 4 (TCON channel 5)49 * in its set of bits is 2 as opposed to 3 for other channels.50 */51#define TCON_START(chan)		(1 << (4 * (chan) + 0))52#define TCON_MANUALUPDATE(chan)		(1 << (4 * (chan) + 1))53#define TCON_INVERT(chan)		(1 << (4 * (chan) + 2))54#define _TCON_AUTORELOAD(chan)		(1 << (4 * (chan) + 3))55#define _TCON_AUTORELOAD4(chan)		(1 << (4 * (chan) + 2))56#define TCON_AUTORELOAD(chan)		\57	((chan < 5) ? _TCON_AUTORELOAD(chan) : _TCON_AUTORELOAD4(chan))58 59DEFINE_SPINLOCK(samsung_pwm_lock);60EXPORT_SYMBOL(samsung_pwm_lock);61 62struct samsung_pwm_clocksource {63	void __iomem *base;64	const void __iomem *source_reg;65	unsigned int irq[SAMSUNG_PWM_NUM];66	struct samsung_pwm_variant variant;67 68	struct clk *timerclk;69 70	unsigned int event_id;71	unsigned int source_id;72	unsigned int tcnt_max;73	unsigned int tscaler_div;74	unsigned int tdiv;75 76	unsigned long clock_count_per_tick;77};78 79static struct samsung_pwm_clocksource pwm;80 81static void samsung_timer_set_prescale(unsigned int channel, u16 prescale)82{83	unsigned long flags;84	u8 shift = 0;85	u32 reg;86 87	if (channel >= 2)88		shift = TCFG0_PRESCALER1_SHIFT;89 90	spin_lock_irqsave(&samsung_pwm_lock, flags);91 92	reg = readl(pwm.base + REG_TCFG0);93	reg &= ~(TCFG0_PRESCALER_MASK << shift);94	reg |= (prescale - 1) << shift;95	writel(reg, pwm.base + REG_TCFG0);96 97	spin_unlock_irqrestore(&samsung_pwm_lock, flags);98}99 100static void samsung_timer_set_divisor(unsigned int channel, u8 divisor)101{102	u8 shift = TCFG1_SHIFT(channel);103	unsigned long flags;104	u32 reg;105	u8 bits;106 107	bits = (fls(divisor) - 1) - pwm.variant.div_base;108 109	spin_lock_irqsave(&samsung_pwm_lock, flags);110 111	reg = readl(pwm.base + REG_TCFG1);112	reg &= ~(TCFG1_MUX_MASK << shift);113	reg |= bits << shift;114	writel(reg, pwm.base + REG_TCFG1);115 116	spin_unlock_irqrestore(&samsung_pwm_lock, flags);117}118 119static void samsung_time_stop(unsigned int channel)120{121	unsigned long tcon;122	unsigned long flags;123 124	if (channel > 0)125		++channel;126 127	spin_lock_irqsave(&samsung_pwm_lock, flags);128 129	tcon = readl_relaxed(pwm.base + REG_TCON);130	tcon &= ~TCON_START(channel);131	writel_relaxed(tcon, pwm.base + REG_TCON);132 133	spin_unlock_irqrestore(&samsung_pwm_lock, flags);134}135 136static void samsung_time_setup(unsigned int channel, unsigned long tcnt)137{138	unsigned long tcon;139	unsigned long flags;140	unsigned int tcon_chan = channel;141 142	if (tcon_chan > 0)143		++tcon_chan;144 145	spin_lock_irqsave(&samsung_pwm_lock, flags);146 147	tcon = readl_relaxed(pwm.base + REG_TCON);148 149	tcon &= ~(TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan));150	tcon |= TCON_MANUALUPDATE(tcon_chan);151 152	writel_relaxed(tcnt, pwm.base + REG_TCNTB(channel));153	writel_relaxed(tcnt, pwm.base + REG_TCMPB(channel));154	writel_relaxed(tcon, pwm.base + REG_TCON);155 156	spin_unlock_irqrestore(&samsung_pwm_lock, flags);157}158 159static void samsung_time_start(unsigned int channel, bool periodic)160{161	unsigned long tcon;162	unsigned long flags;163 164	if (channel > 0)165		++channel;166 167	spin_lock_irqsave(&samsung_pwm_lock, flags);168 169	tcon = readl_relaxed(pwm.base + REG_TCON);170 171	tcon &= ~TCON_MANUALUPDATE(channel);172	tcon |= TCON_START(channel);173 174	if (periodic)175		tcon |= TCON_AUTORELOAD(channel);176	else177		tcon &= ~TCON_AUTORELOAD(channel);178 179	writel_relaxed(tcon, pwm.base + REG_TCON);180 181	spin_unlock_irqrestore(&samsung_pwm_lock, flags);182}183 184static int samsung_set_next_event(unsigned long cycles,185				  struct clock_event_device *evt)186{187	/*188	 * This check is needed to account for internal rounding189	 * errors inside clockevents core, which might result in190	 * passing cycles = 0, which in turn would not generate any191	 * timer interrupt and hang the system.192	 *193	 * Another solution would be to set up the clockevent device194	 * with min_delta = 2, but this would unnecessarily increase195	 * the minimum sleep period.196	 */197	if (!cycles)198		cycles = 1;199 200	samsung_time_setup(pwm.event_id, cycles);201	samsung_time_start(pwm.event_id, false);202 203	return 0;204}205 206static int samsung_shutdown(struct clock_event_device *evt)207{208	samsung_time_stop(pwm.event_id);209	return 0;210}211 212static int samsung_set_periodic(struct clock_event_device *evt)213{214	samsung_time_stop(pwm.event_id);215	samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick - 1);216	samsung_time_start(pwm.event_id, true);217	return 0;218}219 220static void samsung_clockevent_resume(struct clock_event_device *cev)221{222	samsung_timer_set_prescale(pwm.event_id, pwm.tscaler_div);223	samsung_timer_set_divisor(pwm.event_id, pwm.tdiv);224 225	if (pwm.variant.has_tint_cstat) {226		u32 mask = (1 << pwm.event_id);227 228		writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT);229	}230}231 232static struct clock_event_device time_event_device = {233	.name			= "samsung_event_timer",234	.features		= CLOCK_EVT_FEAT_PERIODIC |235				  CLOCK_EVT_FEAT_ONESHOT,236	.rating			= 200,237	.set_next_event		= samsung_set_next_event,238	.set_state_shutdown	= samsung_shutdown,239	.set_state_periodic	= samsung_set_periodic,240	.set_state_oneshot	= samsung_shutdown,241	.tick_resume		= samsung_shutdown,242	.resume			= samsung_clockevent_resume,243};244 245static irqreturn_t samsung_clock_event_isr(int irq, void *dev_id)246{247	struct clock_event_device *evt = dev_id;248 249	if (pwm.variant.has_tint_cstat) {250		u32 mask = (1 << pwm.event_id);251 252		writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT);253	}254 255	evt->event_handler(evt);256 257	return IRQ_HANDLED;258}259 260static void __init samsung_clockevent_init(void)261{262	unsigned long pclk;263	unsigned long clock_rate;264	unsigned int irq_number;265 266	pclk = clk_get_rate(pwm.timerclk);267 268	samsung_timer_set_prescale(pwm.event_id, pwm.tscaler_div);269	samsung_timer_set_divisor(pwm.event_id, pwm.tdiv);270 271	clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv);272	pwm.clock_count_per_tick = clock_rate / HZ;273 274	time_event_device.cpumask = cpumask_of(0);275	clockevents_config_and_register(&time_event_device,276					clock_rate, 1, pwm.tcnt_max);277 278	irq_number = pwm.irq[pwm.event_id];279	if (request_irq(irq_number, samsung_clock_event_isr,280			IRQF_TIMER | IRQF_IRQPOLL, "samsung_time_irq",281			&time_event_device))282		pr_err("%s: request_irq() failed\n", "samsung_time_irq");283 284	if (pwm.variant.has_tint_cstat) {285		u32 mask = (1 << pwm.event_id);286 287		writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT);288	}289}290 291static void samsung_clocksource_suspend(struct clocksource *cs)292{293	samsung_time_stop(pwm.source_id);294}295 296static void samsung_clocksource_resume(struct clocksource *cs)297{298	samsung_timer_set_prescale(pwm.source_id, pwm.tscaler_div);299	samsung_timer_set_divisor(pwm.source_id, pwm.tdiv);300 301	samsung_time_setup(pwm.source_id, pwm.tcnt_max);302	samsung_time_start(pwm.source_id, true);303}304 305static u64 notrace samsung_clocksource_read(struct clocksource *c)306{307	return ~readl_relaxed(pwm.source_reg);308}309 310static struct clocksource samsung_clocksource = {311	.name		= "samsung_clocksource_timer",312	.rating		= 250,313	.read		= samsung_clocksource_read,314	.suspend	= samsung_clocksource_suspend,315	.resume		= samsung_clocksource_resume,316	.flags		= CLOCK_SOURCE_IS_CONTINUOUS,317};318 319/*320 * Override the global weak sched_clock symbol with this321 * local implementation which uses the clocksource to get some322 * better resolution when scheduling the kernel. We accept that323 * this wraps around for now, since it is just a relative time324 * stamp. (Inspired by U300 implementation.)325 */326static u64 notrace samsung_read_sched_clock(void)327{328	return samsung_clocksource_read(NULL);329}330 331static int __init samsung_clocksource_init(void)332{333	unsigned long pclk;334	unsigned long clock_rate;335 336	pclk = clk_get_rate(pwm.timerclk);337 338	samsung_timer_set_prescale(pwm.source_id, pwm.tscaler_div);339	samsung_timer_set_divisor(pwm.source_id, pwm.tdiv);340 341	clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv);342 343	samsung_time_setup(pwm.source_id, pwm.tcnt_max);344	samsung_time_start(pwm.source_id, true);345 346	if (pwm.source_id == 4)347		pwm.source_reg = pwm.base + 0x40;348	else349		pwm.source_reg = pwm.base + pwm.source_id * 0x0c + 0x14;350 351	sched_clock_register(samsung_read_sched_clock,352			     pwm.variant.bits, clock_rate);353 354	samsung_clocksource.mask = CLOCKSOURCE_MASK(pwm.variant.bits);355	return clocksource_register_hz(&samsung_clocksource, clock_rate);356}357 358static void __init samsung_timer_resources(void)359{360	clk_prepare_enable(pwm.timerclk);361 362	pwm.tcnt_max = (1UL << pwm.variant.bits) - 1;363	if (pwm.variant.bits == 16) {364		pwm.tscaler_div = 25;365		pwm.tdiv = 2;366	} else {367		pwm.tscaler_div = 2;368		pwm.tdiv = 1;369	}370}371 372/*373 * PWM master driver374 */375static int __init _samsung_pwm_clocksource_init(void)376{377	u8 mask;378	int channel;379 380	mask = ~pwm.variant.output_mask & ((1 << SAMSUNG_PWM_NUM) - 1);381	channel = fls(mask) - 1;382	if (channel < 0) {383		pr_crit("failed to find PWM channel for clocksource\n");384		return -EINVAL;385	}386	pwm.source_id = channel;387 388	mask &= ~(1 << channel);389	channel = fls(mask) - 1;390	if (channel < 0) {391		pr_crit("failed to find PWM channel for clock event\n");392		return -EINVAL;393	}394	pwm.event_id = channel;395 396	samsung_timer_resources();397	samsung_clockevent_init();398 399	return samsung_clocksource_init();400}401 402void __init samsung_pwm_clocksource_init(void __iomem *base,403					 unsigned int *irqs,404					 const struct samsung_pwm_variant *variant)405{406	pwm.base = base;407	memcpy(&pwm.variant, variant, sizeof(pwm.variant));408	memcpy(pwm.irq, irqs, SAMSUNG_PWM_NUM * sizeof(*irqs));409 410	pwm.timerclk = clk_get(NULL, "timers");411	if (IS_ERR(pwm.timerclk))412		panic("failed to get timers clock for timer");413 414	_samsung_pwm_clocksource_init();415}416 417#ifdef CONFIG_TIMER_OF418static int __init samsung_pwm_alloc(struct device_node *np,419				    const struct samsung_pwm_variant *variant)420{421	u32 val;422	int i, ret;423 424	memcpy(&pwm.variant, variant, sizeof(pwm.variant));425	for (i = 0; i < SAMSUNG_PWM_NUM; ++i)426		pwm.irq[i] = irq_of_parse_and_map(np, i);427 428	of_property_for_each_u32(np, "samsung,pwm-outputs", val) {429		if (val >= SAMSUNG_PWM_NUM) {430			pr_warn("%s: invalid channel index in samsung,pwm-outputs property\n", __func__);431			continue;432		}433		pwm.variant.output_mask |= 1 << val;434	}435 436	pwm.base = of_iomap(np, 0);437	if (!pwm.base) {438		pr_err("%s: failed to map PWM registers\n", __func__);439		return -ENXIO;440	}441 442	pwm.timerclk = of_clk_get_by_name(np, "timers");443	if (IS_ERR(pwm.timerclk)) {444		pr_crit("failed to get timers clock for timer\n");445		ret = PTR_ERR(pwm.timerclk);446		goto err_clk;447	}448 449	ret = _samsung_pwm_clocksource_init();450	if (ret)451		goto err_clocksource;452 453	return 0;454 455err_clocksource:456	clk_put(pwm.timerclk);457	pwm.timerclk = NULL;458err_clk:459	iounmap(pwm.base);460	pwm.base = NULL;461 462	return ret;463}464 465static const struct samsung_pwm_variant s3c24xx_variant = {466	.bits		= 16,467	.div_base	= 1,468	.has_tint_cstat	= false,469	.tclk_mask	= (1 << 4),470};471 472static int __init s3c2410_pwm_clocksource_init(struct device_node *np)473{474	return samsung_pwm_alloc(np, &s3c24xx_variant);475}476TIMER_OF_DECLARE(s3c2410_pwm, "samsung,s3c2410-pwm", s3c2410_pwm_clocksource_init);477 478static const struct samsung_pwm_variant s3c64xx_variant = {479	.bits		= 32,480	.div_base	= 0,481	.has_tint_cstat	= true,482	.tclk_mask	= (1 << 7) | (1 << 6) | (1 << 5),483};484 485static int __init s3c64xx_pwm_clocksource_init(struct device_node *np)486{487	return samsung_pwm_alloc(np, &s3c64xx_variant);488}489TIMER_OF_DECLARE(s3c6400_pwm, "samsung,s3c6400-pwm", s3c64xx_pwm_clocksource_init);490 491static const struct samsung_pwm_variant s5p64x0_variant = {492	.bits		= 32,493	.div_base	= 0,494	.has_tint_cstat	= true,495	.tclk_mask	= 0,496};497 498static int __init s5p64x0_pwm_clocksource_init(struct device_node *np)499{500	return samsung_pwm_alloc(np, &s5p64x0_variant);501}502TIMER_OF_DECLARE(s5p6440_pwm, "samsung,s5p6440-pwm", s5p64x0_pwm_clocksource_init);503 504static const struct samsung_pwm_variant s5p_variant = {505	.bits		= 32,506	.div_base	= 0,507	.has_tint_cstat	= true,508	.tclk_mask	= (1 << 5),509};510 511static int __init s5p_pwm_clocksource_init(struct device_node *np)512{513	return samsung_pwm_alloc(np, &s5p_variant);514}515TIMER_OF_DECLARE(s5pc100_pwm, "samsung,s5pc100-pwm", s5p_pwm_clocksource_init);516#endif517