18cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd/**
28cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd * @file op_rtc.c
38cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd * Setup and handling of RTC interrupts
48cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd *
58cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd * @remark Copyright 2002 OProfile authors
68cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd * @remark Read the file COPYING
78cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd *
88cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd * @author Bob Montgomery
98cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd * @author Philippe Elie
108cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd * @author John Levon
118cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd */
128cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
138cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd#include <linux/ioport.h>
148cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd#include <linux/mc146818rtc.h>
158cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd#include <asm/ptrace.h>
168cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
178cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd#include "oprofile.h"
188cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd#include "op_arch.h"
198cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd#include "op_util.h"
208cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
218cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd#define RTC_IO_PORTS 2
228cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
238cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd/* not in 2.2 */
248cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd#ifndef RTC_IRQ
258cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd#define RTC_IRQ 8
268cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd#endif
278cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
288cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd/* ---------------- RTC handler ------------------ */
298cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
308cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddstatic void do_rtc_interrupt(int irq, void * dev_id, struct pt_regs * regs)
318cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{
328cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	uint cpu = op_cpu_id();
338cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	unsigned char intr_flags;
348cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	unsigned long flags;
358cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
368cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	int usermode = user_mode(regs);
378cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	if ((sysctl.ctr[0].kernel && usermode)
388cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd		|| (sysctl.ctr[0].user && !usermode))
398cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd		return;
408cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
418cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	lock_rtc(flags);
428cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
438cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	/* read and ack the interrupt */
448cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	intr_flags = CMOS_READ(RTC_INTR_FLAGS);
458cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	/* Is this my type of interrupt? */
468cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	if (intr_flags & RTC_PF) {
478cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd		op_do_profile(cpu, instruction_pointer(regs), IRQ_ENABLED(regs), 0);
488cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	}
498cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
508cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	unlock_rtc(flags);
518cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
528cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	return;
538cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd}
548cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
558cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddstatic int rtc_setup(void)
568cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{
578cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	unsigned char tmp_control;
588cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	unsigned long flags;
598cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	unsigned char tmp_freq_select;
608cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	unsigned long target;
618cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	unsigned int exp, freq;
628cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
638cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	lock_rtc(flags);
648cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
658cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	/* disable periodic interrupts */
668cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	tmp_control = CMOS_READ(RTC_CONTROL);
678cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	tmp_control &= ~RTC_PIE;
688cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	CMOS_WRITE(tmp_control, RTC_CONTROL);
698cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	CMOS_READ(RTC_INTR_FLAGS);
708cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
718cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	/* Set the frequency for periodic interrupts by finding the
728cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	 * closest power of two within the allowed range.
738cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	 */
748cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
758cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	target = sysctl.ctr[0].count;
768cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
778cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	exp = 0;
788cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	while (target > (1 << exp) + ((1 << exp) >> 1))
798cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd		exp++;
808cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	freq = 16 - exp;
818cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
828cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	tmp_freq_select = CMOS_READ(RTC_FREQ_SELECT);
838cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	tmp_freq_select = (tmp_freq_select & 0xf0) | freq;
848cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	CMOS_WRITE(tmp_freq_select, RTC_FREQ_SELECT);
858cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
868cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	/* Update /proc with the actual frequency. */
878cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	sysctl_parms.ctr[0].count = sysctl.ctr[0].count = 1 << exp;
888cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
898cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	unlock_rtc(flags);
908cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	return 0;
918cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd}
928cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
938cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddstatic void rtc_start(void)
948cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{
958cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	unsigned char tmp_control;
968cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	unsigned long flags;
978cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
988cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	lock_rtc(flags);
998cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
1008cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	/* Enable periodic interrupts */
1018cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	tmp_control = CMOS_READ(RTC_CONTROL);
1028cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	tmp_control |= RTC_PIE;
1038cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	CMOS_WRITE(tmp_control, RTC_CONTROL);
1048cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
1058cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	/* read the flags register to start interrupts */
1068cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	CMOS_READ(RTC_INTR_FLAGS);
1078cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
1088cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	unlock_rtc(flags);
1098cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd}
1108cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
1118cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddstatic void rtc_stop(void)
1128cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{
1138cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	unsigned char tmp_control;
1148cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	unsigned long flags;
1158cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
1168cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	lock_rtc(flags);
1178cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
1188cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	/* disable periodic interrupts */
1198cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	tmp_control = CMOS_READ(RTC_CONTROL);
1208cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	tmp_control &= ~RTC_PIE;
1218cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	CMOS_WRITE(tmp_control, RTC_CONTROL);
1228cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	CMOS_READ(RTC_INTR_FLAGS);
1238cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
1248cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	unlock_rtc(flags);
1258cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd}
1268cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
1278cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddstatic void rtc_start_cpu(uint cpu)
1288cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{
1298cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	rtc_start();
1308cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd}
1318cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
1328cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddstatic void rtc_stop_cpu(uint cpu)
1338cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{
1348cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	rtc_stop();
1358cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd}
1368cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
1378cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddstatic int rtc_check_params(void)
1388cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{
1398cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	int target = sysctl.ctr[0].count;
1408cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
1418cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	if (check_range(target, OP_MIN_RTC_COUNT, OP_MAX_RTC_COUNT,
1428cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd		"RTC value %d is out of range (%d-%d)\n"))
1438cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd		return -EINVAL;
1448cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
1458cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	return 0;
1468cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd}
1478cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
1488cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddstatic int rtc_init(void)
1498cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{
1508cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	 /* request_region returns 0 on **failure** */
1518cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	if (!request_region_check(RTC_PORT(0), RTC_IO_PORTS, "oprofile")) {
1528cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd		printk(KERN_ERR "oprofile: can't get RTC I/O Ports\n");
1538cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd		return -EBUSY;
1548cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	}
1558cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
1568cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	/* request_irq returns 0 on **success** */
1578cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	if (request_irq(RTC_IRQ, do_rtc_interrupt,
1588cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd			SA_INTERRUPT, "oprofile", NULL)) {
1598cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd		printk(KERN_ERR "oprofile: IRQ%d busy \n", RTC_IRQ);
1608cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd		release_region(RTC_PORT(0), RTC_IO_PORTS);
1618cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd		return -EBUSY;
1628cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	}
1638cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	return 0;
1648cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd}
1658cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
1668cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddstatic void rtc_deinit(void)
1678cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{
1688cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	free_irq(RTC_IRQ, NULL);
1698cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	release_region(RTC_PORT(0), RTC_IO_PORTS);
1708cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd}
1718cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
1728cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddstatic int rtc_add_sysctls(ctl_table * next)
1738cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{
1748cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	*next = ((ctl_table) { 1, "rtc_value", &sysctl_parms.ctr[0].count, sizeof(int), 0600, NULL, lproc_dointvec, NULL, });
1758cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	return 0;
1768cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd}
1778cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
1788cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddstatic void rtc_remove_sysctls(ctl_table * next)
1798cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd{
1808cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	/* nothing to do */
1818cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd}
1828cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd
1838cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Doddstruct op_int_operations op_rtc_ops = {
1848cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	init: rtc_init,
1858cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	deinit: rtc_deinit,
1868cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	add_sysctls: rtc_add_sysctls,
1878cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	remove_sysctls: rtc_remove_sysctls,
1888cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	check_params: rtc_check_params,
1898cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	setup: rtc_setup,
1908cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	start: rtc_start,
1918cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	stop: rtc_stop,
1928cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	start_cpu: rtc_start_cpu,
1938cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd	stop_cpu: rtc_stop_cpu,
1948cfa702f803c5ef6a2b062a489a1b2cf66b45b5eMike Dodd};
195