linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade
@ 2005-10-06 15:03 Mark Gross
  2005-10-06 16:52 ` Jesper Juhl
                   ` (2 more replies)
  0 siblings, 3 replies; 18+ messages in thread
From: Mark Gross @ 2005-10-06 15:03 UTC (permalink / raw)
  To: akpm; +Cc: linux-kernel, Sebastien.Bouchard, mark.gross

[-- Attachment #1: Type: text/plain, Size: 1055 bytes --]


Andrew,

Attached is a simple charactor driver for possible inclusion in your MM tree.

This driver is specific to the MPCBL0010 that will start shipping this fall.

The telcom clock is a special circuit, line card PLL, that provids a mechanism 
for synchronization of specialized hardware across the backplane of a chassis 
of multiple computers with similar specail curcits.  In this case the 
synchronization signals get routed to multiple places, typically to pins on 
expansion slots for hardware that knows what to do with this signal.  (SONET, 
G.813, stratum 3...) and similar signaling applications found in telcom sites 
can use this type of thing.

The actual device is hidden behind the FPGA on the motherboar, and is 
connected to the FPGA via I2C.  This driver only talks to the FPGA registers.

Thanks,

-- 
--mgross
BTW: This may or may not be the opinion of my employer, more likely not.  


-------------------------------------------------------

-- 
--mgross
BTW: This may or may not be the opinion of my employer, more likely not.  

[-- Attachment #2: tlclk-2.6.14-rc2-mm2.patch --]
[-- Type: text/x-diff, Size: 31639 bytes --]

diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/Kconfig linux-2.6.14-rc2-mm2-tlclk/drivers/char/Kconfig
--- linux-2.6.14-rc2-mm2/drivers/char/Kconfig	2005-10-04 14:55:15.000000000 -0700
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/Kconfig	2005-10-04 14:57:47.000000000 -0700
@@ -1001,5 +1001,15 @@
 
 source "drivers/char/tpm/Kconfig"
 
+config TELCLOCK
+	tristate "Telecom clock driver for ATCA"
+	depends on EXPERIMENTAL
+	default n
+	help
+	  The telecom clock device allows direct userspace access to the
+	  configuration of the telecom clock configuration settings.
+	  This device is used for hardware synchronization across the ATCA
+	  backplane fabric.
+
 endmenu
 
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/Makefile linux-2.6.14-rc2-mm2-tlclk/drivers/char/Makefile
--- linux-2.6.14-rc2-mm2/drivers/char/Makefile	2005-10-04 14:55:15.000000000 -0700
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/Makefile	2005-10-04 14:57:47.000000000 -0700
@@ -82,6 +82,7 @@
 obj-$(CONFIG_SCx200_GPIO) += scx200_gpio.o
 obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o
 obj-$(CONFIG_TANBAC_TB0219) += tb0219.o
+obj-$(CONFIG_TELCLOCK) += tlclk.o
 
 obj-$(CONFIG_SPEAKUP) += speakup/
 obj-$(CONFIG_WATCHDOG)	+= watchdog/
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/tlclk.c linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.c
--- linux-2.6.14-rc2-mm2/drivers/char/tlclk.c	1969-12-31 16:00:00.000000000 -0800
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.c	2005-10-04 14:57:47.000000000 -0700
@@ -0,0 +1,944 @@
+/*
+ * Telecom Clock driver for Intel NetStructure(tm) MPCBL0010
+ *
+ * Copyright (C) 2005 Kontron Canada
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT.  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Send feedback to <sebastien.bouchard@ca.kontron.com>
+ * 
+ * 2.6 driver version being maintained by <mark.gross@intel.com>
+ *
+ * Description : This is the TELECOM CLOCK module driver for the ATCA platform.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>	/* printk() */
+#include <linux/fs.h>		/* everything... */
+#include <linux/errno.h>	/* error codes */
+#include <linux/delay.h>	/* udelay */
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/sysfs.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+#include <asm/io.h>		/* inb/outb */
+#include <asm/uaccess.h>
+
+#include "tlclk.h"		/* TELECOM IOCTL DEFINE */
+
+MODULE_AUTHOR("Sebastien Bouchard <sebastien.bouchard@ca.kontron.com>");
+MODULE_LICENSE("GPL");
+
+/* Telecom clock I/O register definition */
+#define TLCLK_BASE 0xa08
+#define TLCLK_REG0 TLCLK_BASE
+#define TLCLK_REG1 (TLCLK_BASE+1)
+#define TLCLK_REG2 (TLCLK_BASE+2)
+#define TLCLK_REG3 (TLCLK_BASE+3)
+#define TLCLK_REG4 (TLCLK_BASE+4)
+#define TLCLK_REG5 (TLCLK_BASE+5)
+#define TLCLK_REG6 (TLCLK_BASE+6)
+#define TLCLK_REG7 (TLCLK_BASE+7)
+
+#define SET_PORT_BITS(port, mask, val) outb(((inb(port) & mask) | val), port)
+
+/* 0 = Dynamic allocation of the major device number */
+#define TLCLK_MAJOR 252
+
+/*
+uncomment to include classic IOCTL's 
+#define TLCLK_IOCTL
+*/
+
+/* sysFS interface definition:
+Uppon loading the driver will create a sysfs directory under class/misc/tlclk.
+
+This directory exports the following interfaces.  There opperation is documented 
+in the MCPBL0010 TPS under the Telecom Clock API section, 11.4.
+alarms				:
+current_ref			:
+enable_clk3a_output		:
+enable_clk3b_output		:
+enable_clka0_output		:
+enable_clka1_output		:
+enable_clkb0_output		:
+enable_clkb1_output		:
+filter_select			:
+hardware_switching		:
+hardware_switching_mode		:
+interrupt_switch		:
+mode_select			:
+refalign			:
+reset				: 
+select_amcb1_transmit_clock	:
+select_amcb2_transmit_clock	:
+select_redundant_clock		:
+select_ref_frequency		:
+test_mode			:
+
+All sysfs interaces are integers in hex format, i.e echo 99 > refalign
+has the same effect as echo 0x99 > refalign.
+
+*/
+
+#if CONFIG_DEBUG_KERNEL 
+#define debug_printk( args... ) printk( args)
+#else
+#define debug_printk( args... )
+#endif
+
+
+static unsigned int telclk_interrupt;
+
+static int int_events;		/* Event that generate a interrupt */
+static int got_event;		/* if events processing have been done */
+
+static void switchover_timeout(unsigned long data);
+static struct timer_list switchover_timer = TIMER_INITIALIZER(switchover_timeout , 0, 0);
+
+static struct tlclk_alarms *alarm_events;
+
+DEFINE_SPINLOCK(event_lock);
+
+static int tlclk_major = TLCLK_MAJOR;
+
+irqreturn_t tlclk_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+
+DECLARE_WAIT_QUEUE_HEAD(wq);
+
+#ifdef TLCLK_IOCTL
+static int
+tlclk_ioctl(struct inode *inode,
+	    struct file *filp, unsigned int cmd, unsigned long arg)
+{
+	unsigned long flags;
+	unsigned char val;
+	int ret_val = 0;
+
+	val = (unsigned char)arg;
+	if (_IOC_TYPE(cmd) != TLCLK_IOC_MAGIC)
+		return -ENOTTY;
+
+	if (_IOC_NR(cmd) > TLCLK_IOC_MAXNR)
+		return -ENOTTY;
+
+	spin_lock_irqsave(&event_lock, flags);
+	switch (cmd) {
+	case IOCTL_RESET:
+		SET_PORT_BITS(TLCLK_REG4, 0xfd, val);
+		break;
+	case IOCTL_MODE_SELECT:
+		SET_PORT_BITS(TLCLK_REG0, 0xcf, val);
+		break;
+	case IOCTL_REFALIGN:
+		/* GENERATING 0 to 1 transistion */
+		SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
+		udelay(2);
+		SET_PORT_BITS(TLCLK_REG0, 0xf7, 0x08);
+		udelay(2);
+		SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
+		break;
+	case IOCTL_HARDWARE_SWITCHING:
+		SET_PORT_BITS(TLCLK_REG0, 0x7f, val);
+		break;
+	case IOCTL_HARDWARE_SWITCHING_MODE:
+		SET_PORT_BITS(TLCLK_REG0, 0xbf, val);
+		break;
+	case IOCTL_FILTER_SELECT:
+		SET_PORT_BITS(TLCLK_REG0, 0xfb, val);
+		break;
+	case IOCTL_SELECT_REF_FREQUENCY:
+		SET_PORT_BITS(TLCLK_REG1, 0xfd, val);
+		break;
+	case IOCTL_SELECT_REDUNDANT_CLOCK:
+		SET_PORT_BITS(TLCLK_REG1, 0xfe, val);
+		break;
+	case IOCTL_SELECT_AMCB1_TRANSMIT_CLOCK:
+		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x5);
+			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
+		} else if (val >= CLK_8_592MHz) {
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x7);
+			switch (val) {
+			case CLK_8_592MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
+				break;
+			case CLK_11_184MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
+				break;
+			case CLK_34_368MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
+				break;
+			case CLK_44_736MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
+				break;
+			}
+		} else
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, val);
+		break;
+	case IOCTL_SELECT_AMCB2_TRANSMIT_CLOCK:
+		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x28);
+			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
+		} else if (val >= CLK_8_592MHz) {
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x38);
+			switch (val) {
+			case CLK_8_592MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
+				break;
+			case CLK_11_184MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
+				break;
+			case CLK_34_368MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
+				break;
+			case CLK_44_736MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
+				break;
+			}
+		} else
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, val << 3);
+		break;
+	case IOCTL_TEST_MODE:
+		SET_PORT_BITS(TLCLK_REG4, 0xfd, 2);
+		break;
+	case IOCTL_ENABLE_CLKA0_OUTPUT:
+		SET_PORT_BITS(TLCLK_REG2, 0xfe, val);
+		break;
+	case IOCTL_ENABLE_CLKB0_OUTPUT:
+		SET_PORT_BITS(TLCLK_REG2, 0xfd, val << 1);
+		break;
+	case IOCTL_ENABLE_CLKA1_OUTPUT:
+		SET_PORT_BITS(TLCLK_REG2, 0xfb, val << 2);
+		break;
+	case IOCTL_ENABLE_CLKB1_OUTPUT:
+		SET_PORT_BITS(TLCLK_REG2, 0xf7, val << 3);
+		break;
+	case IOCTL_ENABLE_CLK3A_OUTPUT:
+		SET_PORT_BITS(TLCLK_REG3, 0xbf, val << 6);
+		break;
+	case IOCTL_ENABLE_CLK3B_OUTPUT:
+		SET_PORT_BITS(TLCLK_REG3, 0x7f, val << 7);
+		break;
+	case IOCTL_READ_ALARMS:
+		ret_val = (inb(TLCLK_REG2) & 0xf0);
+		break;
+	case IOCTL_READ_INTERRUPT_SWITCH:
+		ret_val = inb(TLCLK_REG6);
+		break;
+	case IOCTL_READ_CURRENT_REF:
+		ret_val = ((inb(TLCLK_REG1) & 0x08) >> 3);
+		break;
+	}
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return ret_val;
+}
+#endif
+
+
+static int tlclk_open(struct inode *inode, struct file *filp)
+{
+	int result;
+
+	/* Make sure there is no interrupt pending while 
+	 * initialising interrupt handler */
+	inb(TLCLK_REG6);
+
+	/* This device is wired through the FPGA IO space of the ATCA blade 
+	 * we can't share this IRQ */
+	result = request_irq(telclk_interrupt, &tlclk_interrupt,
+			     SA_INTERRUPT, "telclock", tlclk_interrupt);
+	if (result == -EBUSY) {
+		printk(KERN_ERR "telclock: Interrupt can't be reserved!\n");
+		return -EBUSY;
+	}
+	inb(TLCLK_REG6);	/* Clear interrupt events */
+
+	return 0;
+}
+
+static int tlclk_release(struct inode *inode, struct file *filp)
+{
+	free_irq(telclk_interrupt, tlclk_interrupt);
+
+	return 0;
+}
+
+ssize_t
+tlclk_read(struct file * filp, char __user * buf, size_t count, loff_t * f_pos)
+{
+	int count0 = sizeof(struct tlclk_alarms);
+
+	wait_event_interruptible(wq, got_event);
+	if (copy_to_user(buf, alarm_events, sizeof(struct tlclk_alarms)))
+		return -EFAULT;
+
+	memset(alarm_events, 0, sizeof(struct tlclk_alarms));
+	got_event = 0;
+
+	return count0;
+}
+
+ssize_t
+tlclk_write(struct file * filp, const char __user * buf, size_t count,
+	    loff_t * f_pos)
+{
+	return 0;
+}
+
+static struct file_operations tlclk_fops = {
+	.read = tlclk_read,
+	.write = tlclk_write,
+#ifdef TLCLK_IOCTL
+	.ioctl = tlclk_ioctl,
+#endif
+	.open = tlclk_open,
+	.release = tlclk_release,
+
+};
+
+#ifdef CONFIG_SYSFS
+static struct miscdevice tlclk_miscdev = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "tlclk",
+	.fops = &tlclk_fops,
+};
+
+static ssize_t show_current_ref(struct class_device *d, char * buf)
+{
+	unsigned long ret_val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+		ret_val = ((inb(TLCLK_REG1) & 0x08) >> 3);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return sprintf(buf, "0x%lX\n", ret_val);
+}
+
+static CLASS_DEVICE_ATTR(current_ref, S_IRUGO, show_current_ref, NULL);
+
+
+static ssize_t show_interrupt_switch(struct class_device *d, char * buf)
+{
+	unsigned long ret_val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+		ret_val = inb(TLCLK_REG6);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return sprintf(buf, "0x%lX\n", ret_val);
+}
+
+static CLASS_DEVICE_ATTR(interrupt_switch, S_IRUGO, 
+		show_interrupt_switch, NULL);
+
+
+static ssize_t show_alarms(struct class_device *d, char * buf)
+{
+	unsigned long ret_val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+		ret_val = (inb(TLCLK_REG2) & 0xf0);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return sprintf(buf, "0x%lX\n", ret_val);
+}
+
+static CLASS_DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
+
+
+static ssize_t store_enable_clk3b_output(struct class_device *d, 
+		const char * buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX \n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		SET_PORT_BITS(TLCLK_REG3, 0x7f, val << 7);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(enable_clk3b_output, S_IWUGO, NULL, 
+		store_enable_clk3b_output);
+
+
+
+static ssize_t store_enable_clk3a_output(struct class_device *d, 
+		const char * buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX \n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		SET_PORT_BITS(TLCLK_REG3, 0xbf, val << 6);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(enable_clk3a_output, S_IWUGO, NULL, 
+		store_enable_clk3a_output);
+
+
+
+static ssize_t store_enable_clkb1_output(struct class_device *d, 
+		const char * buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX \n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		SET_PORT_BITS(TLCLK_REG2, 0xf7, val << 3);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(enable_clkb1_output, S_IWUGO, NULL, 
+		store_enable_clkb1_output);
+
+
+static ssize_t store_enable_clka1_output(struct class_device *d, 
+		const char * buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX \n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		SET_PORT_BITS(TLCLK_REG2, 0xfb, val << 2);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(enable_clka1_output, S_IWUGO, NULL, 
+		store_enable_clka1_output);
+
+
+static ssize_t store_enable_clkb0_output(struct class_device *d, 
+		const char * buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX \n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		SET_PORT_BITS(TLCLK_REG2, 0xfd, val << 1);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(enable_clkb0_output, S_IWUGO, NULL, 
+		store_enable_clkb0_output);
+
+
+static ssize_t store_enable_clka0_output(struct class_device *d, 
+		const char * buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX \n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG2, 0xfe, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(enable_clka0_output, S_IWUGO, NULL, 
+		store_enable_clka0_output);
+
+
+static ssize_t store_test_mode(struct class_device *d, const char * buf, 
+	size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX \n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG4, 0xfd, 2);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(test_mode, S_IWUGO, NULL, store_test_mode);
+
+static ssize_t store_select_amcb2_transmit_clock(struct class_device *d, 
+	const char * buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX \n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x28);
+			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
+		} else if (val >= CLK_8_592MHz) {
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x38);
+			switch (val) {
+			case CLK_8_592MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
+				break;
+			case CLK_11_184MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
+				break;
+			case CLK_34_368MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
+				break;
+			case CLK_44_736MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
+				break;
+			}
+		} else
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, val << 3);
+		
+	
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(select_amcb2_transmit_clock, S_IWUGO, NULL, 
+	store_select_amcb2_transmit_clock);
+
+static ssize_t store_select_amcb1_transmit_clock(struct class_device *d, 
+		const char * buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX \n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x5);
+			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
+		} else if (val >= CLK_8_592MHz) {
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x7);
+			switch (val) {
+			case CLK_8_592MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
+				break;
+			case CLK_11_184MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
+				break;
+			case CLK_34_368MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
+				break;
+			case CLK_44_736MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
+				break;
+			}
+		} else
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(select_amcb1_transmit_clock, S_IWUGO, NULL, 
+		store_select_amcb1_transmit_clock);
+
+static ssize_t store_select_redundant_clock(struct class_device *d, 
+		const char * buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX \n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG1, 0xfe, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(select_redundant_clock, S_IWUGO, NULL, 
+		store_select_redundant_clock);
+
+static ssize_t store_select_ref_frequency(struct class_device *d, 
+		const char * buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX \n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG1, 0xfd, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(select_ref_frequency, S_IWUGO, NULL, 
+		store_select_ref_frequency);
+
+static ssize_t store_filter_select(struct class_device *d, 
+		const char * buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX \n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xfb, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(filter_select, S_IWUGO, NULL, store_filter_select);
+
+static ssize_t store_hardware_switching_mode(struct class_device *d, 
+		const char * buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX \n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xbf, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(hardware_switching_mode, S_IWUGO, NULL, 
+		store_hardware_switching_mode);
+
+
+static ssize_t store_hardware_switching(struct class_device *d, 
+		const char * buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX \n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0x7f, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(hardware_switching, S_IWUGO, NULL, 
+		store_hardware_switching);
+
+static ssize_t store_refalign (struct class_device *d, 
+		const char * buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX \n", tmp);
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
+	udelay(2);
+	SET_PORT_BITS(TLCLK_REG0, 0xf7, 0x08);
+	udelay(2);
+	SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(refalign, S_IWUGO, NULL, store_refalign);
+
+static ssize_t store_mode_select (struct class_device *d, 
+		const char * buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX \n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xcf, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(mode_select, S_IWUGO, NULL, store_mode_select);
+
+static ssize_t store_reset (struct class_device *d, 
+		const char * buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX \n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG4, 0xfd, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(reset, S_IWUGO, NULL, store_reset);
+#endif /*  CONFIG_SYSFS */
+
+static int __init tlclk_init(void)
+{
+	int ret;
+#ifdef  CONFIG_SYSFS
+	struct class_device *class;
+#endif
+	ret = register_chrdev(tlclk_major, "telclock", &tlclk_fops);
+
+	if (ret < 0) {
+		printk(KERN_ERR "telclock: can't get major! %d\n", tlclk_major);
+		return ret;
+	}
+
+	alarm_events = kcalloc(1, sizeof(struct tlclk_alarms), GFP_KERNEL);
+
+	if (!alarm_events)
+		goto out1;
+
+/* Read telecom clock IRQ number (Set by BIOS) */
+
+	if (!request_region(TLCLK_BASE, 8, "telclock")) {
+		printk(KERN_ERR "tlclk: request_region failed! 0x%X\n",
+			TLCLK_BASE);
+		ret =  -EBUSY;
+		goto out2;
+	}
+	telclk_interrupt = (inb(TLCLK_REG7) & 0x0f);
+	
+	if (0x0F == telclk_interrupt ) { /* not MCPBL0010 ? */
+		printk(KERN_ERR "telclk_interrup = 0x%x non-mcpbl0010 hw \n", telclk_interrupt);
+		ret = -ENXIO;
+		goto out3;
+	}
+
+	init_timer(&switchover_timer);
+/*	switchover_timer.function = switchover_timeout; */
+/*	switchover_timer.data = 0; */
+	
+#ifdef  CONFIG_SYSFS
+	if( 0 > (ret = misc_register(&tlclk_miscdev )) ) {
+		printk(KERN_ERR" misc_register retruns %d \n", ret);
+		ret =  -EBUSY;
+		goto out3;
+	}
+	class = tlclk_miscdev.class;
+	class_device_create_file(class, &class_device_attr_current_ref);
+	class_device_create_file(class, &class_device_attr_interrupt_switch);
+	class_device_create_file(class, &class_device_attr_alarms);
+	class_device_create_file(class, &class_device_attr_enable_clk3b_output);
+	class_device_create_file(class, &class_device_attr_enable_clk3a_output);
+	class_device_create_file(class, &class_device_attr_enable_clkb1_output);
+	class_device_create_file(class, &class_device_attr_enable_clka1_output);
+	class_device_create_file(class, &class_device_attr_enable_clkb0_output);
+	class_device_create_file(class, &class_device_attr_enable_clka0_output);
+	class_device_create_file(class, &class_device_attr_test_mode);
+	class_device_create_file(class, &class_device_attr_select_amcb2_transmit_clock);
+	class_device_create_file(class, &class_device_attr_select_amcb1_transmit_clock);
+	class_device_create_file(class, &class_device_attr_select_redundant_clock);
+	class_device_create_file(class, &class_device_attr_select_ref_frequency);
+	class_device_create_file(class, &class_device_attr_filter_select);
+	class_device_create_file(class, &class_device_attr_hardware_switching_mode);
+	class_device_create_file(class, &class_device_attr_hardware_switching);
+	class_device_create_file(class, &class_device_attr_refalign);
+	class_device_create_file(class, &class_device_attr_mode_select);
+	class_device_create_file(class, &class_device_attr_reset);
+
+#endif /*  CONFIG_SYSFS */
+	return 0;
+out3:
+	release_region(TLCLK_BASE, 8);
+out2:
+	kfree(alarm_events);
+out1:
+	return ret;
+}
+
+static void __exit tlclk_cleanup(void)
+{
+#ifdef  CONFIG_SYSFS
+	misc_deregister(&tlclk_miscdev);
+#endif
+	unregister_chrdev(tlclk_major, "telclock");
+
+	release_region(TLCLK_BASE, 8);
+	del_timer_sync(&switchover_timer);
+	kfree(alarm_events);
+
+}
+static void switchover_timeout(unsigned long data)
+{
+	if ((data & 1)) {
+		if ((inb(TLCLK_REG1) & 0x08) != (data & 0x08))
+			alarm_events->switchover_primary++;
+	} else {
+		if ((inb(TLCLK_REG1) & 0x08) != (data & 0x08))
+			alarm_events->switchover_secondary++;
+	}
+
+	/* Alarm processing is done, wake up read task */
+	del_timer(&switchover_timer);
+	got_event = 1;
+	wake_up(&wq);
+}
+
+irqreturn_t tlclk_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+	/* Read and clear interrupt events */
+	int_events = inb(TLCLK_REG6);
+
+	/* Primary_Los changed from 0 to 1 ? */
+	if (int_events & PRI_LOS_01_MASK) {
+		if (inb(TLCLK_REG2) & SEC_LOST_MASK)
+			alarm_events->lost_clocks++;
+		else
+			alarm_events->lost_primary_clock++;
+	}
+
+	/* Primary_Los changed from 1 to 0 ? */
+	if (int_events & PRI_LOS_10_MASK) {
+		alarm_events->primary_clock_back++;
+		SET_PORT_BITS(TLCLK_REG1, 0xFE, 1);
+	}
+	/* Secondary_Los changed from 0 to 1 ? */
+	if (int_events & SEC_LOS_01_MASK) {
+		if (inb(TLCLK_REG2) & PRI_LOST_MASK)
+			alarm_events->lost_clocks++;
+		else
+			alarm_events->lost_secondary_clock++;
+	}
+	/* Secondary_Los changed from 1 to 0 ? */
+	if (int_events & SEC_LOS_10_MASK) {
+		alarm_events->secondary_clock_back++;
+		SET_PORT_BITS(TLCLK_REG1, 0xFE, 0);
+	}
+	if (int_events & HOLDOVER_10_MASK)
+		alarm_events->pll_end_holdover++;
+
+	if (int_events & UNLOCK_01_MASK)
+		alarm_events->pll_lost_sync++;
+
+	if (int_events & UNLOCK_10_MASK)
+		alarm_events->pll_sync++;
+
+	/* Holdover changed from 0 to 1 ? */
+	if (int_events & HOLDOVER_01_MASK) {
+		alarm_events->pll_holdover++;
+
+		switchover_timer.expires = jiffies + msecs_to_jiffies(10);	/* TIMEOUT in ~10ms */
+		switchover_timer.data = inb(TLCLK_REG1);
+		add_timer(&switchover_timer);
+	} else {
+		got_event = 1;
+		wake_up(&wq);
+	}
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+module_init(tlclk_init);
+module_exit(tlclk_cleanup);
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/tlclk.h linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.h
--- linux-2.6.14-rc2-mm2/drivers/char/tlclk.h	1969-12-31 16:00:00.000000000 -0800
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.h	2005-10-04 14:57:47.000000000 -0700
@@ -0,0 +1,166 @@
+/*
+ * Telecom Clock driver for Wainwright board
+ *
+ * Copyright (C) 2005 Kontron Canada
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT.  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Send feedback to <sebastien.bouchard@ca.kontron.com>
+ *
+ */
+
+/* Ioctl definitions  */
+
+/* Use 0xA1 as magic number */
+#define TLCLK_IOC_MAGIC 0xA1
+
+/*Hardware Reset of the PLL */
+
+#define RESET_ON	0x00
+#define RESET_OFF	0x01
+#define IOCTL_RESET			_IO(TLCLK_IOC_MAGIC,  1)
+
+#define IOCTL_REFALIGN			_IO(TLCLK_IOC_MAGIC,  2)
+
+/* MODE SELECT */
+
+#define NORMAL_MODE 	0x00
+#define HOLDOVER_MODE	0x10
+#define FREERUN_MODE	0x20
+
+#define IOCTL_MODE_SELECT		_IOR(TLCLK_IOC_MAGIC,  3, char)
+
+/* FILTER SELECT */
+
+#define FILTER_6HZ	0x04
+#define FILTER_12HZ	0x00
+
+#define IOCTL_FILTER_SELECT		_IOR(TLCLK_IOC_MAGIC,  4, char)
+
+/* SELECT REFERENCE FREQUENCY */
+
+#define REF_CLK1_8kHz		0x00
+#define REF_CLK2_19_44MHz	0x02
+
+#define IOCTL_SELECT_REF_FREQUENCY	_IOR(TLCLK_IOC_MAGIC,  6, char)
+
+/* Select primary or secondary redundant clock */
+
+#define PRIMARY_CLOCK	0x00
+#define SECONDARY_CLOCK	0x01
+#define IOCTL_SELECT_REDUNDANT_CLOCK	_IOR(TLCLK_IOC_MAGIC,  7, char)
+
+/* CLOCK TRANSMISSION DEFINE */
+
+#define CLK_8kHz	0xff
+#define CLK_16_384MHz	0xfb
+
+#define CLK_1_544MHz	0x00
+#define CLK_2_048MHz	0x01
+#define CLK_4_096MHz	0x02
+#define CLK_6_312MHz	0x03
+#define CLK_8_192MHz	0x04
+#define CLK_19_440MHz	0x06
+
+#define CLK_8_592MHz	0x08
+#define CLK_11_184MHz	0x09
+#define CLK_34_368MHz	0x0b
+#define CLK_44_736MHz	0x0a
+
+#define IOCTL_SELECT_AMCB1_TRANSMIT_CLOCK _IOR(TLCLK_IOC_MAGIC,  9, char)
+#define IOCTL_SELECT_AMCB2_TRANSMIT_CLOCK _IOR(TLCLK_IOC_MAGIC,  10, char)
+
+/* RECEIVED REFERENCE */
+
+#define AMC_B1 0
+#define AMC_B2 1
+
+#define IOCTL_SELECT_RECEIVED_REF_CLK3A _IOR(TLCLK_IOC_MAGIC,  11, char)
+#define IOCTL_SELECT_RECEIVED_REF_CLK3B _IOR(TLCLK_IOC_MAGIC,  12, char)
+
+/* OEM COMMAND - NOT IN FINAL VERSION */
+
+#define IOCTL_TEST_MODE	_IO(TLCLK_IOC_MAGIC,  13)
+
+/* HARDWARE SWITCHING DEFINE */
+
+#define HW_ENABLE	0x80
+#define HW_DISABLE	0x00
+
+#define IOCTL_HARDWARE_SWITCHING	_IOR(TLCLK_IOC_MAGIC,  14, char)
+
+/* HARDWARE SWITCHING MODE DEFINE */
+
+#define PLL_HOLDOVER	0x40
+#define LOST_CLOCK	0x00
+
+#define IOCTL_HARDWARE_SWITCHING_MODE	_IOR(TLCLK_IOC_MAGIC,  15, char)
+
+/* CLOCK OUTPUT DEFINE */
+
+#define IOCTL_ENABLE_CLKA0_OUTPUT	_IOR(TLCLK_IOC_MAGIC,  16, char)
+#define IOCTL_ENABLE_CLKB0_OUTPUT	_IOR(TLCLK_IOC_MAGIC,  17, char)
+#define IOCTL_ENABLE_CLKA1_OUTPUT	_IOR(TLCLK_IOC_MAGIC,  18, char)
+#define IOCTL_ENABLE_CLKB1_OUTPUT	_IOR(TLCLK_IOC_MAGIC,  19, char)
+
+#define IOCTL_ENABLE_CLK3A_OUTPUT	_IOR(TLCLK_IOC_MAGIC,  20, char)
+#define IOCTL_ENABLE_CLK3B_OUTPUT	_IOR(TLCLK_IOC_MAGIC,  21, char)
+
+/* ALARMS DEFINE */
+
+#define UNLOCK_MASK	0x10
+#define HOLDOVER_MASK	0x20
+#define SEC_LOST_MASK	0x40
+#define PRI_LOST_MASK	0x80
+
+#define IOCTL_READ_ALARMS		_IO(TLCLK_IOC_MAGIC,  22)
+
+/* INTERRUPT CAUSE DEFINE */
+
+#define PRI_LOS_01_MASK		0x01
+#define PRI_LOS_10_MASK		0x02
+
+#define SEC_LOS_01_MASK		0x04
+#define SEC_LOS_10_MASK		0x08
+
+#define HOLDOVER_01_MASK	0x10
+#define HOLDOVER_10_MASK	0x20
+
+#define UNLOCK_01_MASK		0x40
+#define UNLOCK_10_MASK		0x80
+
+#define IOCTL_READ_INTERRUPT_SWITCH	_IO(TLCLK_IOC_MAGIC,  23)
+
+#define IOCTL_READ_CURRENT_REF		_IO(TLCLK_IOC_MAGIC,  25)
+
+/* MAX NUMBER OF IOCTL */
+#define TLCLK_IOC_MAXNR 25
+
+struct tlclk_alarms {
+	__u32 lost_clocks;
+	__u32 lost_primary_clock;
+	__u32 lost_secondary_clock;
+	__u32 primary_clock_back;
+	__u32 secondary_clock_back;
+	__u32 switchover_primary;
+	__u32 switchover_secondary;
+	__u32 pll_holdover;
+	__u32 pll_end_holdover;
+	__u32 pll_lost_sync;
+	__u32 pll_sync;
+};

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Telecom Clock Driver for MPCBL0010 ATCA computer blade
  2005-10-06 15:03 Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade Mark Gross
@ 2005-10-06 16:52 ` Jesper Juhl
  2005-10-06 17:28   ` Mark Gross
  2005-10-06 17:24 ` Fwd: " Alexey Dobriyan
  2005-10-06 18:20 ` Greg KH
  2 siblings, 1 reply; 18+ messages in thread
From: Jesper Juhl @ 2005-10-06 16:52 UTC (permalink / raw)
  To: Mark Gross; +Cc: akpm, linux-kernel, Sebastien.Bouchard, mark.gross

On 10/6/05, Mark Gross <mgross@linux.intel.com> wrote:
>
> Andrew,
>
> Attached is a simple charactor driver for possible inclusion in your MM tree.
>
> This driver is specific to the MPCBL0010 that will start shipping this fall.
>
> The telcom clock is a special circuit, line card PLL, that provids a mechanism
> for synchronization of specialized hardware across the backplane of a chassis
> of multiple computers with similar specail curcits.  In this case the
> synchronization signals get routed to multiple places, typically to pins on
> expansion slots for hardware that knows what to do with this signal.  (SONET,
> G.813, stratum 3...) and similar signaling applications found in telcom sites
> can use this type of thing.
>
> The actual device is hidden behind the FPGA on the motherboar, and is
> connected to the FPGA via I2C.  This driver only talks to the FPGA registers.
>

A few minor style and spelling comments :

[snip]
> + *
> + * Send feedback to <sebastien.bouchard@ca.kontron.com>
> + *
> + * 2.6 driver version being maintained by <mark.gross@intel.com>

shouldn't this info go into CREDITS/MAINTAINERS and the above then simply be

 * Send feedback to Sebastien Bouchard and the current maintainer.

???

Same comment for the other files.


[snip]
> +/* sysFS interface definition:

Isn't it just called "sysfs" without the caps?


> +Uppon loading the driver will create a sysfs directory under class/misc/tlclk.

s/Uppon/Upon/


> +
> +This directory exports the following interfaces.  There opperation is documented

Line exceeds 80 characters (in this case due to trailing whitespace).

Let me quote Documentation/CodingStyle Chapter 2 :

"
The limit on the length of lines is 80 columns and this is a hard limit.

Statements longer than 80 columns will be broken into sensible chunks.
Descendants are always substantially shorter than the parent and are placed
substantially to the right. The same applies to function headers with a long
argument list. Long strings are as well broken into shorter strings.
"

You have both comment and code lines elsewhere in the file that exceed this.
Please fix.

And while you are at it, please get rid of all the trailing whitespace. A
simple sed script will do it like this :

sed -r s/"[ \t]+$"/""/ file_with_trailing_whitespace.c > fixed_file.c

[snip]
> +All sysfs interaces are integers in hex format, i.e echo 99 > refalign

I trust you mean "interfaces" ...

[snip]
> +#define debug_printk( args... ) printk( args)
> +#else
> +#define debug_printk( args... )

Why these spaces after start paren and before closing paren?


[snip]
> +tlclk_read(struct file * filp, char __user * buf, size_t count, loff_t * f_pos)

Most of your functions nicely place the '*' next to the variable name, but this
one, the next one and a few other cases do not. Please be consistent.


[snip]
> +static CLASS_DEVICE_ATTR(enable_clk3b_output, S_IWUGO, NULL,
> +		store_enable_clk3b_output);
> +
> +
> +
> +static ssize_t store_enable_clk3a_output(struct class_device *d,

Why all those blank lines? One should do just fine.


[snip]
> +static CLASS_DEVICE_ATTR(enable_clk3a_output, S_IWUGO, NULL,
> +		store_enable_clk3a_output);
> +
> +
> +
> +static ssize_t store_enable_clkb1_output(struct class_device *d,

Again a lot of blank lines, and this is not the last case (just the last one
I'm going to point out).


[snip]
> +#ifdef  CONFIG_SYSFS
> +	if( 0 > (ret = misc_register(&tlclk_miscdev )) ) {

First a style thing :
   	if (0 > (ret = misc_register(&tlclk_miscdev))) {

Secondly, wouldn't this look nicer as
   	if ((ret = misc_register(&tlclk_miscdev)) < 0) {
or is that just me?

Personally I think I would prefer this :
   	ret = misc_register(&tlclk_miscdev);
   	if (ret < 0) {
Looks more readable to me.

> +		printk(KERN_ERR" misc_register retruns %d \n", ret);

Ehh, why a space just before the newline in this printk?


> +		ret =  -EBUSY;

   		ret = -EBUSY;




--
Jesper Juhl <jesper.juhl@gmail.com>
Don't top-post  http://www.catb.org/~esr/jargon/html/T/top-post.html
Plain text mails only, please      http://www.expita.com/nomime.html

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade
  2005-10-06 15:03 Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade Mark Gross
  2005-10-06 16:52 ` Jesper Juhl
@ 2005-10-06 17:24 ` Alexey Dobriyan
  2005-10-06 17:31   ` Mark Gross
  2005-10-06 18:20 ` Greg KH
  2 siblings, 1 reply; 18+ messages in thread
From: Alexey Dobriyan @ 2005-10-06 17:24 UTC (permalink / raw)
  To: Mark Gross; +Cc: akpm, linux-kernel, Sebastien.Bouchard, mark.gross

> --- linux-2.6.14-rc2-mm2/drivers/char/tlclk.c
> +++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.c

Can you drop ioctl part of interface and leave only sysfs one?

> +#ifdef TLCLK_IOCTL
> +static int
> +tlclk_ioctl(struct inode *inode,
> +	    struct file *filp, unsigned int cmd, unsigned long arg)
> +{
> +	unsigned long flags;
> +	unsigned char val;
> +	int ret_val = 0;
> +
> +	val = (unsigned char)arg;
> +	if (_IOC_TYPE(cmd) != TLCLK_IOC_MAGIC)
> +		return -ENOTTY;
> +
> +	if (_IOC_NR(cmd) > TLCLK_IOC_MAXNR)
> +		return -ENOTTY;
> +
> +	spin_lock_irqsave(&event_lock, flags);
> +	switch (cmd) {
> +	case IOCTL_RESET:
> +		SET_PORT_BITS(TLCLK_REG4, 0xfd, val);
> +		break;
> +	case IOCTL_MODE_SELECT:
> +		SET_PORT_BITS(TLCLK_REG0, 0xcf, val);
> +		break;
> +	case IOCTL_REFALIGN:
> +		/* GENERATING 0 to 1 transistion */
> +		SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
> +		udelay(2);
> +		SET_PORT_BITS(TLCLK_REG0, 0xf7, 0x08);
> +		udelay(2);
> +		SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
> +		break;
> +	case IOCTL_HARDWARE_SWITCHING:
> +		SET_PORT_BITS(TLCLK_REG0, 0x7f, val);
> +		break;
> +	case IOCTL_HARDWARE_SWITCHING_MODE:
> +		SET_PORT_BITS(TLCLK_REG0, 0xbf, val);
> +		break;
> +	case IOCTL_FILTER_SELECT:
> +		SET_PORT_BITS(TLCLK_REG0, 0xfb, val);
> +		break;
> +	case IOCTL_SELECT_REF_FREQUENCY:
> +		SET_PORT_BITS(TLCLK_REG1, 0xfd, val);
> +		break;
> +	case IOCTL_SELECT_REDUNDANT_CLOCK:
> +		SET_PORT_BITS(TLCLK_REG1, 0xfe, val);
> +		break;
> +	case IOCTL_SELECT_AMCB1_TRANSMIT_CLOCK:
> +		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
> +			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x5);
> +			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
> +		} else if (val >= CLK_8_592MHz) {
> +			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x7);
> +			switch (val) {
> +			case CLK_8_592MHz:
> +				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
> +				break;
> +			case CLK_11_184MHz:
> +				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
> +				break;
> +			case CLK_34_368MHz:
> +				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
> +				break;
> +			case CLK_44_736MHz:
> +				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
> +				break;
> +			}
> +		} else
> +			SET_PORT_BITS(TLCLK_REG3, 0xf8, val);
> +		break;
> +	case IOCTL_SELECT_AMCB2_TRANSMIT_CLOCK:
> +		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
> +			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x28);
> +			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
> +		} else if (val >= CLK_8_592MHz) {
> +			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x38);
> +			switch (val) {
> +			case CLK_8_592MHz:
> +				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
> +				break;
> +			case CLK_11_184MHz:
> +				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
> +				break;
> +			case CLK_34_368MHz:
> +				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
> +				break;
> +			case CLK_44_736MHz:
> +				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
> +				break;
> +			}
> +		} else
> +			SET_PORT_BITS(TLCLK_REG3, 0xc7, val << 3);
> +		break;
> +	case IOCTL_TEST_MODE:
> +		SET_PORT_BITS(TLCLK_REG4, 0xfd, 2);
> +		break;
> +	case IOCTL_ENABLE_CLKA0_OUTPUT:
> +		SET_PORT_BITS(TLCLK_REG2, 0xfe, val);
> +		break;
> +	case IOCTL_ENABLE_CLKB0_OUTPUT:
> +		SET_PORT_BITS(TLCLK_REG2, 0xfd, val << 1);
> +		break;
> +	case IOCTL_ENABLE_CLKA1_OUTPUT:
> +		SET_PORT_BITS(TLCLK_REG2, 0xfb, val << 2);
> +		break;
> +	case IOCTL_ENABLE_CLKB1_OUTPUT:
> +		SET_PORT_BITS(TLCLK_REG2, 0xf7, val << 3);
> +		break;
> +	case IOCTL_ENABLE_CLK3A_OUTPUT:
> +		SET_PORT_BITS(TLCLK_REG3, 0xbf, val << 6);
> +		break;
> +	case IOCTL_ENABLE_CLK3B_OUTPUT:
> +		SET_PORT_BITS(TLCLK_REG3, 0x7f, val << 7);
> +		break;
> +	case IOCTL_READ_ALARMS:
> +		ret_val = (inb(TLCLK_REG2) & 0xf0);
> +		break;
> +	case IOCTL_READ_INTERRUPT_SWITCH:
> +		ret_val = inb(TLCLK_REG6);
> +		break;
> +	case IOCTL_READ_CURRENT_REF:
> +		ret_val = ((inb(TLCLK_REG1) & 0x08) >> 3);
> +		break;
> +	}
> +	spin_unlock_irqrestore(&event_lock, flags);
> +
> +	return ret_val;
> +}
> +#endif


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Telecom Clock Driver for MPCBL0010 ATCA computer blade
  2005-10-06 16:52 ` Jesper Juhl
@ 2005-10-06 17:28   ` Mark Gross
  2005-10-07 12:21     ` Jesper Juhl
  0 siblings, 1 reply; 18+ messages in thread
From: Mark Gross @ 2005-10-06 17:28 UTC (permalink / raw)
  To: Jesper Juhl; +Cc: akpm, linux-kernel, Sebastien.Bouchard, mark.gross

[-- Attachment #1: Type: text/plain, Size: 4702 bytes --]

On Thursday 06 October 2005 09:52, Jesper Juhl wrote:
> On 10/6/05, Mark Gross <mgross@linux.intel.com> wrote:
> >
> > Andrew,
> >
> > Attached is a simple charactor driver for possible inclusion in your MM tree.
> >
> > This driver is specific to the MPCBL0010 that will start shipping this fall.
> >
> > The telcom clock is a special circuit, line card PLL, that provids a mechanism
> > for synchronization of specialized hardware across the backplane of a chassis
> > of multiple computers with similar specail curcits.  In this case the
> > synchronization signals get routed to multiple places, typically to pins on
> > expansion slots for hardware that knows what to do with this signal.  (SONET,
> > G.813, stratum 3...) and similar signaling applications found in telcom sites
> > can use this type of thing.
> >
> > The actual device is hidden behind the FPGA on the motherboar, and is
> > connected to the FPGA via I2C.  This driver only talks to the FPGA registers.
> >
> 
> A few minor style and spelling comments :
> 
> [snip]
> > + *
> > + * Send feedback to <sebastien.bouchard@ca.kontron.com>
> > + *
> > + * 2.6 driver version being maintained by <mark.gross@intel.com>
> 
> shouldn't this info go into CREDITS/MAINTAINERS and the above then simply be
> 
>  * Send feedback to Sebastien Bouchard and the current maintainer.
> 
> ???
> 
> Same comment for the other files.
> 
> 
> [snip]
> > +/* sysFS interface definition:
> 
> Isn't it just called "sysfs" without the caps?
> 
> 
> > +Uppon loading the driver will create a sysfs directory under class/misc/tlclk.
> 
> s/Uppon/Upon/
> 
> 
> > +
> > +This directory exports the following interfaces.  There opperation is documented
> 
> Line exceeds 80 characters (in this case due to trailing whitespace).
> 
> Let me quote Documentation/CodingStyle Chapter 2 :
> 
> "
> The limit on the length of lines is 80 columns and this is a hard limit.
> 
> Statements longer than 80 columns will be broken into sensible chunks.
> Descendants are always substantially shorter than the parent and are placed
> substantially to the right. The same applies to function headers with a long
> argument list. Long strings are as well broken into shorter strings.
> "
> 
> You have both comment and code lines elsewhere in the file that exceed this.
> Please fix.
> 
> And while you are at it, please get rid of all the trailing whitespace. A
> simple sed script will do it like this :
> 
> sed -r s/"[ \t]+$"/""/ file_with_trailing_whitespace.c > fixed_file.c
> 
> [snip]
> > +All sysfs interaces are integers in hex format, i.e echo 99 > refalign
> 
> I trust you mean "interfaces" ...
> 
> [snip]
> > +#define debug_printk( args... ) printk( args)
> > +#else
> > +#define debug_printk( args... )
> 
> Why these spaces after start paren and before closing paren?
> 
> 
> [snip]
> > +tlclk_read(struct file * filp, char __user * buf, size_t count, loff_t * f_pos)
> 
> Most of your functions nicely place the '*' next to the variable name, but this
> one, the next one and a few other cases do not. Please be consistent.
> 
> 
> [snip]
> > +static CLASS_DEVICE_ATTR(enable_clk3b_output, S_IWUGO, NULL,
> > +  store_enable_clk3b_output);
> > +
> > +
> > +
> > +static ssize_t store_enable_clk3a_output(struct class_device *d,
> 
> Why all those blank lines? One should do just fine.
> 
> 
> [snip]
> > +static CLASS_DEVICE_ATTR(enable_clk3a_output, S_IWUGO, NULL,
> > +  store_enable_clk3a_output);
> > +
> > +
> > +
> > +static ssize_t store_enable_clkb1_output(struct class_device *d,
> 
> Again a lot of blank lines, and this is not the last case (just the last one
> I'm going to point out).
> 
> 
> [snip]
> > +#ifdef  CONFIG_SYSFS
> > + if( 0 > (ret = misc_register(&tlclk_miscdev )) ) {
> 
> First a style thing :
>     if (0 > (ret = misc_register(&tlclk_miscdev))) {
> 
> Secondly, wouldn't this look nicer as
>     if ((ret = misc_register(&tlclk_miscdev)) < 0) {
> or is that just me?
> 
> Personally I think I would prefer this :
>     ret = misc_register(&tlclk_miscdev);
>     if (ret < 0) {
> Looks more readable to me.
> 
> > +  printk(KERN_ERR" misc_register retruns %d \n", ret);
> 
> Ehh, why a space just before the newline in this printk?
> 
> 
> > +  ret =  -EBUSY;
> 
>      ret = -EBUSY;
> 
> 
> 
> 
> --
> Jesper Juhl <jesper.juhl@gmail.com>
> Don't top-post  http://www.catb.org/~esr/jargon/html/T/top-post.html
> Plain text mails only, please      http://www.expita.com/nomime.html
> 

Thanks for the review, I thought I had the 80 line thing taken care of last month.  :(

See attached, I hope it catches all the issues you found.



-- 
--mgross
BTW: This may or may not be the opinion of my employer, more likely not.  

[-- Attachment #2: tlclk-2.6.14-rc2-mm2.patch --]
[-- Type: text/x-diff, Size: 32090 bytes --]

diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/Kconfig linux-2.6.14-rc2-mm2-tlclk/drivers/char/Kconfig
--- linux-2.6.14-rc2-mm2/drivers/char/Kconfig	2005-10-04 14:55:15.000000000 -0700
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/Kconfig	2005-10-04 14:57:47.000000000 -0700
@@ -1001,5 +1001,15 @@
 
 source "drivers/char/tpm/Kconfig"
 
+config TELCLOCK
+	tristate "Telecom clock driver for ATCA"
+	depends on EXPERIMENTAL
+	default n
+	help
+	  The telecom clock device allows direct userspace access to the
+	  configuration of the telecom clock configuration settings.
+	  This device is used for hardware synchronization across the ATCA
+	  backplane fabric.
+
 endmenu
 
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/Makefile linux-2.6.14-rc2-mm2-tlclk/drivers/char/Makefile
--- linux-2.6.14-rc2-mm2/drivers/char/Makefile	2005-10-04 14:55:15.000000000 -0700
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/Makefile	2005-10-04 14:57:47.000000000 -0700
@@ -82,6 +82,7 @@
 obj-$(CONFIG_SCx200_GPIO) += scx200_gpio.o
 obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o
 obj-$(CONFIG_TANBAC_TB0219) += tb0219.o
+obj-$(CONFIG_TELCLOCK) += tlclk.o
 
 obj-$(CONFIG_SPEAKUP) += speakup/
 obj-$(CONFIG_WATCHDOG)	+= watchdog/
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/tlclk.c linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.c
--- linux-2.6.14-rc2-mm2/drivers/char/tlclk.c	1969-12-31 16:00:00.000000000 -0800
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.c	2005-10-06 10:20:50.000000000 -0700
@@ -0,0 +1,941 @@
+/*
+ * Telecom Clock driver for Intel NetStructure(tm) MPCBL0010
+ *
+ * Copyright (C) 2005 Kontron Canada
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT.  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Send feedback to <sebastien.bouchard@ca.kontron.com> and the current
+ * Maintainer  <mark.gross@intel.com>
+ *
+ * Description : This is the TELECOM CLOCK module driver for the ATCA
+ * MPCBL0010 ATCA computer.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>	/* printk() */
+#include <linux/fs.h>		/* everything... */
+#include <linux/errno.h>	/* error codes */
+#include <linux/delay.h>	/* udelay */
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/sysfs.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+#include <asm/io.h>		/* inb/outb */
+#include <asm/uaccess.h>
+
+#include "tlclk.h"		/* TELECOM IOCTL DEFINE */
+
+MODULE_AUTHOR("Sebastien Bouchard <sebastien.bouchard@ca.kontron.com>");
+MODULE_LICENSE("GPL");
+
+/* Telecom clock I/O register definition */
+#define TLCLK_BASE 0xa08
+#define TLCLK_REG0 TLCLK_BASE
+#define TLCLK_REG1 (TLCLK_BASE+1)
+#define TLCLK_REG2 (TLCLK_BASE+2)
+#define TLCLK_REG3 (TLCLK_BASE+3)
+#define TLCLK_REG4 (TLCLK_BASE+4)
+#define TLCLK_REG5 (TLCLK_BASE+5)
+#define TLCLK_REG6 (TLCLK_BASE+6)
+#define TLCLK_REG7 (TLCLK_BASE+7)
+
+#define SET_PORT_BITS(port, mask, val) outb(((inb(port) & mask) | val), port)
+
+/* 0 = Dynamic allocation of the major device number */
+#define TLCLK_MAJOR 252
+
+/*
+uncomment to include classic IOCTL's
+#define TLCLK_IOCTL
+*/
+
+/* sysfs interface definition:
+Uppon loading the driver will create a sysfs directory under class/misc/tlclk.
+
+This directory exports the following interfaces.  There operation is
+documented in the MCPBL0010 TPS under the Telecom Clock API section, 11.4.
+alarms				:
+current_ref			:
+enable_clk3a_output		:
+enable_clk3b_output		:
+enable_clka0_output		:
+enable_clka1_output		:
+enable_clkb0_output		:
+enable_clkb1_output		:
+filter_select			:
+hardware_switching		:
+hardware_switching_mode		:
+interrupt_switch		:
+mode_select			:
+refalign			:
+reset				:
+select_amcb1_transmit_clock	:
+select_amcb2_transmit_clock	:
+select_redundant_clock		:
+select_ref_frequency		:
+test_mode			:
+
+All sysfs interfaces are integers in hex format, i.e echo 99 > refalign
+has the same effect as echo 0x99 > refalign.
+
+*/
+
+#if CONFIG_DEBUG_KERNEL
+#define debug_printk(args...) printk(args)
+#else
+#define debug_printk(args...)
+#endif
+
+
+static unsigned int telclk_interrupt;
+
+static int int_events;		/* Event that generate a interrupt */
+static int got_event;		/* if events processing have been done */
+
+static void switchover_timeout(unsigned long data);
+static struct timer_list switchover_timer =
+	TIMER_INITIALIZER(switchover_timeout , 0, 0);
+
+static struct tlclk_alarms *alarm_events;
+
+DEFINE_SPINLOCK(event_lock);
+
+static int tlclk_major = TLCLK_MAJOR;
+
+irqreturn_t tlclk_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+
+DECLARE_WAIT_QUEUE_HEAD(wq);
+
+#ifdef TLCLK_IOCTL
+static int
+tlclk_ioctl(struct inode *inode,
+	    struct file *filp, unsigned int cmd, unsigned long arg)
+{
+	unsigned long flags;
+	unsigned char val;
+	int ret_val = 0;
+
+	val = (unsigned char)arg;
+	if (_IOC_TYPE(cmd) != TLCLK_IOC_MAGIC)
+		return -ENOTTY;
+
+	if (_IOC_NR(cmd) > TLCLK_IOC_MAXNR)
+		return -ENOTTY;
+
+	spin_lock_irqsave(&event_lock, flags);
+	switch (cmd) {
+	case IOCTL_RESET:
+		SET_PORT_BITS(TLCLK_REG4, 0xfd, val);
+		break;
+	case IOCTL_MODE_SELECT:
+		SET_PORT_BITS(TLCLK_REG0, 0xcf, val);
+		break;
+	case IOCTL_REFALIGN:
+		/* GENERATING 0 to 1 transition */
+		SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
+		udelay(2);
+		SET_PORT_BITS(TLCLK_REG0, 0xf7, 0x08);
+		udelay(2);
+		SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
+		break;
+	case IOCTL_HARDWARE_SWITCHING:
+		SET_PORT_BITS(TLCLK_REG0, 0x7f, val);
+		break;
+	case IOCTL_HARDWARE_SWITCHING_MODE:
+		SET_PORT_BITS(TLCLK_REG0, 0xbf, val);
+		break;
+	case IOCTL_FILTER_SELECT:
+		SET_PORT_BITS(TLCLK_REG0, 0xfb, val);
+		break;
+	case IOCTL_SELECT_REF_FREQUENCY:
+		SET_PORT_BITS(TLCLK_REG1, 0xfd, val);
+		break;
+	case IOCTL_SELECT_REDUNDANT_CLOCK:
+		SET_PORT_BITS(TLCLK_REG1, 0xfe, val);
+		break;
+	case IOCTL_SELECT_AMCB1_TRANSMIT_CLOCK:
+		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x5);
+			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
+		} else if (val >= CLK_8_592MHz) {
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x7);
+			switch (val) {
+			case CLK_8_592MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
+				break;
+			case CLK_11_184MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
+				break;
+			case CLK_34_368MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
+				break;
+			case CLK_44_736MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
+				break;
+			}
+		} else
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, val);
+		break;
+	case IOCTL_SELECT_AMCB2_TRANSMIT_CLOCK:
+		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x28);
+			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
+		} else if (val >= CLK_8_592MHz) {
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x38);
+			switch (val) {
+			case CLK_8_592MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
+				break;
+			case CLK_11_184MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
+				break;
+			case CLK_34_368MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
+				break;
+			case CLK_44_736MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
+				break;
+			}
+		} else
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, val << 3);
+		break;
+	case IOCTL_TEST_MODE:
+		SET_PORT_BITS(TLCLK_REG4, 0xfd, 2);
+		break;
+	case IOCTL_ENABLE_CLKA0_OUTPUT:
+		SET_PORT_BITS(TLCLK_REG2, 0xfe, val);
+		break;
+	case IOCTL_ENABLE_CLKB0_OUTPUT:
+		SET_PORT_BITS(TLCLK_REG2, 0xfd, val << 1);
+		break;
+	case IOCTL_ENABLE_CLKA1_OUTPUT:
+		SET_PORT_BITS(TLCLK_REG2, 0xfb, val << 2);
+		break;
+	case IOCTL_ENABLE_CLKB1_OUTPUT:
+		SET_PORT_BITS(TLCLK_REG2, 0xf7, val << 3);
+		break;
+	case IOCTL_ENABLE_CLK3A_OUTPUT:
+		SET_PORT_BITS(TLCLK_REG3, 0xbf, val << 6);
+		break;
+	case IOCTL_ENABLE_CLK3B_OUTPUT:
+		SET_PORT_BITS(TLCLK_REG3, 0x7f, val << 7);
+		break;
+	case IOCTL_READ_ALARMS:
+		ret_val = (inb(TLCLK_REG2) & 0xf0);
+		break;
+	case IOCTL_READ_INTERRUPT_SWITCH:
+		ret_val = inb(TLCLK_REG6);
+		break;
+	case IOCTL_READ_CURRENT_REF:
+		ret_val = ((inb(TLCLK_REG1) & 0x08) >> 3);
+		break;
+	}
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return ret_val;
+}
+#endif
+
+
+static int tlclk_open(struct inode *inode, struct file *filp)
+{
+	int result;
+
+	/* Make sure there is no interrupt pending while
+	 * initialising interrupt handler */
+	inb(TLCLK_REG6);
+
+	/* This device is wired through the FPGA IO space of the ATCA blade
+	 * we can't share this IRQ */
+	result = request_irq(telclk_interrupt, &tlclk_interrupt,
+			     SA_INTERRUPT, "telclock", tlclk_interrupt);
+	if (result == -EBUSY) {
+		printk(KERN_ERR "telclock: Interrupt can't be reserved!\n");
+		return -EBUSY;
+	}
+	inb(TLCLK_REG6);	/* Clear interrupt events */
+
+	return 0;
+}
+
+static int tlclk_release(struct inode *inode, struct file *filp)
+{
+	free_irq(telclk_interrupt, tlclk_interrupt);
+
+	return 0;
+}
+
+ssize_t
+tlclk_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
+{
+	int count0 = sizeof(struct tlclk_alarms);
+
+	wait_event_interruptible(wq, got_event);
+	if (copy_to_user(buf, alarm_events, sizeof(struct tlclk_alarms)))
+		return -EFAULT;
+
+	memset(alarm_events, 0, sizeof(struct tlclk_alarms));
+	got_event = 0;
+
+	return count0;
+}
+
+ssize_t
+tlclk_write(struct file *filp, const char __user *buf, size_t count,
+	    loff_t *f_pos)
+{
+	return 0;
+}
+
+static struct file_operations tlclk_fops = {
+	.read = tlclk_read,
+	.write = tlclk_write,
+#ifdef TLCLK_IOCTL
+	.ioctl = tlclk_ioctl,
+#endif
+	.open = tlclk_open,
+	.release = tlclk_release,
+
+};
+
+#ifdef CONFIG_SYSFS
+static struct miscdevice tlclk_miscdev = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "tlclk",
+	.fops = &tlclk_fops,
+};
+
+static ssize_t show_current_ref(struct class_device *d, char *buf)
+{
+	unsigned long ret_val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+		ret_val = ((inb(TLCLK_REG1) & 0x08) >> 3);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return sprintf(buf, "0x%lX\n", ret_val);
+}
+
+static CLASS_DEVICE_ATTR(current_ref, S_IRUGO, show_current_ref, NULL);
+
+
+static ssize_t show_interrupt_switch(struct class_device *d, char *buf)
+{
+	unsigned long ret_val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+		ret_val = inb(TLCLK_REG6);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return sprintf(buf, "0x%lX\n", ret_val);
+}
+
+static CLASS_DEVICE_ATTR(interrupt_switch, S_IRUGO,
+		show_interrupt_switch, NULL);
+
+static ssize_t show_alarms(struct class_device *d, char *buf)
+{
+	unsigned long ret_val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+		ret_val = (inb(TLCLK_REG2) & 0xf0);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return sprintf(buf, "0x%lX\n", ret_val);
+}
+
+static CLASS_DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
+
+static ssize_t store_enable_clk3b_output(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		SET_PORT_BITS(TLCLK_REG3, 0x7f, val << 7);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(enable_clk3b_output, S_IWUGO, NULL,
+		store_enable_clk3b_output);
+
+static ssize_t store_enable_clk3a_output(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		SET_PORT_BITS(TLCLK_REG3, 0xbf, val << 6);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(enable_clk3a_output, S_IWUGO, NULL,
+		store_enable_clk3a_output);
+
+static ssize_t store_enable_clkb1_output(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		SET_PORT_BITS(TLCLK_REG2, 0xf7, val << 3);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(enable_clkb1_output, S_IWUGO, NULL,
+		store_enable_clkb1_output);
+
+
+static ssize_t store_enable_clka1_output(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		SET_PORT_BITS(TLCLK_REG2, 0xfb, val << 2);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(enable_clka1_output, S_IWUGO, NULL,
+		store_enable_clka1_output);
+
+static ssize_t store_enable_clkb0_output(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		SET_PORT_BITS(TLCLK_REG2, 0xfd, val << 1);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(enable_clkb0_output, S_IWUGO, NULL,
+		store_enable_clkb0_output);
+
+static ssize_t store_enable_clka0_output(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG2, 0xfe, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(enable_clka0_output, S_IWUGO, NULL,
+		store_enable_clka0_output);
+
+static ssize_t store_test_mode(struct class_device *d, const char *buf,
+	size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG4, 0xfd, 2);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(test_mode, S_IWUGO, NULL, store_test_mode);
+
+static ssize_t store_select_amcb2_transmit_clock(struct class_device *d,
+	const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x28);
+			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
+		} else if (val >= CLK_8_592MHz) {
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x38);
+			switch (val) {
+			case CLK_8_592MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
+				break;
+			case CLK_11_184MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
+				break;
+			case CLK_34_368MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
+				break;
+			case CLK_44_736MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
+				break;
+			}
+		} else
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, val << 3);
+
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(select_amcb2_transmit_clock, S_IWUGO, NULL,
+	store_select_amcb2_transmit_clock);
+
+static ssize_t store_select_amcb1_transmit_clock(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x5);
+			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
+		} else if (val >= CLK_8_592MHz) {
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x7);
+			switch (val) {
+			case CLK_8_592MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
+				break;
+			case CLK_11_184MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
+				break;
+			case CLK_34_368MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
+				break;
+			case CLK_44_736MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
+				break;
+			}
+		} else
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(select_amcb1_transmit_clock, S_IWUGO, NULL,
+		store_select_amcb1_transmit_clock);
+
+static ssize_t store_select_redundant_clock(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG1, 0xfe, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(select_redundant_clock, S_IWUGO, NULL,
+		store_select_redundant_clock);
+
+static ssize_t store_select_ref_frequency(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG1, 0xfd, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(select_ref_frequency, S_IWUGO, NULL,
+		store_select_ref_frequency);
+
+static ssize_t store_filter_select(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xfb, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(filter_select, S_IWUGO, NULL, store_filter_select);
+
+static ssize_t store_hardware_switching_mode(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xbf, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(hardware_switching_mode, S_IWUGO, NULL,
+		store_hardware_switching_mode);
+
+static ssize_t store_hardware_switching(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0x7f, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(hardware_switching, S_IWUGO, NULL,
+		store_hardware_switching);
+
+static ssize_t store_refalign (struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX\n", tmp);
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
+	udelay(2);
+	SET_PORT_BITS(TLCLK_REG0, 0xf7, 0x08);
+	udelay(2);
+	SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(refalign, S_IWUGO, NULL, store_refalign);
+
+static ssize_t store_mode_select (struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xcf, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(mode_select, S_IWUGO, NULL, store_mode_select);
+
+static ssize_t store_reset (struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	debug_printk(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG4, 0xfd, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(reset, S_IWUGO, NULL, store_reset);
+#endif /* CONFIG_SYSFS */
+
+static int __init tlclk_init(void)
+{
+	int ret;
+#ifdef  CONFIG_SYSFS
+	struct class_device *class;
+#endif
+	ret = register_chrdev(tlclk_major, "telclock", &tlclk_fops);
+
+	if (ret < 0) {
+		printk(KERN_ERR "telclock: can't get major! %d\n", tlclk_major);
+		return ret;
+	}
+
+	alarm_events = kcalloc(1, sizeof(struct tlclk_alarms), GFP_KERNEL);
+
+	if (!alarm_events)
+		goto out1;
+
+/* Read telecom clock IRQ number (Set by BIOS) */
+
+	if (!request_region(TLCLK_BASE, 8, "telclock")) {
+		printk(KERN_ERR "tlclk: request_region failed! 0x%X\n",
+			TLCLK_BASE);
+		ret = -EBUSY;
+		goto out2;
+	}
+	telclk_interrupt = (inb(TLCLK_REG7) & 0x0f);
+
+	if (0x0F == telclk_interrupt ) { /* not MCPBL0010 ? */
+		printk(KERN_ERR "telclk_interrup = 0x%x non-mcpbl0010 hw\n",
+			telclk_interrupt);
+		ret = -ENXIO;
+		goto out3;
+	}
+
+	init_timer(&switchover_timer);
+/*	switchover_timer.function = switchover_timeout; */
+/*	switchover_timer.data = 0; */
+
+#ifdef  CONFIG_SYSFS
+	ret = misc_register(&tlclk_miscdev);
+	if( ret < 0 ) {
+		printk(KERN_ERR" misc_register retruns %d\n", ret);
+		ret = -EBUSY;
+		goto out3;
+	}
+	class = tlclk_miscdev.class;
+	class_device_create_file(class, &class_device_attr_current_ref);
+	class_device_create_file(class, &class_device_attr_interrupt_switch);
+	class_device_create_file(class, &class_device_attr_alarms);
+	class_device_create_file(class, &class_device_attr_enable_clk3b_output);
+	class_device_create_file(class, &class_device_attr_enable_clk3a_output);
+	class_device_create_file(class, &class_device_attr_enable_clkb1_output);
+	class_device_create_file(class, &class_device_attr_enable_clka1_output);
+	class_device_create_file(class, &class_device_attr_enable_clkb0_output);
+	class_device_create_file(class, &class_device_attr_enable_clka0_output);
+	class_device_create_file(class, &class_device_attr_test_mode);
+	class_device_create_file(class,
+		&class_device_attr_select_amcb2_transmit_clock);
+	class_device_create_file(class,
+		&class_device_attr_select_amcb1_transmit_clock);
+	class_device_create_file(class, &class_device_attr_select_redundant_clock);
+	class_device_create_file(class, &class_device_attr_select_ref_frequency);
+	class_device_create_file(class, &class_device_attr_filter_select);
+	class_device_create_file(class,
+		&class_device_attr_hardware_switching_mode);
+	class_device_create_file(class, &class_device_attr_hardware_switching);
+	class_device_create_file(class, &class_device_attr_refalign);
+	class_device_create_file(class, &class_device_attr_mode_select);
+	class_device_create_file(class, &class_device_attr_reset);
+
+#endif /*  CONFIG_SYSFS */
+	return 0;
+out3:
+	release_region(TLCLK_BASE, 8);
+out2:
+	kfree(alarm_events);
+out1:
+	return ret;
+}
+
+static void __exit tlclk_cleanup(void)
+{
+#ifdef  CONFIG_SYSFS
+	misc_deregister(&tlclk_miscdev);
+#endif
+	unregister_chrdev(tlclk_major, "telclock");
+
+	release_region(TLCLK_BASE, 8);
+	del_timer_sync(&switchover_timer);
+	kfree(alarm_events);
+
+}
+
+static void switchover_timeout(unsigned long data)
+{
+	if ((data & 1)) {
+		if ((inb(TLCLK_REG1) & 0x08) != (data & 0x08))
+			alarm_events->switchover_primary++;
+	} else {
+		if ((inb(TLCLK_REG1) & 0x08) != (data & 0x08))
+			alarm_events->switchover_secondary++;
+	}
+
+	/* Alarm processing is done, wake up read task */
+	del_timer(&switchover_timer);
+	got_event = 1;
+	wake_up(&wq);
+}
+
+irqreturn_t tlclk_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+	/* Read and clear interrupt events */
+	int_events = inb(TLCLK_REG6);
+
+	/* Primary_Los changed from 0 to 1 ? */
+	if (int_events & PRI_LOS_01_MASK) {
+		if (inb(TLCLK_REG2) & SEC_LOST_MASK)
+			alarm_events->lost_clocks++;
+		else
+			alarm_events->lost_primary_clock++;
+	}
+
+	/* Primary_Los changed from 1 to 0 ? */
+	if (int_events & PRI_LOS_10_MASK) {
+		alarm_events->primary_clock_back++;
+		SET_PORT_BITS(TLCLK_REG1, 0xFE, 1);
+	}
+	/* Secondary_Los changed from 0 to 1 ? */
+	if (int_events & SEC_LOS_01_MASK) {
+		if (inb(TLCLK_REG2) & PRI_LOST_MASK)
+			alarm_events->lost_clocks++;
+		else
+			alarm_events->lost_secondary_clock++;
+	}
+	/* Secondary_Los changed from 1 to 0 ? */
+	if (int_events & SEC_LOS_10_MASK) {
+		alarm_events->secondary_clock_back++;
+		SET_PORT_BITS(TLCLK_REG1, 0xFE, 0);
+	}
+	if (int_events & HOLDOVER_10_MASK)
+		alarm_events->pll_end_holdover++;
+
+	if (int_events & UNLOCK_01_MASK)
+		alarm_events->pll_lost_sync++;
+
+	if (int_events & UNLOCK_10_MASK)
+		alarm_events->pll_sync++;
+
+	/* Holdover changed from 0 to 1 ? */
+	if (int_events & HOLDOVER_01_MASK) {
+		alarm_events->pll_holdover++;
+
+		/* TIMEOUT in ~10ms */
+		switchover_timer.expires = jiffies + msecs_to_jiffies(10);
+		switchover_timer.data = inb(TLCLK_REG1);
+		add_timer(&switchover_timer);
+	} else {
+		got_event = 1;
+		wake_up(&wq);
+	}
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+module_init(tlclk_init);
+module_exit(tlclk_cleanup);
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/tlclk.h linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.h
--- linux-2.6.14-rc2-mm2/drivers/char/tlclk.h	1969-12-31 16:00:00.000000000 -0800
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.h	2005-10-06 10:15:33.000000000 -0700
@@ -0,0 +1,167 @@
+/*
+ * Telecom Clock driver for Wainwright board
+ *
+ * Copyright (C) 2005 Kontron Canada
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT.  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Send feedback to <sebastien.bouchard@ca.kontron.com> and the current
+ * Maintainer  <mark.gross@intel.com>
+ *
+ */
+
+/* Ioctl definitions  */
+
+/* Use 0xA1 as magic number */
+#define TLCLK_IOC_MAGIC 0xA1
+
+/*Hardware Reset of the PLL */
+
+#define RESET_ON	0x00
+#define RESET_OFF	0x01
+#define IOCTL_RESET			_IO(TLCLK_IOC_MAGIC,  1)
+
+#define IOCTL_REFALIGN			_IO(TLCLK_IOC_MAGIC,  2)
+
+/* MODE SELECT */
+
+#define NORMAL_MODE 	0x00
+#define HOLDOVER_MODE	0x10
+#define FREERUN_MODE	0x20
+
+#define IOCTL_MODE_SELECT		_IOR(TLCLK_IOC_MAGIC,  3, char)
+
+/* FILTER SELECT */
+
+#define FILTER_6HZ	0x04
+#define FILTER_12HZ	0x00
+
+#define IOCTL_FILTER_SELECT		_IOR(TLCLK_IOC_MAGIC,  4, char)
+
+/* SELECT REFERENCE FREQUENCY */
+
+#define REF_CLK1_8kHz		0x00
+#define REF_CLK2_19_44MHz	0x02
+
+#define IOCTL_SELECT_REF_FREQUENCY	_IOR(TLCLK_IOC_MAGIC,  6, char)
+
+/* Select primary or secondary redundant clock */
+
+#define PRIMARY_CLOCK	0x00
+#define SECONDARY_CLOCK	0x01
+#define IOCTL_SELECT_REDUNDANT_CLOCK	_IOR(TLCLK_IOC_MAGIC,  7, char)
+
+/* CLOCK TRANSMISSION DEFINE */
+
+#define CLK_8kHz	0xff
+#define CLK_16_384MHz	0xfb
+
+#define CLK_1_544MHz	0x00
+#define CLK_2_048MHz	0x01
+#define CLK_4_096MHz	0x02
+#define CLK_6_312MHz	0x03
+#define CLK_8_192MHz	0x04
+#define CLK_19_440MHz	0x06
+
+#define CLK_8_592MHz	0x08
+#define CLK_11_184MHz	0x09
+#define CLK_34_368MHz	0x0b
+#define CLK_44_736MHz	0x0a
+
+#define IOCTL_SELECT_AMCB1_TRANSMIT_CLOCK _IOR(TLCLK_IOC_MAGIC,  9, char)
+#define IOCTL_SELECT_AMCB2_TRANSMIT_CLOCK _IOR(TLCLK_IOC_MAGIC,  10, char)
+
+/* RECEIVED REFERENCE */
+
+#define AMC_B1 0
+#define AMC_B2 1
+
+#define IOCTL_SELECT_RECEIVED_REF_CLK3A _IOR(TLCLK_IOC_MAGIC,  11, char)
+#define IOCTL_SELECT_RECEIVED_REF_CLK3B _IOR(TLCLK_IOC_MAGIC,  12, char)
+
+/* OEM COMMAND - NOT IN FINAL VERSION */
+
+#define IOCTL_TEST_MODE	_IO(TLCLK_IOC_MAGIC,  13)
+
+/* HARDWARE SWITCHING DEFINE */
+
+#define HW_ENABLE	0x80
+#define HW_DISABLE	0x00
+
+#define IOCTL_HARDWARE_SWITCHING	_IOR(TLCLK_IOC_MAGIC,  14, char)
+
+/* HARDWARE SWITCHING MODE DEFINE */
+
+#define PLL_HOLDOVER	0x40
+#define LOST_CLOCK	0x00
+
+#define IOCTL_HARDWARE_SWITCHING_MODE	_IOR(TLCLK_IOC_MAGIC,  15, char)
+
+/* CLOCK OUTPUT DEFINE */
+
+#define IOCTL_ENABLE_CLKA0_OUTPUT	_IOR(TLCLK_IOC_MAGIC,  16, char)
+#define IOCTL_ENABLE_CLKB0_OUTPUT	_IOR(TLCLK_IOC_MAGIC,  17, char)
+#define IOCTL_ENABLE_CLKA1_OUTPUT	_IOR(TLCLK_IOC_MAGIC,  18, char)
+#define IOCTL_ENABLE_CLKB1_OUTPUT	_IOR(TLCLK_IOC_MAGIC,  19, char)
+
+#define IOCTL_ENABLE_CLK3A_OUTPUT	_IOR(TLCLK_IOC_MAGIC,  20, char)
+#define IOCTL_ENABLE_CLK3B_OUTPUT	_IOR(TLCLK_IOC_MAGIC,  21, char)
+
+/* ALARMS DEFINE */
+
+#define UNLOCK_MASK	0x10
+#define HOLDOVER_MASK	0x20
+#define SEC_LOST_MASK	0x40
+#define PRI_LOST_MASK	0x80
+
+#define IOCTL_READ_ALARMS		_IO(TLCLK_IOC_MAGIC,  22)
+
+/* INTERRUPT CAUSE DEFINE */
+
+#define PRI_LOS_01_MASK		0x01
+#define PRI_LOS_10_MASK		0x02
+
+#define SEC_LOS_01_MASK		0x04
+#define SEC_LOS_10_MASK		0x08
+
+#define HOLDOVER_01_MASK	0x10
+#define HOLDOVER_10_MASK	0x20
+
+#define UNLOCK_01_MASK		0x40
+#define UNLOCK_10_MASK		0x80
+
+#define IOCTL_READ_INTERRUPT_SWITCH	_IO(TLCLK_IOC_MAGIC,  23)
+
+#define IOCTL_READ_CURRENT_REF		_IO(TLCLK_IOC_MAGIC,  25)
+
+/* MAX NUMBER OF IOCTL */
+#define TLCLK_IOC_MAXNR 25
+
+struct tlclk_alarms {
+	__u32 lost_clocks;
+	__u32 lost_primary_clock;
+	__u32 lost_secondary_clock;
+	__u32 primary_clock_back;
+	__u32 secondary_clock_back;
+	__u32 switchover_primary;
+	__u32 switchover_secondary;
+	__u32 pll_holdover;
+	__u32 pll_end_holdover;
+	__u32 pll_lost_sync;
+	__u32 pll_sync;
+};
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/MAINTAINERS linux-2.6.14-rc2-mm2-tlclk/MAINTAINERS
--- linux-2.6.14-rc2-mm2/MAINTAINERS	2005-10-04 14:55:18.000000000 -0700
+++ linux-2.6.14-rc2-mm2-tlclk/MAINTAINERS	2005-10-06 10:02:23.000000000 -0700
@@ -2319,6 +2319,11 @@
 L:	tpmdd-devel@lists.sourceforge.net
 S:	Maintained
 
+Telecom Clock Driver for MCPL0010
+P: Mark Gross
+M: mark.gross@intel.com
+S: Supported
+
 TENSILICA XTENSA PORT (xtensa):
 P:	Chris Zankel
 M:	chris@zankel.net

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade
  2005-10-06 17:24 ` Fwd: " Alexey Dobriyan
@ 2005-10-06 17:31   ` Mark Gross
  0 siblings, 0 replies; 18+ messages in thread
From: Mark Gross @ 2005-10-06 17:31 UTC (permalink / raw)
  To: Alexey Dobriyan; +Cc: akpm, linux-kernel, Sebastien.Bouchard, mark.gross

On Thursday 06 October 2005 10:24, Alexey Dobriyan wrote:
> > --- linux-2.6.14-rc2-mm2/drivers/char/tlclk.c
> > +++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.c
> 
> Can you drop ioctl part of interface and leave only sysfs one?
> 

I would like to keep if for a little while because the hardware validation 
guys are still using test harnesses written for the 2.4 kernel driver 
version.  However; I am willing to pull this block if that would help in 
getting this driver into the kernel.

--mgross


> > +#ifdef TLCLK_IOCTL
> > +static int
> > +tlclk_ioctl(struct inode *inode,
> > +     struct file *filp, unsigned int cmd, unsigned long arg)
> > +{
> > + unsigned long flags;
> > + unsigned char val;
> > + int ret_val = 0;
> > +
> > + val = (unsigned char)arg;
> > + if (_IOC_TYPE(cmd) != TLCLK_IOC_MAGIC)
> > +  return -ENOTTY;
> > +
> > + if (_IOC_NR(cmd) > TLCLK_IOC_MAXNR)
> > +  return -ENOTTY;
> > +
> > + spin_lock_irqsave(&event_lock, flags);
> > + switch (cmd) {
> > + case IOCTL_RESET:
> > +		SET_PORT_BITS(TLCLK_REG4, 0xfd, val);
> > +		break;
> > +	case IOCTL_MODE_SELECT:
> > +		SET_PORT_BITS(TLCLK_REG0, 0xcf, val);
> > +		break;
> > +	case IOCTL_REFALIGN:
> > +		/* GENERATING 0 to 1 transistion */
> > +		SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
> > +		udelay(2);
> > +		SET_PORT_BITS(TLCLK_REG0, 0xf7, 0x08);
> > +		udelay(2);
> > +		SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
> > +		break;
> > +	case IOCTL_HARDWARE_SWITCHING:
> > +		SET_PORT_BITS(TLCLK_REG0, 0x7f, val);
> > +		break;
> > +	case IOCTL_HARDWARE_SWITCHING_MODE:
> > +		SET_PORT_BITS(TLCLK_REG0, 0xbf, val);
> > +		break;
> > +	case IOCTL_FILTER_SELECT:
> > +		SET_PORT_BITS(TLCLK_REG0, 0xfb, val);
> > +		break;
> > +	case IOCTL_SELECT_REF_FREQUENCY:
> > +		SET_PORT_BITS(TLCLK_REG1, 0xfd, val);
> > +		break;
> > +	case IOCTL_SELECT_REDUNDANT_CLOCK:
> > +		SET_PORT_BITS(TLCLK_REG1, 0xfe, val);
> > +		break;
> > +	case IOCTL_SELECT_AMCB1_TRANSMIT_CLOCK:
> > +		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
> > +			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x5);
> > +			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
> > +		} else if (val >= CLK_8_592MHz) {
> > +			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x7);
> > +			switch (val) {
> > +			case CLK_8_592MHz:
> > +				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
> > +				break;
> > +			case CLK_11_184MHz:
> > +				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
> > +				break;
> > +			case CLK_34_368MHz:
> > +				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
> > +				break;
> > +			case CLK_44_736MHz:
> > +				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
> > +				break;
> > +			}
> > +		} else
> > +			SET_PORT_BITS(TLCLK_REG3, 0xf8, val);
> > +		break;
> > +	case IOCTL_SELECT_AMCB2_TRANSMIT_CLOCK:
> > +		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
> > +			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x28);
> > +			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
> > +		} else if (val >= CLK_8_592MHz) {
> > +			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x38);
> > +			switch (val) {
> > +			case CLK_8_592MHz:
> > +				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
> > +				break;
> > +			case CLK_11_184MHz:
> > +				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
> > +				break;
> > +			case CLK_34_368MHz:
> > +				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
> > +				break;
> > +			case CLK_44_736MHz:
> > +				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
> > +				break;
> > +			}
> > +		} else
> > +			SET_PORT_BITS(TLCLK_REG3, 0xc7, val << 3);
> > +		break;
> > +	case IOCTL_TEST_MODE:
> > +		SET_PORT_BITS(TLCLK_REG4, 0xfd, 2);
> > +		break;
> > +	case IOCTL_ENABLE_CLKA0_OUTPUT:
> > +		SET_PORT_BITS(TLCLK_REG2, 0xfe, val);
> > +		break;
> > +	case IOCTL_ENABLE_CLKB0_OUTPUT:
> > +		SET_PORT_BITS(TLCLK_REG2, 0xfd, val << 1);
> > +		break;
> > +	case IOCTL_ENABLE_CLKA1_OUTPUT:
> > +		SET_PORT_BITS(TLCLK_REG2, 0xfb, val << 2);
> > +		break;
> > +	case IOCTL_ENABLE_CLKB1_OUTPUT:
> > +		SET_PORT_BITS(TLCLK_REG2, 0xf7, val << 3);
> > +		break;
> > +	case IOCTL_ENABLE_CLK3A_OUTPUT:
> > +		SET_PORT_BITS(TLCLK_REG3, 0xbf, val << 6);
> > +		break;
> > +	case IOCTL_ENABLE_CLK3B_OUTPUT:
> > +		SET_PORT_BITS(TLCLK_REG3, 0x7f, val << 7);
> > +		break;
> > +	case IOCTL_READ_ALARMS:
> > +		ret_val = (inb(TLCLK_REG2) & 0xf0);
> > +		break;
> > +	case IOCTL_READ_INTERRUPT_SWITCH:
> > +		ret_val = inb(TLCLK_REG6);
> > +		break;
> > +	case IOCTL_READ_CURRENT_REF:
> > +		ret_val = ((inb(TLCLK_REG1) & 0x08) >> 3);
> > +		break;
> > +	}
> > +	spin_unlock_irqrestore(&event_lock, flags);
> > +
> > +	return ret_val;
> > +}
> > +#endif
> 
> -
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/
> 

-- 
--mgross
BTW: This may or may not be the opinion of my employer, more likely not.  

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade
  2005-10-06 15:03 Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade Mark Gross
  2005-10-06 16:52 ` Jesper Juhl
  2005-10-06 17:24 ` Fwd: " Alexey Dobriyan
@ 2005-10-06 18:20 ` Greg KH
  2005-10-06 22:54   ` Mark Gross
  2 siblings, 1 reply; 18+ messages in thread
From: Greg KH @ 2005-10-06 18:20 UTC (permalink / raw)
  To: Mark Gross; +Cc: akpm, linux-kernel, Sebastien.Bouchard, mark.gross

On Thu, Oct 06, 2005 at 08:03:21AM -0700, Mark Gross wrote:
> +#if CONFIG_DEBUG_KERNEL 
> +#define debug_printk( args... ) printk( args)
> +#else
> +#define debug_printk( args... )
> +#endif

Please just use the existing dev_dbg() and friend functions instead of
creating your own.

> +DEFINE_SPINLOCK(event_lock);

This should be static, right?

> +irqreturn_t tlclk_interrupt(int irq, void *dev_id, struct pt_regs *regs);

static?

> +DECLARE_WAIT_QUEUE_HEAD(wq);

static?

> +#ifdef TLCLK_IOCTL

Please just delete this whole section, no new ioctls for 2.6 please.

> +ssize_t
> +tlclk_read(struct file * filp, char __user * buf, size_t count, loff_t * f_pos)

Return type on the same line as the function name please.

> +{
> +	int count0 = sizeof(struct tlclk_alarms);
> +
> +	wait_event_interruptible(wq, got_event);
> +	if (copy_to_user(buf, alarm_events, sizeof(struct tlclk_alarms)))
> +		return -EFAULT;
> +
> +	memset(alarm_events, 0, sizeof(struct tlclk_alarms));
> +	got_event = 0;
> +
> +	return count0;

count0 doesn't really need to be here, does it?

What if you get passed less than that size of data?  You will be reading
in off of the end of the buffer (which is not a nice thing to do...)

> +#ifdef CONFIG_SYSFS

Not needed, just drop this #ifdef please.

> +static ssize_t show_current_ref(struct class_device *d, char * buf)
> +{
> +	unsigned long ret_val;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&event_lock, flags);
> +		ret_val = ((inb(TLCLK_REG1) & 0x08) >> 3);
> +	spin_unlock_irqrestore(&event_lock, flags);

Odd indentation here.  You do this a lot, please fix them all.

> +static int __init tlclk_init(void)
> +{
> +	int ret;
> +#ifdef  CONFIG_SYSFS
> +	struct class_device *class;
> +#endif

Again, please drop all of the #ifdefs from this file, they are not
needed.

> +	alarm_events = kcalloc(1, sizeof(struct tlclk_alarms), GFP_KERNEL);

We have kzalloc() now.

> +
> +	if (!alarm_events)
> +		goto out1;
> +
> +/* Read telecom clock IRQ number (Set by BIOS) */

Indentation is wrong.

> +	if( 0 > (ret = misc_register(&tlclk_miscdev )) ) {

Try this instead:
	ret = misc_register(&tlclk_miscdev);
	if (ret) {

> +		printk(KERN_ERR" misc_register retruns %d \n", ret);
> +		ret =  -EBUSY;
> +		goto out3;
> +	}
> +	class = tlclk_miscdev.class;
> +	class_device_create_file(class, &class_device_attr_current_ref);

Try registering a whole attribute group instead.  It's much nicer than
the 20 lines you have to register and unregister your devices (and you
don't handle the error condition properly if something goes wrong half
way through.)

> diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/tlclk.h linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.h

Why not just put this stuff into the tlclk.c file itself, as it isn't
needed anywhere else?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade
  2005-10-06 18:20 ` Greg KH
@ 2005-10-06 22:54   ` Mark Gross
  2005-10-06 23:15     ` Greg KH
  2005-10-06 23:45     ` Alexey Dobriyan
  0 siblings, 2 replies; 18+ messages in thread
From: Mark Gross @ 2005-10-06 22:54 UTC (permalink / raw)
  To: Greg KH; +Cc: akpm, linux-kernel, Sebastien.Bouchard, mark.gross

[-- Attachment #1: Type: text/plain, Size: 1119 bytes --]

On Thursday 06 October 2005 11:20, Greg KH wrote:
> > +             printk(KERN_ERR" misc_register retruns %d \n", ret);
> > +             ret =  -EBUSY;
> > +             goto out3;
> > +     }
> > +     class = tlclk_miscdev.class;
> > +     class_device_create_file(class, &class_device_attr_current_ref);
> 
> Try registering a whole attribute group instead.  It's much nicer than
> the 20 lines you have to register and unregister your devices (and you
> don't handle the error condition properly if something goes wrong half
> way through.)
> 

I couldn't find such an API that wasn't static to class.c, or described in class.txt.  Any pointers on this would be helpful.


> > diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/tlclk.h linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.h
> 
> Why not just put this stuff into the tlclk.c file itself, as it isn't
> needed anywhere else?
> 
done.

Attached is an update that I think addresses your other comments.

Thanks for looking at this.

-- 
--mgross
BTW: This may or may not be the opinion of my employer, more likely not.  

[-- Attachment #2: tlclk-2.6.14-rc2-mm2.patch --]
[-- Type: text/x-diff, Size: 25172 bytes --]

diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/Kconfig linux-2.6.14-rc2-mm2-tlclk/drivers/char/Kconfig
--- linux-2.6.14-rc2-mm2/drivers/char/Kconfig	2005-10-04 14:55:15.000000000 -0700
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/Kconfig	2005-10-04 14:57:47.000000000 -0700
@@ -1001,5 +1001,15 @@
 
 source "drivers/char/tpm/Kconfig"
 
+config TELCLOCK
+	tristate "Telecom clock driver for ATCA"
+	depends on EXPERIMENTAL
+	default n
+	help
+	  The telecom clock device allows direct userspace access to the
+	  configuration of the telecom clock configuration settings.
+	  This device is used for hardware synchronization across the ATCA
+	  backplane fabric.
+
 endmenu
 
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/Makefile linux-2.6.14-rc2-mm2-tlclk/drivers/char/Makefile
--- linux-2.6.14-rc2-mm2/drivers/char/Makefile	2005-10-04 14:55:15.000000000 -0700
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/Makefile	2005-10-04 14:57:47.000000000 -0700
@@ -82,6 +82,7 @@
 obj-$(CONFIG_SCx200_GPIO) += scx200_gpio.o
 obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o
 obj-$(CONFIG_TANBAC_TB0219) += tb0219.o
+obj-$(CONFIG_TELCLOCK) += tlclk.o
 
 obj-$(CONFIG_SPEAKUP) += speakup/
 obj-$(CONFIG_WATCHDOG)	+= watchdog/
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/tlclk.c linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.c
--- linux-2.6.14-rc2-mm2/drivers/char/tlclk.c	1969-12-31 16:00:00.000000000 -0800
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.c	2005-10-06 15:41:56.000000000 -0700
@@ -0,0 +1,868 @@
+/*
+ * Telecom Clock driver for Intel NetStructure(tm) MPCBL0010
+ *
+ * Copyright (C) 2005 Kontron Canada
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT.  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Send feedback to <sebastien.bouchard@ca.kontron.com> and the current
+ * Maintainer  <mark.gross@intel.com>
+ *
+ * Description : This is the TELECOM CLOCK module driver for the ATCA
+ * MPCBL0010 ATCA computer.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>	/* printk() */
+#include <linux/fs.h>		/* everything... */
+#include <linux/errno.h>	/* error codes */
+#include <linux/delay.h>	/* udelay */
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/sysfs.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+#include <asm/io.h>		/* inb/outb */
+#include <asm/uaccess.h>
+
+MODULE_AUTHOR("Sebastien Bouchard <sebastien.bouchard@ca.kontron.com>");
+MODULE_LICENSE("GPL");
+
+/*Hardware Reset of the PLL */
+#define RESET_ON	0x00
+#define RESET_OFF	0x01
+
+/* MODE SELECT */
+#define NORMAL_MODE 	0x00
+#define HOLDOVER_MODE	0x10
+#define FREERUN_MODE	0x20
+
+/* FILTER SELECT */
+#define FILTER_6HZ	0x04
+#define FILTER_12HZ	0x00
+
+/* SELECT REFERENCE FREQUENCY */
+#define REF_CLK1_8kHz		0x00
+#define REF_CLK2_19_44MHz	0x02
+
+/* Select primary or secondary redundant clock */
+#define PRIMARY_CLOCK	0x00
+#define SECONDARY_CLOCK	0x01
+
+/* CLOCK TRANSMISSION DEFINE */
+#define CLK_8kHz	0xff
+#define CLK_16_384MHz	0xfb
+
+#define CLK_1_544MHz	0x00
+#define CLK_2_048MHz	0x01
+#define CLK_4_096MHz	0x02
+#define CLK_6_312MHz	0x03
+#define CLK_8_192MHz	0x04
+#define CLK_19_440MHz	0x06
+
+#define CLK_8_592MHz	0x08
+#define CLK_11_184MHz	0x09
+#define CLK_34_368MHz	0x0b
+#define CLK_44_736MHz	0x0a
+
+/* RECEIVED REFERENCE */
+#define AMC_B1 0
+#define AMC_B2 1
+
+/* HARDWARE SWITCHING DEFINE */
+#define HW_ENABLE	0x80
+#define HW_DISABLE	0x00
+
+/* HARDWARE SWITCHING MODE DEFINE */
+#define PLL_HOLDOVER	0x40
+#define LOST_CLOCK	0x00
+
+/* ALARMS DEFINE */
+#define UNLOCK_MASK	0x10
+#define HOLDOVER_MASK	0x20
+#define SEC_LOST_MASK	0x40
+#define PRI_LOST_MASK	0x80
+
+/* INTERRUPT CAUSE DEFINE */
+
+#define PRI_LOS_01_MASK		0x01
+#define PRI_LOS_10_MASK		0x02
+
+#define SEC_LOS_01_MASK		0x04
+#define SEC_LOS_10_MASK		0x08
+
+#define HOLDOVER_01_MASK	0x10
+#define HOLDOVER_10_MASK	0x20
+
+#define UNLOCK_01_MASK		0x40
+#define UNLOCK_10_MASK		0x80
+
+struct tlclk_alarms {
+	__u32 lost_clocks;
+	__u32 lost_primary_clock;
+	__u32 lost_secondary_clock;
+	__u32 primary_clock_back;
+	__u32 secondary_clock_back;
+	__u32 switchover_primary;
+	__u32 switchover_secondary;
+	__u32 pll_holdover;
+	__u32 pll_end_holdover;
+	__u32 pll_lost_sync;
+	__u32 pll_sync;
+};
+/* Telecom clock I/O register definition */
+#define TLCLK_BASE 0xa08
+#define TLCLK_REG0 TLCLK_BASE
+#define TLCLK_REG1 (TLCLK_BASE+1)
+#define TLCLK_REG2 (TLCLK_BASE+2)
+#define TLCLK_REG3 (TLCLK_BASE+3)
+#define TLCLK_REG4 (TLCLK_BASE+4)
+#define TLCLK_REG5 (TLCLK_BASE+5)
+#define TLCLK_REG6 (TLCLK_BASE+6)
+#define TLCLK_REG7 (TLCLK_BASE+7)
+
+#define SET_PORT_BITS(port, mask, val) outb(((inb(port) & mask) | val), port)
+
+/* 0 = Dynamic allocation of the major device number */
+#define TLCLK_MAJOR 0
+
+/* sysfs interface definition:
+Uppon loading the driver will create a sysfs directory under class/misc/tlclk.
+
+This directory exports the following interfaces.  There operation is
+documented in the MCPBL0010 TPS under the Telecom Clock API section, 11.4.
+alarms				:
+current_ref			:
+enable_clk3a_output		:
+enable_clk3b_output		:
+enable_clka0_output		:
+enable_clka1_output		:
+enable_clkb0_output		:
+enable_clkb1_output		:
+filter_select			:
+hardware_switching		:
+hardware_switching_mode		:
+interrupt_switch		:
+mode_select			:
+refalign			:
+reset				:
+select_amcb1_transmit_clock	:
+select_amcb2_transmit_clock	:
+select_redundant_clock		:
+select_ref_frequency		:
+test_mode			:
+
+All sysfs interfaces are integers in hex format, i.e echo 99 > refalign
+has the same effect as echo 0x99 > refalign.
+*/
+
+static unsigned int telclk_interrupt;
+
+static int int_events;		/* Event that generate a interrupt */
+static int got_event;		/* if events processing have been done */
+
+static void switchover_timeout(unsigned long data);
+static struct timer_list switchover_timer =
+	TIMER_INITIALIZER(switchover_timeout , 0, 0);
+
+static struct tlclk_alarms *alarm_events;
+
+static DEFINE_SPINLOCK(event_lock);
+
+static int tlclk_major = TLCLK_MAJOR;
+
+static irqreturn_t tlclk_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+
+static DECLARE_WAIT_QUEUE_HEAD(wq);
+
+static int tlclk_open(struct inode *inode, struct file *filp)
+{
+	int result;
+
+	/* Make sure there is no interrupt pending while
+	 * initialising interrupt handler */
+	inb(TLCLK_REG6);
+
+	/* This device is wired through the FPGA IO space of the ATCA blade
+	 * we can't share this IRQ */
+	result = request_irq(telclk_interrupt, &tlclk_interrupt,
+			     SA_INTERRUPT, "telclock", tlclk_interrupt);
+	if (result == -EBUSY) {
+		printk(KERN_ERR "telclock: Interrupt can't be reserved!\n");
+		return -EBUSY;
+	}
+	inb(TLCLK_REG6);	/* Clear interrupt events */
+
+	return 0;
+}
+
+static int tlclk_release(struct inode *inode, struct file *filp)
+{
+	free_irq(telclk_interrupt, tlclk_interrupt);
+
+	return 0;
+}
+
+ssize_t tlclk_read(struct file *filp, char __user *buf, size_t count,
+		loff_t *f_pos)
+{
+	if (count < sizeof(struct tlclk_alarms))
+		return -EIO;
+
+	wait_event_interruptible(wq, got_event);
+	if (copy_to_user(buf, alarm_events, sizeof(struct tlclk_alarms)))
+		return -EFAULT;
+
+	memset(alarm_events, 0, sizeof(struct tlclk_alarms));
+	got_event = 0;
+
+	return  sizeof(struct tlclk_alarms);
+}
+
+ssize_t tlclk_write(struct file *filp, const char __user *buf, size_t count,
+	    loff_t *f_pos)
+{
+	return 0;
+}
+
+static struct file_operations tlclk_fops = {
+	.read = tlclk_read,
+	.write = tlclk_write,
+	.open = tlclk_open,
+	.release = tlclk_release,
+
+};
+
+static struct miscdevice tlclk_miscdev = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "tlclk",
+	.fops = &tlclk_fops,
+};
+
+static ssize_t show_current_ref(struct class_device *d, char *buf)
+{
+	unsigned long ret_val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+	ret_val = ((inb(TLCLK_REG1) & 0x08) >> 3);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return sprintf(buf, "0x%lX\n", ret_val);
+}
+
+static CLASS_DEVICE_ATTR(current_ref, S_IRUGO, show_current_ref, NULL);
+
+
+static ssize_t show_interrupt_switch(struct class_device *d, char *buf)
+{
+	unsigned long ret_val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+	ret_val = inb(TLCLK_REG6);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return sprintf(buf, "0x%lX\n", ret_val);
+}
+
+static CLASS_DEVICE_ATTR(interrupt_switch, S_IRUGO,
+		show_interrupt_switch, NULL);
+
+static ssize_t show_alarms(struct class_device *d, char *buf)
+{
+	unsigned long ret_val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+	ret_val = (inb(TLCLK_REG2) & 0xf0);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return sprintf(buf, "0x%lX\n", ret_val);
+}
+
+static CLASS_DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
+
+static ssize_t store_enable_clk3b_output(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG3, 0x7f, val << 7);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(enable_clk3b_output, S_IWUGO, NULL,
+		store_enable_clk3b_output);
+
+static ssize_t store_enable_clk3a_output(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG3, 0xbf, val << 6);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(enable_clk3a_output, S_IWUGO, NULL,
+		store_enable_clk3a_output);
+
+static ssize_t store_enable_clkb1_output(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG2, 0xf7, val << 3);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(enable_clkb1_output, S_IWUGO, NULL,
+		store_enable_clkb1_output);
+
+
+static ssize_t store_enable_clka1_output(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG2, 0xfb, val << 2);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(enable_clka1_output, S_IWUGO, NULL,
+		store_enable_clka1_output);
+
+static ssize_t store_enable_clkb0_output(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG2, 0xfd, val << 1);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(enable_clkb0_output, S_IWUGO, NULL,
+		store_enable_clkb0_output);
+
+static ssize_t store_enable_clka0_output(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG2, 0xfe, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(enable_clka0_output, S_IWUGO, NULL,
+		store_enable_clka0_output);
+
+static ssize_t store_test_mode(struct class_device *d, const char *buf,
+		size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG4, 0xfd, 2);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(test_mode, S_IWUGO, NULL, store_test_mode);
+
+static ssize_t store_select_amcb2_transmit_clock(struct class_device *d,
+	const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x28);
+			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
+		} else if (val >= CLK_8_592MHz) {
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x38);
+			switch (val) {
+			case CLK_8_592MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
+				break;
+			case CLK_11_184MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
+				break;
+			case CLK_34_368MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
+				break;
+			case CLK_44_736MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
+				break;
+			}
+		} else
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, val << 3);
+
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(select_amcb2_transmit_clock, S_IWUGO, NULL,
+	store_select_amcb2_transmit_clock);
+
+static ssize_t store_select_amcb1_transmit_clock(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x5);
+			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
+		} else if (val >= CLK_8_592MHz) {
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x7);
+			switch (val) {
+			case CLK_8_592MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
+				break;
+			case CLK_11_184MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
+				break;
+			case CLK_34_368MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
+				break;
+			case CLK_44_736MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
+				break;
+			}
+		} else
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(select_amcb1_transmit_clock, S_IWUGO, NULL,
+		store_select_amcb1_transmit_clock);
+
+static ssize_t store_select_redundant_clock(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG1, 0xfe, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(select_redundant_clock, S_IWUGO, NULL,
+		store_select_redundant_clock);
+
+static ssize_t store_select_ref_frequency(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG1, 0xfd, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(select_ref_frequency, S_IWUGO, NULL,
+		store_select_ref_frequency);
+
+static ssize_t store_filter_select(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xfb, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(filter_select, S_IWUGO, NULL, store_filter_select);
+
+static ssize_t store_hardware_switching_mode(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xbf, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(hardware_switching_mode, S_IWUGO, NULL,
+		store_hardware_switching_mode);
+
+static ssize_t store_hardware_switching(struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0x7f, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(hardware_switching, S_IWUGO, NULL,
+		store_hardware_switching);
+
+static ssize_t store_refalign (struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(KERN_ERR "tmp = 0x%lX\n", tmp);
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
+	udelay(2);
+	SET_PORT_BITS(TLCLK_REG0, 0xf7, 0x08);
+	udelay(2);
+	SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(refalign, S_IWUGO, NULL, store_refalign);
+
+static ssize_t store_mode_select (struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xcf, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(mode_select, S_IWUGO, NULL, store_mode_select);
+
+static ssize_t store_reset (struct class_device *d,
+		const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(KERN_ERR "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG4, 0xfd, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static CLASS_DEVICE_ATTR(reset, S_IWUGO, NULL, store_reset);
+
+static int __init tlclk_init(void)
+{
+	int ret;
+	struct class_device *class;
+
+	ret = register_chrdev(tlclk_major, "telclock", &tlclk_fops);
+
+	if (ret < 0) {
+		printk(KERN_ERR "telclock: can't get major! %d\n", tlclk_major);
+		return ret;
+	}
+
+	alarm_events = kzalloc( sizeof(struct tlclk_alarms), GFP_KERNEL);
+
+	if (!alarm_events)
+		goto out1;
+
+/* Read telecom clock IRQ number (Set by BIOS) */
+
+	if (!request_region(TLCLK_BASE, 8, "telclock")) {
+		printk(KERN_ERR "tlclk: request_region failed! 0x%X\n",
+			TLCLK_BASE);
+		ret = -EBUSY;
+		goto out2;
+	}
+	telclk_interrupt = (inb(TLCLK_REG7) & 0x0f);
+
+	if (0x0F == telclk_interrupt ) { /* not MCPBL0010 ? */
+		printk(KERN_ERR "telclk_interrup = 0x%x non-mcpbl0010 hw\n",
+			telclk_interrupt);
+		ret = -ENXIO;
+		goto out3;
+	}
+
+	init_timer(&switchover_timer);
+/*	switchover_timer.function = switchover_timeout; */
+/*	switchover_timer.data = 0; */
+
+	ret = misc_register(&tlclk_miscdev);
+	if (ret < 0) {
+		printk(KERN_ERR" misc_register retruns %d\n", ret);
+		ret = -EBUSY;
+		goto out3;
+	}
+
+	class = tlclk_miscdev.class;
+	class_device_create_file(class, &class_device_attr_current_ref);
+	class_device_create_file(class, &class_device_attr_interrupt_switch);
+	class_device_create_file(class, &class_device_attr_alarms);
+	class_device_create_file(class, &class_device_attr_enable_clk3b_output);
+	class_device_create_file(class, &class_device_attr_enable_clk3a_output);
+	class_device_create_file(class, &class_device_attr_enable_clkb1_output);
+	class_device_create_file(class, &class_device_attr_enable_clka1_output);
+	class_device_create_file(class, &class_device_attr_enable_clkb0_output);
+	class_device_create_file(class, &class_device_attr_enable_clka0_output);
+	class_device_create_file(class, &class_device_attr_test_mode);
+	class_device_create_file(class,
+		&class_device_attr_select_amcb2_transmit_clock);
+	class_device_create_file(class,
+		&class_device_attr_select_amcb1_transmit_clock);
+	class_device_create_file(class, &class_device_attr_select_redundant_clock);
+	class_device_create_file(class, &class_device_attr_select_ref_frequency);
+	class_device_create_file(class, &class_device_attr_filter_select);
+	class_device_create_file(class,
+		&class_device_attr_hardware_switching_mode);
+	class_device_create_file(class, &class_device_attr_hardware_switching);
+	class_device_create_file(class, &class_device_attr_refalign);
+	class_device_create_file(class, &class_device_attr_mode_select);
+	class_device_create_file(class, &class_device_attr_reset);
+	
+	return 0;
+out3:
+	release_region(TLCLK_BASE, 8);
+out2:
+	kfree(alarm_events);
+out1:
+	return ret;
+}
+
+static void __exit tlclk_cleanup(void)
+{
+	misc_deregister(&tlclk_miscdev);
+	unregister_chrdev(tlclk_major, "telclock");
+
+	release_region(TLCLK_BASE, 8);
+	del_timer_sync(&switchover_timer);
+	kfree(alarm_events);
+
+}
+
+static void switchover_timeout(unsigned long data)
+{
+	if ((data & 1)) {
+		if ((inb(TLCLK_REG1) & 0x08) != (data & 0x08))
+			alarm_events->switchover_primary++;
+	} else {
+		if ((inb(TLCLK_REG1) & 0x08) != (data & 0x08))
+			alarm_events->switchover_secondary++;
+	}
+
+	/* Alarm processing is done, wake up read task */
+	del_timer(&switchover_timer);
+	got_event = 1;
+	wake_up(&wq);
+}
+
+static irqreturn_t tlclk_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+	/* Read and clear interrupt events */
+	int_events = inb(TLCLK_REG6);
+
+	/* Primary_Los changed from 0 to 1 ? */
+	if (int_events & PRI_LOS_01_MASK) {
+		if (inb(TLCLK_REG2) & SEC_LOST_MASK)
+			alarm_events->lost_clocks++;
+		else
+			alarm_events->lost_primary_clock++;
+	}
+
+	/* Primary_Los changed from 1 to 0 ? */
+	if (int_events & PRI_LOS_10_MASK) {
+		alarm_events->primary_clock_back++;
+		SET_PORT_BITS(TLCLK_REG1, 0xFE, 1);
+	}
+	/* Secondary_Los changed from 0 to 1 ? */
+	if (int_events & SEC_LOS_01_MASK) {
+		if (inb(TLCLK_REG2) & PRI_LOST_MASK)
+			alarm_events->lost_clocks++;
+		else
+			alarm_events->lost_secondary_clock++;
+	}
+	/* Secondary_Los changed from 1 to 0 ? */
+	if (int_events & SEC_LOS_10_MASK) {
+		alarm_events->secondary_clock_back++;
+		SET_PORT_BITS(TLCLK_REG1, 0xFE, 0);
+	}
+	if (int_events & HOLDOVER_10_MASK)
+		alarm_events->pll_end_holdover++;
+
+	if (int_events & UNLOCK_01_MASK)
+		alarm_events->pll_lost_sync++;
+
+	if (int_events & UNLOCK_10_MASK)
+		alarm_events->pll_sync++;
+
+	/* Holdover changed from 0 to 1 ? */
+	if (int_events & HOLDOVER_01_MASK) {
+		alarm_events->pll_holdover++;
+
+		/* TIMEOUT in ~10ms */
+		switchover_timer.expires = jiffies + msecs_to_jiffies(10);
+		switchover_timer.data = inb(TLCLK_REG1);
+		add_timer(&switchover_timer);
+	} else {
+		got_event = 1;
+		wake_up(&wq);
+	}
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+module_init(tlclk_init);
+module_exit(tlclk_cleanup);
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/MAINTAINERS linux-2.6.14-rc2-mm2-tlclk/MAINTAINERS
--- linux-2.6.14-rc2-mm2/MAINTAINERS	2005-10-04 14:55:18.000000000 -0700
+++ linux-2.6.14-rc2-mm2-tlclk/MAINTAINERS	2005-10-06 10:02:23.000000000 -0700
@@ -2319,6 +2319,11 @@
 L:	tpmdd-devel@lists.sourceforge.net
 S:	Maintained
 
+Telecom Clock Driver for MCPL0010
+P: Mark Gross
+M: mark.gross@intel.com
+S: Supported
+
 TENSILICA XTENSA PORT (xtensa):
 P:	Chris Zankel
 M:	chris@zankel.net

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade
  2005-10-06 22:54   ` Mark Gross
@ 2005-10-06 23:15     ` Greg KH
  2005-10-12 22:30       ` Mark Gross
  2005-10-06 23:45     ` Alexey Dobriyan
  1 sibling, 1 reply; 18+ messages in thread
From: Greg KH @ 2005-10-06 23:15 UTC (permalink / raw)
  To: Mark Gross; +Cc: akpm, linux-kernel, Sebastien.Bouchard, mark.gross

On Thu, Oct 06, 2005 at 03:54:34PM -0700, Mark Gross wrote:
> On Thursday 06 October 2005 11:20, Greg KH wrote:
> > > +?????????????printk(KERN_ERR" misc_register retruns %d \n", ret);
> > > +?????????????ret = ?-EBUSY;
> > > +?????????????goto out3;
> > > +?????}
> > > +?????class = tlclk_miscdev.class;
> > > +?????class_device_create_file(class, &class_device_attr_current_ref);
> > 
> > Try registering a whole attribute group instead. ?It's much nicer than
> > the 20 lines you have to register and unregister your devices (and you
> > don't handle the error condition properly if something goes wrong half
> > way through.)
> > 
> 
> I couldn't find such an API that wasn't static to class.c, or
> described in class.txt.  Any pointers on this would be helpful.

sysfs_create_group() is what you want.

> +ssize_t tlclk_read(struct file *filp, char __user *buf, size_t count,
> +		loff_t *f_pos)
> +{
> +	if (count < sizeof(struct tlclk_alarms))
> +		return -EIO;
> +
> +	wait_event_interruptible(wq, got_event);
> +	if (copy_to_user(buf, alarm_events, sizeof(struct tlclk_alarms)))
> +		return -EFAULT;
> +
> +	memset(alarm_events, 0, sizeof(struct tlclk_alarms));
> +	got_event = 0;
> +
> +	return  sizeof(struct tlclk_alarms);
> +}

Two spaces after the return :(

> +
> +/* Read telecom clock IRQ number (Set by BIOS) */

Indent the comment please.

> +	init_timer(&switchover_timer);
> +/*	switchover_timer.function = switchover_timeout; */
> +/*	switchover_timer.data = 0; */

Remove these commented out lines?

Other than these minor things, and the attribute group stuff, this looks
good.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade
  2005-10-06 22:54   ` Mark Gross
  2005-10-06 23:15     ` Greg KH
@ 2005-10-06 23:45     ` Alexey Dobriyan
  1 sibling, 0 replies; 18+ messages in thread
From: Alexey Dobriyan @ 2005-10-06 23:45 UTC (permalink / raw)
  To: Mark Gross; +Cc: Greg KH, akpm, linux-kernel, Sebastien.Bouchard, mark.gross

> Attached is an update that I think addresses your other comments.

> +config TELCLOCK
> +	tristate "Telecom clock driver for ATCA"
> +	depends on EXPERIMENTAL
> +	default n
> +	help
> +	  The telecom clock device allows direct userspace access to the
> +	  configuration of the telecom clock configuration settings.
> +	  This device is used for hardware synchronization across the ATCA
> +	  backplane fabric.

People usually tell here how the module will be called. See plenty
examples around.

> --- linux-2.6.14-rc2-mm2/drivers/char/tlclk.c
> +++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.c
> +Uppon loading the driver will create a sysfs directory under class/misc/tlclk.

Upon.

> +static int __init tlclk_init(void)
> +{

Missing unregister_chrdev() on error unrolling.

> +		printk(KERN_ERR" misc_register retruns %d\n", ret);

returns.

> +	return 0;
> +out3:
> +	release_region(TLCLK_BASE, 8);
> +out2:
> +	kfree(alarm_events);
> +out1:
> +	return ret;
> +}


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Telecom Clock Driver for MPCBL0010 ATCA computer blade
  2005-10-06 17:28   ` Mark Gross
@ 2005-10-07 12:21     ` Jesper Juhl
  0 siblings, 0 replies; 18+ messages in thread
From: Jesper Juhl @ 2005-10-07 12:21 UTC (permalink / raw)
  To: Mark Gross; +Cc: akpm, linux-kernel, Sebastien.Bouchard, mark.gross

On 10/6/05, Mark Gross <mgross@linux.intel.com> wrote:
> On Thursday 06 October 2005 09:52, Jesper Juhl wrote:
> > > +This directory exports the following interfaces.  There opperation is documented

Btw, please spell "operation" correctly :)

[snip]
> > > +  printk(KERN_ERR" misc_register retruns %d \n", ret);

Space between 'KERN_ERR" and '"' please.

--
Jesper Juhl <jesper.juhl@gmail.com>
Don't top-post  http://www.catb.org/~esr/jargon/html/T/top-post.html
Plain text mails only, please      http://www.expita.com/nomime.html

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade
  2005-10-06 23:15     ` Greg KH
@ 2005-10-12 22:30       ` Mark Gross
  2005-10-12 22:49         ` Greg KH
  0 siblings, 1 reply; 18+ messages in thread
From: Mark Gross @ 2005-10-12 22:30 UTC (permalink / raw)
  To: Greg KH; +Cc: akpm, linux-kernel, Sebastien.Bouchard, mark.gross

[-- Attachment #1: Type: text/plain, Size: 2015 bytes --]

On Thursday 06 October 2005 16:15, Greg KH wrote:
> On Thu, Oct 06, 2005 at 03:54:34PM -0700, Mark Gross wrote:
> > On Thursday 06 October 2005 11:20, Greg KH wrote:
> > > > +?????????????printk(KERN_ERR" misc_register retruns %d \n", ret);
> > > > +?????????????ret = ?-EBUSY;
> > > > +?????????????goto out3;
> > > > +?????}
> > > > +?????class = tlclk_miscdev.class;
> > > > +?????class_device_create_file(class, &class_device_attr_current_ref);
> > > 
> > > Try registering a whole attribute group instead. ?It's much nicer than
> > > the 20 lines you have to register and unregister your devices (and you
> > > don't handle the error condition properly if something goes wrong half
> > > way through.)
> > > 
> > 
> > I couldn't find such an API that wasn't static to class.c, or
> > described in class.txt.  Any pointers on this would be helpful.
> 
> sysfs_create_group() is what you want.
> 
> > +ssize_t tlclk_read(struct file *filp, char __user *buf, size_t count,
> > +  loff_t *f_pos)
> > +{
> > + if (count < sizeof(struct tlclk_alarms))
> > +  return -EIO;
> > +
> > + wait_event_interruptible(wq, got_event);
> > + if (copy_to_user(buf, alarm_events, sizeof(struct tlclk_alarms)))
> > +  return -EFAULT;
> > +
> > + memset(alarm_events, 0, sizeof(struct tlclk_alarms));
> > + got_event = 0;
> > +
> > + return  sizeof(struct tlclk_alarms);
> > +}
> 
> Two spaces after the return :(
> 
> > +
> > +/* Read telecom clock IRQ number (Set by BIOS) */
> 
> Indent the comment please.
> 
> > + init_timer(&switchover_timer);
> > +/* switchover_timer.function = switchover_timeout; */
> > +/* switchover_timer.data = 0; */
> 
> Remove these commented out lines?
> 
> Other than these minor things, and the attribute group stuff, this looks
> good.
> 
> thanks,

I think I've gotten all the comments addressed with this version.

Most significantly I moved the driver from a misc_device to a platform_device.

Thanks,


-- 
--mgross
BTW: This may or may not be the opinion of my employer, more likely not.  

[-- Attachment #2: tlclk-2.6.14-rc2-mm2.patch --]
[-- Type: text/x-diff, Size: 25378 bytes --]

diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/Kconfig linux-2.6.14-rc2-mm2-tlclk/drivers/char/Kconfig
--- linux-2.6.14-rc2-mm2/drivers/char/Kconfig	2005-10-04 14:55:15.000000000 -0700
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/Kconfig	2005-10-12 15:23:23.000000000 -0700
@@ -1001,5 +1001,17 @@
 
 source "drivers/char/tpm/Kconfig"
 
+config TELCLOCK
+	tristate "Telecom clock driver for MPBL0010 ATCA SBC"
+	depends on EXPERIMENTAL
+	default n
+	help
+	  The telecom clock device is specific to the MPBL0010 ATCA computer and
+	  allows direct userspace access to the configuration of the telecom clock
+	  configuration settings.  This device is used for hardware synchronization
+	  across the ATCA backplane fabric.  Upon loading, the driver exports a
+	  sysfs directory, /sys/devices/platform/telco_clock, with a number of
+	  files for controlling the behavior of this hardware.
+
 endmenu
 
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/Makefile linux-2.6.14-rc2-mm2-tlclk/drivers/char/Makefile
--- linux-2.6.14-rc2-mm2/drivers/char/Makefile	2005-10-04 14:55:15.000000000 -0700
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/Makefile	2005-10-12 10:02:13.000000000 -0700
@@ -82,6 +82,7 @@
 obj-$(CONFIG_SCx200_GPIO) += scx200_gpio.o
 obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o
 obj-$(CONFIG_TANBAC_TB0219) += tb0219.o
+obj-$(CONFIG_TELCLOCK) += tlclk.o
 
 obj-$(CONFIG_SPEAKUP) += speakup/
 obj-$(CONFIG_WATCHDOG)	+= watchdog/
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/tlclk.c linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.c
--- linux-2.6.14-rc2-mm2/drivers/char/tlclk.c	1969-12-31 16:00:00.000000000 -0800
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.c	2005-10-12 15:22:55.000000000 -0700
@@ -0,0 +1,878 @@
+/*
+ * Telecom Clock driver for Intel NetStructure(tm) MPCBL0010
+ *
+ * Copyright (C) 2005 Kontron Canada
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT.  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Send feedback to <sebastien.bouchard@ca.kontron.com> and the current
+ * Maintainer  <mark.gross@intel.com>
+ *
+ * Description : This is the TELECOM CLOCK module driver for the ATCA
+ * MPCBL0010 ATCA computer.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>	/* printk() */
+#include <linux/fs.h>		/* everything... */
+#include <linux/errno.h>	/* error codes */
+#include <linux/delay.h>	/* udelay */
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/sysfs.h>
+#define DEBUG
+#include <linux/device.h>
+#include <asm/io.h>		/* inb/outb */
+#include <asm/uaccess.h>
+
+MODULE_AUTHOR("Sebastien Bouchard <sebastien.bouchard@ca.kontron.com>");
+MODULE_LICENSE("GPL");
+
+/*Hardware Reset of the PLL */
+#define RESET_ON	0x00
+#define RESET_OFF	0x01
+
+/* MODE SELECT */
+#define NORMAL_MODE 	0x00
+#define HOLDOVER_MODE	0x10
+#define FREERUN_MODE	0x20
+
+/* FILTER SELECT */
+#define FILTER_6HZ	0x04
+#define FILTER_12HZ	0x00
+
+/* SELECT REFERENCE FREQUENCY */
+#define REF_CLK1_8kHz		0x00
+#define REF_CLK2_19_44MHz	0x02
+
+/* Select primary or secondary redundant clock */
+#define PRIMARY_CLOCK	0x00
+#define SECONDARY_CLOCK	0x01
+
+/* CLOCK TRANSMISSION DEFINE */
+#define CLK_8kHz	0xff
+#define CLK_16_384MHz	0xfb
+
+#define CLK_1_544MHz	0x00
+#define CLK_2_048MHz	0x01
+#define CLK_4_096MHz	0x02
+#define CLK_6_312MHz	0x03
+#define CLK_8_192MHz	0x04
+#define CLK_19_440MHz	0x06
+
+#define CLK_8_592MHz	0x08
+#define CLK_11_184MHz	0x09
+#define CLK_34_368MHz	0x0b
+#define CLK_44_736MHz	0x0a
+
+/* RECEIVED REFERENCE */
+#define AMC_B1 0
+#define AMC_B2 1
+
+/* HARDWARE SWITCHING DEFINE */
+#define HW_ENABLE	0x80
+#define HW_DISABLE	0x00
+
+/* HARDWARE SWITCHING MODE DEFINE */
+#define PLL_HOLDOVER	0x40
+#define LOST_CLOCK	0x00
+
+/* ALARMS DEFINE */
+#define UNLOCK_MASK	0x10
+#define HOLDOVER_MASK	0x20
+#define SEC_LOST_MASK	0x40
+#define PRI_LOST_MASK	0x80
+
+/* INTERRUPT CAUSE DEFINE */
+
+#define PRI_LOS_01_MASK		0x01
+#define PRI_LOS_10_MASK		0x02
+
+#define SEC_LOS_01_MASK		0x04
+#define SEC_LOS_10_MASK		0x08
+
+#define HOLDOVER_01_MASK	0x10
+#define HOLDOVER_10_MASK	0x20
+
+#define UNLOCK_01_MASK		0x40
+#define UNLOCK_10_MASK		0x80
+
+struct tlclk_alarms {
+	__u32 lost_clocks;
+	__u32 lost_primary_clock;
+	__u32 lost_secondary_clock;
+	__u32 primary_clock_back;
+	__u32 secondary_clock_back;
+	__u32 switchover_primary;
+	__u32 switchover_secondary;
+	__u32 pll_holdover;
+	__u32 pll_end_holdover;
+	__u32 pll_lost_sync;
+	__u32 pll_sync;
+};
+/* Telecom clock I/O register definition */
+#define TLCLK_BASE 0xa08
+#define TLCLK_REG0 TLCLK_BASE
+#define TLCLK_REG1 (TLCLK_BASE+1)
+#define TLCLK_REG2 (TLCLK_BASE+2)
+#define TLCLK_REG3 (TLCLK_BASE+3)
+#define TLCLK_REG4 (TLCLK_BASE+4)
+#define TLCLK_REG5 (TLCLK_BASE+5)
+#define TLCLK_REG6 (TLCLK_BASE+6)
+#define TLCLK_REG7 (TLCLK_BASE+7)
+
+#define SET_PORT_BITS(port, mask, val) outb(((inb(port) & mask) | val), port)
+
+/* 0 = Dynamic allocation of the major device number */
+#define TLCLK_MAJOR 0
+
+/* sysfs interface definition:
+Upon loading the driver will create a sysfs directory under
+/sys/devices/platform/telco_clock.
+
+This directory exports the following interfaces.  There operation is
+documented in the MCPBL0010 TPS under the Telecom Clock API section, 11.4.
+alarms				:
+current_ref			:
+enable_clk3a_output		:
+enable_clk3b_output		:
+enable_clka0_output		:
+enable_clka1_output		:
+enable_clkb0_output		:
+enable_clkb1_output		:
+filter_select			:
+hardware_switching		:
+hardware_switching_mode		:
+interrupt_switch		:
+mode_select			:
+refalign			:
+reset				:
+select_amcb1_transmit_clock	:
+select_amcb2_transmit_clock	:
+select_redundant_clock		:
+select_ref_frequency		:
+test_mode			:
+
+All sysfs interfaces are integers in hex format, i.e echo 99 > refalign
+has the same effect as echo 0x99 > refalign.
+*/
+
+static unsigned int telclk_interrupt;
+
+static int int_events;		/* Event that generate a interrupt */
+static int got_event;		/* if events processing have been done */
+
+static void switchover_timeout(unsigned long data);
+static struct timer_list switchover_timer =
+	TIMER_INITIALIZER(switchover_timeout , 0, 0);
+
+static struct tlclk_alarms *alarm_events;
+
+static DEFINE_SPINLOCK(event_lock);
+
+static int tlclk_major = TLCLK_MAJOR;
+
+static irqreturn_t tlclk_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+
+static DECLARE_WAIT_QUEUE_HEAD(wq);
+
+static int tlclk_open(struct inode *inode, struct file *filp)
+{
+	int result;
+
+	/* Make sure there is no interrupt pending while
+	 * initialising interrupt handler */
+	inb(TLCLK_REG6);
+
+	/* This device is wired through the FPGA IO space of the ATCA blade
+	 * we can't share this IRQ */
+	result = request_irq(telclk_interrupt, &tlclk_interrupt,
+			     SA_INTERRUPT, "telco_clock", tlclk_interrupt);
+	if (result == -EBUSY) {
+		printk(KERN_ERR "telco_clock: Interrupt can't be reserved!\n");
+		return -EBUSY;
+	}
+	inb(TLCLK_REG6);	/* Clear interrupt events */
+
+	return 0;
+}
+
+static int tlclk_release(struct inode *inode, struct file *filp)
+{
+	free_irq(telclk_interrupt, tlclk_interrupt);
+
+	return 0;
+}
+
+ssize_t tlclk_read(struct file *filp, char __user *buf, size_t count,
+		loff_t *f_pos)
+{
+	if (count < sizeof(struct tlclk_alarms))
+		return -EIO;
+
+	wait_event_interruptible(wq, got_event);
+	if (copy_to_user(buf, alarm_events, sizeof(struct tlclk_alarms)))
+		return -EFAULT;
+
+	memset(alarm_events, 0, sizeof(struct tlclk_alarms));
+	got_event = 0;
+
+	return  sizeof(struct tlclk_alarms);
+}
+
+ssize_t tlclk_write(struct file *filp, const char __user *buf, size_t count,
+	    loff_t *f_pos)
+{
+	return 0;
+}
+
+static struct file_operations tlclk_fops = {
+	.read = tlclk_read,
+	.write = tlclk_write,
+	.open = tlclk_open,
+	.release = tlclk_release,
+
+};
+
+
+static ssize_t show_current_ref(struct device *d,
+		struct device_attribute *attr, char *buf)
+{
+	unsigned long ret_val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+	ret_val = ((inb(TLCLK_REG1) & 0x08) >> 3);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return sprintf(buf, "0x%lX\n", ret_val);
+}
+
+static DEVICE_ATTR(current_ref, S_IRUGO, show_current_ref, NULL);
+
+
+static ssize_t show_interrupt_switch(struct device *d,
+		struct device_attribute *attr, char *buf)
+{
+	unsigned long ret_val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+	ret_val = inb(TLCLK_REG6);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return sprintf(buf, "0x%lX\n", ret_val);
+}
+
+static DEVICE_ATTR(interrupt_switch, S_IRUGO,
+		show_interrupt_switch, NULL);
+
+static ssize_t show_alarms(struct device *d,
+		struct device_attribute *attr,  char *buf)
+{
+	unsigned long ret_val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+	ret_val = (inb(TLCLK_REG2) & 0xf0);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return sprintf(buf, "0x%lX\n", ret_val);
+}
+
+static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
+
+static ssize_t store_enable_clk3b_output(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, ": tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG3, 0x7f, val << 7);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(enable_clk3b_output, S_IWUGO, NULL,
+		store_enable_clk3b_output);
+
+static ssize_t store_enable_clk3a_output(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG3, 0xbf, val << 6);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(enable_clk3a_output, S_IWUGO, NULL,
+		store_enable_clk3a_output);
+
+static ssize_t store_enable_clkb1_output(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG2, 0xf7, val << 3);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(enable_clkb1_output, S_IWUGO, NULL,
+		store_enable_clkb1_output);
+
+
+static ssize_t store_enable_clka1_output(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG2, 0xfb, val << 2);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(enable_clka1_output, S_IWUGO, NULL,
+		store_enable_clka1_output);
+
+static ssize_t store_enable_clkb0_output(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG2, 0xfd, val << 1);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(enable_clkb0_output, S_IWUGO, NULL,
+		store_enable_clkb0_output);
+
+static ssize_t store_enable_clka0_output(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG2, 0xfe, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(enable_clka0_output, S_IWUGO, NULL,
+		store_enable_clka0_output);
+
+static ssize_t store_test_mode(struct device *d,
+		struct device_attribute *attr,  const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG4, 0xfd, 2);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(test_mode, S_IWUGO, NULL, store_test_mode);
+
+static ssize_t store_select_amcb2_transmit_clock(struct device *d,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x28);
+			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
+		} else if (val >= CLK_8_592MHz) {
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x38);
+			switch (val) {
+			case CLK_8_592MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
+				break;
+			case CLK_11_184MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
+				break;
+			case CLK_34_368MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
+				break;
+			case CLK_44_736MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
+				break;
+			}
+		} else
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, val << 3);
+
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(select_amcb2_transmit_clock, S_IWUGO, NULL,
+	store_select_amcb2_transmit_clock);
+
+static ssize_t store_select_amcb1_transmit_clock(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x5);
+			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
+		} else if (val >= CLK_8_592MHz) {
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x7);
+			switch (val) {
+			case CLK_8_592MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
+				break;
+			case CLK_11_184MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
+				break;
+			case CLK_34_368MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
+				break;
+			case CLK_44_736MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
+				break;
+			}
+		} else
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(select_amcb1_transmit_clock, S_IWUGO, NULL,
+		store_select_amcb1_transmit_clock);
+
+static ssize_t store_select_redundant_clock(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG1, 0xfe, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(select_redundant_clock, S_IWUGO, NULL,
+		store_select_redundant_clock);
+
+static ssize_t store_select_ref_frequency(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG1, 0xfd, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(select_ref_frequency, S_IWUGO, NULL,
+		store_select_ref_frequency);
+
+static ssize_t store_filter_select(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xfb, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(filter_select, S_IWUGO, NULL, store_filter_select);
+
+static ssize_t store_hardware_switching_mode(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xbf, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(hardware_switching_mode, S_IWUGO, NULL,
+		store_hardware_switching_mode);
+
+static ssize_t store_hardware_switching(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0x7f, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(hardware_switching, S_IWUGO, NULL,
+		store_hardware_switching);
+
+static ssize_t store_refalign (struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
+	udelay(2);
+	SET_PORT_BITS(TLCLK_REG0, 0xf7, 0x08);
+	udelay(2);
+	SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(refalign, S_IWUGO, NULL, store_refalign);
+
+static ssize_t store_mode_select (struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xcf, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(mode_select, S_IWUGO, NULL, store_mode_select);
+
+static ssize_t store_reset (struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG4, 0xfd, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(reset, S_IWUGO, NULL, store_reset);
+
+static struct attribute *tlclk_sysfs_entries[] = {
+	&dev_attr_current_ref.attr,
+	&dev_attr_interrupt_switch.attr,
+	&dev_attr_alarms.attr,
+	&dev_attr_enable_clk3a_output.attr,
+	&dev_attr_enable_clk3b_output.attr,
+	&dev_attr_enable_clkb1_output.attr,
+	&dev_attr_enable_clka1_output.attr,
+	&dev_attr_enable_clkb0_output.attr,
+	&dev_attr_enable_clka0_output.attr,
+	&dev_attr_test_mode.attr,
+	&dev_attr_select_amcb1_transmit_clock.attr,
+	&dev_attr_select_amcb2_transmit_clock.attr,
+	&dev_attr_select_redundant_clock.attr,
+	&dev_attr_select_ref_frequency.attr,
+	&dev_attr_filter_select.attr,
+	&dev_attr_hardware_switching_mode.attr,
+	&dev_attr_hardware_switching.attr,
+	&dev_attr_refalign.attr,
+	&dev_attr_mode_select.attr,
+	&dev_attr_reset.attr,
+	NULL
+};
+
+static struct attribute_group tlclk_attribute_group = {
+	.name = NULL,		/* put in device directory */
+	.attrs = tlclk_sysfs_entries,
+};
+
+static struct platform_device *tlclk_device;
+
+static int __init tlclk_init(void)
+{
+	int ret;
+
+	ret = register_chrdev(tlclk_major, "telco_clock", &tlclk_fops);
+	if (ret < 0) {
+		printk(KERN_ERR "telco_clock: can't get major! %d\n", tlclk_major);
+		return ret;
+	}
+	alarm_events = kzalloc( sizeof(struct tlclk_alarms), GFP_KERNEL);
+	if (!alarm_events)
+		goto out1;
+
+	/* Read telecom clock IRQ number (Set by BIOS) */
+	if (!request_region(TLCLK_BASE, 8, "telco_clock")) {
+		printk(KERN_ERR "tlclk: request_region failed! 0x%X\n",
+			TLCLK_BASE);
+		ret = -EBUSY;
+		goto out2;
+	}
+	telclk_interrupt = (inb(TLCLK_REG7) & 0x0f);
+
+	if (0x0F == telclk_interrupt ) { /* not MCPBL0010 ? */
+		printk(KERN_ERR "telclk_interrup = 0x%x non-mcpbl0010 hw\n",
+			telclk_interrupt);
+		ret = -ENXIO;
+		goto out3;
+	}
+
+	init_timer(&switchover_timer);
+
+	tlclk_device = platform_device_register_simple("telco_clock",
+				-1, NULL, 0);
+	if (!tlclk_device) {
+		printk(KERN_ERR " platform_device_register retruns 0x%X\n",
+			(unsigned int) tlclk_device);
+		ret = -EBUSY;
+		goto out3;
+	}
+	
+	ret = sysfs_create_group(&tlclk_device->dev.kobj,
+			&tlclk_attribute_group);
+	if (ret) {
+		printk(KERN_ERR "failed to create sysfs device attributes\n");
+		sysfs_remove_group(&tlclk_device->dev.kobj,
+			&tlclk_attribute_group);
+		goto out3;
+	}
+
+	return 0;
+out3:
+	release_region(TLCLK_BASE, 8);
+out2:
+	kfree(alarm_events);
+out1:
+	return ret;
+}
+
+static void __exit tlclk_cleanup(void)
+{
+	sysfs_remove_group(&tlclk_device->dev.kobj, &tlclk_attribute_group);
+	platform_device_unregister(tlclk_device);
+	unregister_chrdev(tlclk_major, "telco_clock");
+
+	release_region(TLCLK_BASE, 8);
+	del_timer_sync(&switchover_timer);
+	kfree(alarm_events);
+
+}
+
+static void switchover_timeout(unsigned long data)
+{
+	if ((data & 1)) {
+		if ((inb(TLCLK_REG1) & 0x08) != (data & 0x08))
+			alarm_events->switchover_primary++;
+	} else {
+		if ((inb(TLCLK_REG1) & 0x08) != (data & 0x08))
+			alarm_events->switchover_secondary++;
+	}
+
+	/* Alarm processing is done, wake up read task */
+	del_timer(&switchover_timer);
+	got_event = 1;
+	wake_up(&wq);
+}
+
+static irqreturn_t tlclk_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+	/* Read and clear interrupt events */
+	int_events = inb(TLCLK_REG6);
+
+	/* Primary_Los changed from 0 to 1 ? */
+	if (int_events & PRI_LOS_01_MASK) {
+		if (inb(TLCLK_REG2) & SEC_LOST_MASK)
+			alarm_events->lost_clocks++;
+		else
+			alarm_events->lost_primary_clock++;
+	}
+
+	/* Primary_Los changed from 1 to 0 ? */
+	if (int_events & PRI_LOS_10_MASK) {
+		alarm_events->primary_clock_back++;
+		SET_PORT_BITS(TLCLK_REG1, 0xFE, 1);
+	}
+	/* Secondary_Los changed from 0 to 1 ? */
+	if (int_events & SEC_LOS_01_MASK) {
+		if (inb(TLCLK_REG2) & PRI_LOST_MASK)
+			alarm_events->lost_clocks++;
+		else
+			alarm_events->lost_secondary_clock++;
+	}
+	/* Secondary_Los changed from 1 to 0 ? */
+	if (int_events & SEC_LOS_10_MASK) {
+		alarm_events->secondary_clock_back++;
+		SET_PORT_BITS(TLCLK_REG1, 0xFE, 0);
+	}
+	if (int_events & HOLDOVER_10_MASK)
+		alarm_events->pll_end_holdover++;
+
+	if (int_events & UNLOCK_01_MASK)
+		alarm_events->pll_lost_sync++;
+
+	if (int_events & UNLOCK_10_MASK)
+		alarm_events->pll_sync++;
+
+	/* Holdover changed from 0 to 1 ? */
+	if (int_events & HOLDOVER_01_MASK) {
+		alarm_events->pll_holdover++;
+
+		/* TIMEOUT in ~10ms */
+		switchover_timer.expires = jiffies + msecs_to_jiffies(10);
+		switchover_timer.data = inb(TLCLK_REG1);
+		add_timer(&switchover_timer);
+	} else {
+		got_event = 1;
+		wake_up(&wq);
+	}
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+module_init(tlclk_init);
+module_exit(tlclk_cleanup);
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/MAINTAINERS linux-2.6.14-rc2-mm2-tlclk/MAINTAINERS
--- linux-2.6.14-rc2-mm2/MAINTAINERS	2005-10-04 14:55:18.000000000 -0700
+++ linux-2.6.14-rc2-mm2-tlclk/MAINTAINERS	2005-10-12 10:02:13.000000000 -0700
@@ -2319,6 +2319,11 @@
 L:	tpmdd-devel@lists.sourceforge.net
 S:	Maintained
 
+Telecom Clock Driver for MCPL0010
+P: Mark Gross
+M: mark.gross@intel.com
+S: Supported
+
 TENSILICA XTENSA PORT (xtensa):
 P:	Chris Zankel
 M:	chris@zankel.net

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade
  2005-10-12 22:30       ` Mark Gross
@ 2005-10-12 22:49         ` Greg KH
  2005-10-12 23:36           ` Mark Gross
  0 siblings, 1 reply; 18+ messages in thread
From: Greg KH @ 2005-10-12 22:49 UTC (permalink / raw)
  To: Mark Gross; +Cc: akpm, linux-kernel, Sebastien.Bouchard, mark.gross

On Wed, Oct 12, 2005 at 03:30:00PM -0700, Mark Gross wrote:
> 
> Most significantly I moved the driver from a misc_device to a
> platform_device.

You should still use the misc_device to register your file ops, just
stick with the platform device for the sysfs stuff.  You need that
misc_device in order to work properly with udev.  Have you tested this
code on a udev-only system?

Other than that, it looks very good.

Oh, one minor thing:

> +#include <linux/sysfs.h>
> +#define DEBUG
> +#include <linux/device.h>

Do you always want DEBUG to be enabled?  :)

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade
  2005-10-12 22:49         ` Greg KH
@ 2005-10-12 23:36           ` Mark Gross
  2005-10-13  1:14             ` Greg KH
  0 siblings, 1 reply; 18+ messages in thread
From: Mark Gross @ 2005-10-12 23:36 UTC (permalink / raw)
  To: Greg KH; +Cc: akpm, linux-kernel, Sebastien.Bouchard, mark.gross

[-- Attachment #1: Type: text/plain, Size: 1062 bytes --]

On Wednesday 12 October 2005 15:49, Greg KH wrote:
> On Wed, Oct 12, 2005 at 03:30:00PM -0700, Mark Gross wrote:
> > 
> > Most significantly I moved the driver from a misc_device to a
> > platform_device.
> 
> You should still use the misc_device to register your file ops, just
> stick with the platform device for the sysfs stuff.  You need that
> misc_device in order to work properly with udev.  Have you tested this
> code on a udev-only system?

I shall tomorrow.  Most of the test cases are exercised over the sysfs 
interface, the use of the fops are not used very often.

> 
> Other than that, it looks very good.
> 
> Oh, one minor thing:
> 
> > +#include <linux/sysfs.h>
> > +#define DEBUG
> > +#include <linux/device.h>
> 
> Do you always want DEBUG to be enabled?  :)

No, but I'm glad I tested that otherwise the my problem with using dev_dbg 
with the kobj->dev devices I got from the misc_device class could have gotten 
by me.

> 
> thanks,
> 

Thank you.

-- 
--mgross
BTW: This may or may not be the opinion of my employer, more likely not.  

[-- Attachment #2: tlclk-2.6.14-rc2-mm2.patch --]
[-- Type: text/x-diff, Size: 25841 bytes --]

diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/Kconfig linux-2.6.14-rc2-mm2-tlclk/drivers/char/Kconfig
--- linux-2.6.14-rc2-mm2/drivers/char/Kconfig	2005-10-04 14:55:15.000000000 -0700
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/Kconfig	2005-10-12 15:23:23.000000000 -0700
@@ -1001,5 +1001,17 @@
 
 source "drivers/char/tpm/Kconfig"
 
+config TELCLOCK
+	tristate "Telecom clock driver for MPBL0010 ATCA SBC"
+	depends on EXPERIMENTAL
+	default n
+	help
+	  The telecom clock device is specific to the MPBL0010 ATCA computer and
+	  allows direct userspace access to the configuration of the telecom clock
+	  configuration settings.  This device is used for hardware synchronization
+	  across the ATCA backplane fabric.  Upon loading, the driver exports a
+	  sysfs directory, /sys/devices/platform/telco_clock, with a number of
+	  files for controlling the behavior of this hardware.
+
 endmenu
 
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/Makefile linux-2.6.14-rc2-mm2-tlclk/drivers/char/Makefile
--- linux-2.6.14-rc2-mm2/drivers/char/Makefile	2005-10-04 14:55:15.000000000 -0700
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/Makefile	2005-10-12 10:02:13.000000000 -0700
@@ -82,6 +82,7 @@
 obj-$(CONFIG_SCx200_GPIO) += scx200_gpio.o
 obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o
 obj-$(CONFIG_TANBAC_TB0219) += tb0219.o
+obj-$(CONFIG_TELCLOCK) += tlclk.o
 
 obj-$(CONFIG_SPEAKUP) += speakup/
 obj-$(CONFIG_WATCHDOG)	+= watchdog/
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/tlclk.c linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.c
--- linux-2.6.14-rc2-mm2/drivers/char/tlclk.c	1969-12-31 16:00:00.000000000 -0800
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.c	2005-10-12 16:25:57.000000000 -0700
@@ -0,0 +1,896 @@
+/*
+ * Telecom Clock driver for Intel NetStructure(tm) MPCBL0010
+ *
+ * Copyright (C) 2005 Kontron Canada
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT.  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Send feedback to <sebastien.bouchard@ca.kontron.com> and the current
+ * Maintainer  <mark.gross@intel.com>
+ *
+ * Description : This is the TELECOM CLOCK module driver for the ATCA
+ * MPCBL0010 ATCA computer.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>	/* printk() */
+#include <linux/fs.h>		/* everything... */
+#include <linux/errno.h>	/* error codes */
+#include <linux/delay.h>	/* udelay */
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/sysfs.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+#include <asm/io.h>		/* inb/outb */
+#include <asm/uaccess.h>
+
+MODULE_AUTHOR("Sebastien Bouchard <sebastien.bouchard@ca.kontron.com>");
+MODULE_LICENSE("GPL");
+
+/*Hardware Reset of the PLL */
+#define RESET_ON	0x00
+#define RESET_OFF	0x01
+
+/* MODE SELECT */
+#define NORMAL_MODE 	0x00
+#define HOLDOVER_MODE	0x10
+#define FREERUN_MODE	0x20
+
+/* FILTER SELECT */
+#define FILTER_6HZ	0x04
+#define FILTER_12HZ	0x00
+
+/* SELECT REFERENCE FREQUENCY */
+#define REF_CLK1_8kHz		0x00
+#define REF_CLK2_19_44MHz	0x02
+
+/* Select primary or secondary redundant clock */
+#define PRIMARY_CLOCK	0x00
+#define SECONDARY_CLOCK	0x01
+
+/* CLOCK TRANSMISSION DEFINE */
+#define CLK_8kHz	0xff
+#define CLK_16_384MHz	0xfb
+
+#define CLK_1_544MHz	0x00
+#define CLK_2_048MHz	0x01
+#define CLK_4_096MHz	0x02
+#define CLK_6_312MHz	0x03
+#define CLK_8_192MHz	0x04
+#define CLK_19_440MHz	0x06
+
+#define CLK_8_592MHz	0x08
+#define CLK_11_184MHz	0x09
+#define CLK_34_368MHz	0x0b
+#define CLK_44_736MHz	0x0a
+
+/* RECEIVED REFERENCE */
+#define AMC_B1 0
+#define AMC_B2 1
+
+/* HARDWARE SWITCHING DEFINE */
+#define HW_ENABLE	0x80
+#define HW_DISABLE	0x00
+
+/* HARDWARE SWITCHING MODE DEFINE */
+#define PLL_HOLDOVER	0x40
+#define LOST_CLOCK	0x00
+
+/* ALARMS DEFINE */
+#define UNLOCK_MASK	0x10
+#define HOLDOVER_MASK	0x20
+#define SEC_LOST_MASK	0x40
+#define PRI_LOST_MASK	0x80
+
+/* INTERRUPT CAUSE DEFINE */
+
+#define PRI_LOS_01_MASK		0x01
+#define PRI_LOS_10_MASK		0x02
+
+#define SEC_LOS_01_MASK		0x04
+#define SEC_LOS_10_MASK		0x08
+
+#define HOLDOVER_01_MASK	0x10
+#define HOLDOVER_10_MASK	0x20
+
+#define UNLOCK_01_MASK		0x40
+#define UNLOCK_10_MASK		0x80
+
+struct tlclk_alarms {
+	__u32 lost_clocks;
+	__u32 lost_primary_clock;
+	__u32 lost_secondary_clock;
+	__u32 primary_clock_back;
+	__u32 secondary_clock_back;
+	__u32 switchover_primary;
+	__u32 switchover_secondary;
+	__u32 pll_holdover;
+	__u32 pll_end_holdover;
+	__u32 pll_lost_sync;
+	__u32 pll_sync;
+};
+/* Telecom clock I/O register definition */
+#define TLCLK_BASE 0xa08
+#define TLCLK_REG0 TLCLK_BASE
+#define TLCLK_REG1 (TLCLK_BASE+1)
+#define TLCLK_REG2 (TLCLK_BASE+2)
+#define TLCLK_REG3 (TLCLK_BASE+3)
+#define TLCLK_REG4 (TLCLK_BASE+4)
+#define TLCLK_REG5 (TLCLK_BASE+5)
+#define TLCLK_REG6 (TLCLK_BASE+6)
+#define TLCLK_REG7 (TLCLK_BASE+7)
+
+#define SET_PORT_BITS(port, mask, val) outb(((inb(port) & mask) | val), port)
+
+/* 0 = Dynamic allocation of the major device number */
+#define TLCLK_MAJOR 0
+
+/* sysfs interface definition:
+Upon loading the driver will create a sysfs directory under
+/sys/devices/platform/telco_clock.
+
+This directory exports the following interfaces.  There operation is
+documented in the MCPBL0010 TPS under the Telecom Clock API section, 11.4.
+alarms				:
+current_ref			:
+enable_clk3a_output		:
+enable_clk3b_output		:
+enable_clka0_output		:
+enable_clka1_output		:
+enable_clkb0_output		:
+enable_clkb1_output		:
+filter_select			:
+hardware_switching		:
+hardware_switching_mode		:
+interrupt_switch		:
+mode_select			:
+refalign			:
+reset				:
+select_amcb1_transmit_clock	:
+select_amcb2_transmit_clock	:
+select_redundant_clock		:
+select_ref_frequency		:
+test_mode			:
+
+All sysfs interfaces are integers in hex format, i.e echo 99 > refalign
+has the same effect as echo 0x99 > refalign.
+*/
+
+static unsigned int telclk_interrupt;
+
+static int int_events;		/* Event that generate a interrupt */
+static int got_event;		/* if events processing have been done */
+
+static void switchover_timeout(unsigned long data);
+static struct timer_list switchover_timer =
+	TIMER_INITIALIZER(switchover_timeout , 0, 0);
+
+static struct tlclk_alarms *alarm_events;
+
+static DEFINE_SPINLOCK(event_lock);
+
+static int tlclk_major = TLCLK_MAJOR;
+
+static irqreturn_t tlclk_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+
+static DECLARE_WAIT_QUEUE_HEAD(wq);
+
+static int tlclk_open(struct inode *inode, struct file *filp)
+{
+	int result;
+
+	/* Make sure there is no interrupt pending while
+	 * initialising interrupt handler */
+	inb(TLCLK_REG6);
+
+	/* This device is wired through the FPGA IO space of the ATCA blade
+	 * we can't share this IRQ */
+	result = request_irq(telclk_interrupt, &tlclk_interrupt,
+			     SA_INTERRUPT, "telco_clock", tlclk_interrupt);
+	if (result == -EBUSY) {
+		printk(KERN_ERR "telco_clock: Interrupt can't be reserved!\n");
+		return -EBUSY;
+	}
+	inb(TLCLK_REG6);	/* Clear interrupt events */
+
+	return 0;
+}
+
+static int tlclk_release(struct inode *inode, struct file *filp)
+{
+	free_irq(telclk_interrupt, tlclk_interrupt);
+
+	return 0;
+}
+
+ssize_t tlclk_read(struct file *filp, char __user *buf, size_t count,
+		loff_t *f_pos)
+{
+	if (count < sizeof(struct tlclk_alarms))
+		return -EIO;
+
+	wait_event_interruptible(wq, got_event);
+	if (copy_to_user(buf, alarm_events, sizeof(struct tlclk_alarms)))
+		return -EFAULT;
+
+	memset(alarm_events, 0, sizeof(struct tlclk_alarms));
+	got_event = 0;
+
+	return  sizeof(struct tlclk_alarms);
+}
+
+ssize_t tlclk_write(struct file *filp, const char __user *buf, size_t count,
+	    loff_t *f_pos)
+{
+	return 0;
+}
+
+static struct file_operations tlclk_fops = {
+	.read = tlclk_read,
+	.write = tlclk_write,
+	.open = tlclk_open,
+	.release = tlclk_release,
+
+};
+
+static struct miscdevice tlclk_miscdev = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "tlclk",
+	.fops = &tlclk_fops,
+};
+
+static ssize_t show_current_ref(struct device *d,
+		struct device_attribute *attr, char *buf)
+{
+	unsigned long ret_val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+	ret_val = ((inb(TLCLK_REG1) & 0x08) >> 3);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return sprintf(buf, "0x%lX\n", ret_val);
+}
+
+static DEVICE_ATTR(current_ref, S_IRUGO, show_current_ref, NULL);
+
+
+static ssize_t show_interrupt_switch(struct device *d,
+		struct device_attribute *attr, char *buf)
+{
+	unsigned long ret_val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+	ret_val = inb(TLCLK_REG6);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return sprintf(buf, "0x%lX\n", ret_val);
+}
+
+static DEVICE_ATTR(interrupt_switch, S_IRUGO,
+		show_interrupt_switch, NULL);
+
+static ssize_t show_alarms(struct device *d,
+		struct device_attribute *attr,  char *buf)
+{
+	unsigned long ret_val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+	ret_val = (inb(TLCLK_REG2) & 0xf0);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return sprintf(buf, "0x%lX\n", ret_val);
+}
+
+static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
+
+static ssize_t store_enable_clk3b_output(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, ": tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG3, 0x7f, val << 7);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(enable_clk3b_output, S_IWUGO, NULL,
+		store_enable_clk3b_output);
+
+static ssize_t store_enable_clk3a_output(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG3, 0xbf, val << 6);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(enable_clk3a_output, S_IWUGO, NULL,
+		store_enable_clk3a_output);
+
+static ssize_t store_enable_clkb1_output(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG2, 0xf7, val << 3);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(enable_clkb1_output, S_IWUGO, NULL,
+		store_enable_clkb1_output);
+
+
+static ssize_t store_enable_clka1_output(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG2, 0xfb, val << 2);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(enable_clka1_output, S_IWUGO, NULL,
+		store_enable_clka1_output);
+
+static ssize_t store_enable_clkb0_output(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG2, 0xfd, val << 1);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(enable_clkb0_output, S_IWUGO, NULL,
+		store_enable_clkb0_output);
+
+static ssize_t store_enable_clka0_output(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG2, 0xfe, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(enable_clka0_output, S_IWUGO, NULL,
+		store_enable_clka0_output);
+
+static ssize_t store_test_mode(struct device *d,
+		struct device_attribute *attr,  const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG4, 0xfd, 2);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(test_mode, S_IWUGO, NULL, store_test_mode);
+
+static ssize_t store_select_amcb2_transmit_clock(struct device *d,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x28);
+			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
+		} else if (val >= CLK_8_592MHz) {
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x38);
+			switch (val) {
+			case CLK_8_592MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
+				break;
+			case CLK_11_184MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
+				break;
+			case CLK_34_368MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
+				break;
+			case CLK_44_736MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
+				break;
+			}
+		} else
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, val << 3);
+
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(select_amcb2_transmit_clock, S_IWUGO, NULL,
+	store_select_amcb2_transmit_clock);
+
+static ssize_t store_select_amcb1_transmit_clock(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x5);
+			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
+		} else if (val >= CLK_8_592MHz) {
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x7);
+			switch (val) {
+			case CLK_8_592MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
+				break;
+			case CLK_11_184MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
+				break;
+			case CLK_34_368MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
+				break;
+			case CLK_44_736MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
+				break;
+			}
+		} else
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(select_amcb1_transmit_clock, S_IWUGO, NULL,
+		store_select_amcb1_transmit_clock);
+
+static ssize_t store_select_redundant_clock(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG1, 0xfe, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(select_redundant_clock, S_IWUGO, NULL,
+		store_select_redundant_clock);
+
+static ssize_t store_select_ref_frequency(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG1, 0xfd, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(select_ref_frequency, S_IWUGO, NULL,
+		store_select_ref_frequency);
+
+static ssize_t store_filter_select(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xfb, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(filter_select, S_IWUGO, NULL, store_filter_select);
+
+static ssize_t store_hardware_switching_mode(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xbf, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(hardware_switching_mode, S_IWUGO, NULL,
+		store_hardware_switching_mode);
+
+static ssize_t store_hardware_switching(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0x7f, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(hardware_switching, S_IWUGO, NULL,
+		store_hardware_switching);
+
+static ssize_t store_refalign (struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
+	udelay(2);
+	SET_PORT_BITS(TLCLK_REG0, 0xf7, 0x08);
+	udelay(2);
+	SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(refalign, S_IWUGO, NULL, store_refalign);
+
+static ssize_t store_mode_select (struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xcf, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(mode_select, S_IWUGO, NULL, store_mode_select);
+
+static ssize_t store_reset (struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG4, 0xfd, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(reset, S_IWUGO, NULL, store_reset);
+
+static struct attribute *tlclk_sysfs_entries[] = {
+	&dev_attr_current_ref.attr,
+	&dev_attr_interrupt_switch.attr,
+	&dev_attr_alarms.attr,
+	&dev_attr_enable_clk3a_output.attr,
+	&dev_attr_enable_clk3b_output.attr,
+	&dev_attr_enable_clkb1_output.attr,
+	&dev_attr_enable_clka1_output.attr,
+	&dev_attr_enable_clkb0_output.attr,
+	&dev_attr_enable_clka0_output.attr,
+	&dev_attr_test_mode.attr,
+	&dev_attr_select_amcb1_transmit_clock.attr,
+	&dev_attr_select_amcb2_transmit_clock.attr,
+	&dev_attr_select_redundant_clock.attr,
+	&dev_attr_select_ref_frequency.attr,
+	&dev_attr_filter_select.attr,
+	&dev_attr_hardware_switching_mode.attr,
+	&dev_attr_hardware_switching.attr,
+	&dev_attr_refalign.attr,
+	&dev_attr_mode_select.attr,
+	&dev_attr_reset.attr,
+	NULL
+};
+
+static struct attribute_group tlclk_attribute_group = {
+	.name = NULL,		/* put in device directory */
+	.attrs = tlclk_sysfs_entries,
+};
+
+static struct platform_device *tlclk_device;
+
+static int __init tlclk_init(void)
+{
+	int ret;
+
+	ret = register_chrdev(tlclk_major, "telco_clock", &tlclk_fops);
+	if (ret < 0) {
+		printk(KERN_ERR "telco_clock: can't get major! %d\n", tlclk_major);
+		return ret;
+	}
+	alarm_events = kzalloc( sizeof(struct tlclk_alarms), GFP_KERNEL);
+	if (!alarm_events)
+		goto out1;
+
+	/* Read telecom clock IRQ number (Set by BIOS) */
+	if (!request_region(TLCLK_BASE, 8, "telco_clock")) {
+		printk(KERN_ERR "tlclk: request_region failed! 0x%X\n",
+			TLCLK_BASE);
+		ret = -EBUSY;
+		goto out2;
+	}
+	telclk_interrupt = (inb(TLCLK_REG7) & 0x0f);
+
+	if (0x0F == telclk_interrupt ) { /* not MCPBL0010 ? */
+		printk(KERN_ERR "telclk_interrup = 0x%x non-mcpbl0010 hw\n",
+			telclk_interrupt);
+		ret = -ENXIO;
+		goto out3;
+	}
+
+	init_timer(&switchover_timer);
+
+	ret = misc_register(&tlclk_miscdev);
+	if (ret < 0) {
+		printk(KERN_ERR " misc_register retruns %d\n", ret);
+		ret = -EBUSY;
+		goto out3;
+	}
+
+	tlclk_device = platform_device_register_simple("telco_clock",
+				-1, NULL, 0);
+	if (!tlclk_device) {
+		printk(KERN_ERR " platform_device_register retruns 0x%X\n",
+			(unsigned int) tlclk_device);
+		ret = -EBUSY;
+		goto out4;
+	}
+	
+	ret = sysfs_create_group(&tlclk_device->dev.kobj,
+			&tlclk_attribute_group);
+	if (ret) {
+		printk(KERN_ERR "failed to create sysfs device attributes\n");
+		sysfs_remove_group(&tlclk_device->dev.kobj,
+			&tlclk_attribute_group);
+		goto out5;
+	}
+
+	return 0;
+out5:
+	platform_device_unregister(tlclk_device);
+out4:
+	misc_deregister(&tlclk_miscdev);
+out3:
+	release_region(TLCLK_BASE, 8);
+out2:
+	kfree(alarm_events);
+out1:
+	unregister_chrdev(tlclk_major, "telco_clock");
+	return ret;
+}
+
+static void __exit tlclk_cleanup(void)
+{
+	sysfs_remove_group(&tlclk_device->dev.kobj, &tlclk_attribute_group);
+	platform_device_unregister(tlclk_device);
+	misc_deregister(&tlclk_miscdev);
+	unregister_chrdev(tlclk_major, "telco_clock");
+
+	release_region(TLCLK_BASE, 8);
+	del_timer_sync(&switchover_timer);
+	kfree(alarm_events);
+
+}
+
+static void switchover_timeout(unsigned long data)
+{
+	if ((data & 1)) {
+		if ((inb(TLCLK_REG1) & 0x08) != (data & 0x08))
+			alarm_events->switchover_primary++;
+	} else {
+		if ((inb(TLCLK_REG1) & 0x08) != (data & 0x08))
+			alarm_events->switchover_secondary++;
+	}
+
+	/* Alarm processing is done, wake up read task */
+	del_timer(&switchover_timer);
+	got_event = 1;
+	wake_up(&wq);
+}
+
+static irqreturn_t tlclk_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+	/* Read and clear interrupt events */
+	int_events = inb(TLCLK_REG6);
+
+	/* Primary_Los changed from 0 to 1 ? */
+	if (int_events & PRI_LOS_01_MASK) {
+		if (inb(TLCLK_REG2) & SEC_LOST_MASK)
+			alarm_events->lost_clocks++;
+		else
+			alarm_events->lost_primary_clock++;
+	}
+
+	/* Primary_Los changed from 1 to 0 ? */
+	if (int_events & PRI_LOS_10_MASK) {
+		alarm_events->primary_clock_back++;
+		SET_PORT_BITS(TLCLK_REG1, 0xFE, 1);
+	}
+	/* Secondary_Los changed from 0 to 1 ? */
+	if (int_events & SEC_LOS_01_MASK) {
+		if (inb(TLCLK_REG2) & PRI_LOST_MASK)
+			alarm_events->lost_clocks++;
+		else
+			alarm_events->lost_secondary_clock++;
+	}
+	/* Secondary_Los changed from 1 to 0 ? */
+	if (int_events & SEC_LOS_10_MASK) {
+		alarm_events->secondary_clock_back++;
+		SET_PORT_BITS(TLCLK_REG1, 0xFE, 0);
+	}
+	if (int_events & HOLDOVER_10_MASK)
+		alarm_events->pll_end_holdover++;
+
+	if (int_events & UNLOCK_01_MASK)
+		alarm_events->pll_lost_sync++;
+
+	if (int_events & UNLOCK_10_MASK)
+		alarm_events->pll_sync++;
+
+	/* Holdover changed from 0 to 1 ? */
+	if (int_events & HOLDOVER_01_MASK) {
+		alarm_events->pll_holdover++;
+
+		/* TIMEOUT in ~10ms */
+		switchover_timer.expires = jiffies + msecs_to_jiffies(10);
+		switchover_timer.data = inb(TLCLK_REG1);
+		add_timer(&switchover_timer);
+	} else {
+		got_event = 1;
+		wake_up(&wq);
+	}
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+module_init(tlclk_init);
+module_exit(tlclk_cleanup);
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/MAINTAINERS linux-2.6.14-rc2-mm2-tlclk/MAINTAINERS
--- linux-2.6.14-rc2-mm2/MAINTAINERS	2005-10-04 14:55:18.000000000 -0700
+++ linux-2.6.14-rc2-mm2-tlclk/MAINTAINERS	2005-10-12 10:02:13.000000000 -0700
@@ -2319,6 +2319,11 @@
 L:	tpmdd-devel@lists.sourceforge.net
 S:	Maintained
 
+Telecom Clock Driver for MCPL0010
+P: Mark Gross
+M: mark.gross@intel.com
+S: Supported
+
 TENSILICA XTENSA PORT (xtensa):
 P:	Chris Zankel
 M:	chris@zankel.net

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade
  2005-10-12 23:36           ` Mark Gross
@ 2005-10-13  1:14             ` Greg KH
  2005-10-13 21:36               ` Mark Gross
  0 siblings, 1 reply; 18+ messages in thread
From: Greg KH @ 2005-10-13  1:14 UTC (permalink / raw)
  To: Mark Gross; +Cc: akpm, linux-kernel, Sebastien.Bouchard, mark.gross

On Wed, Oct 12, 2005 at 04:36:29PM -0700, Mark Gross wrote:
> No, but I'm glad I tested that otherwise the my problem with using dev_dbg 
> with the kobj->dev devices I got from the misc_device class could have gotten 
> by me.

Yeah, it's always good to test the code to make sure it compiles :)

This patch looks good, I have no objections to it.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade
  2005-10-13  1:14             ` Greg KH
@ 2005-10-13 21:36               ` Mark Gross
  2005-10-13 22:08                 ` Jesper Juhl
  0 siblings, 1 reply; 18+ messages in thread
From: Mark Gross @ 2005-10-13 21:36 UTC (permalink / raw)
  To: Greg KH; +Cc: akpm, linux-kernel, Sebastien.Bouchard, mark.gross

[-- Attachment #1: Type: text/plain, Size: 762 bytes --]

On Wednesday 12 October 2005 18:14, Greg KH wrote:
> On Wed, Oct 12, 2005 at 04:36:29PM -0700, Mark Gross wrote:
> > No, but I'm glad I tested that otherwise the my problem with using dev_dbg 
> > with the kobj->dev devices I got from the misc_device class could have 
gotten 
> > by me.
> 
> Yeah, it's always good to test the code to make sure it compiles :)
> 
> This patch looks good, I have no objections to it.
> 
> thanks,
> 

One minor update, after testing with misc_class device for udev I found that 
it would be nice to have the sysfs file inodes all use the same base name 
"teleco_clock".

Please consider including this driver in the MM tree.

See attached.

-- 
--mgross
BTW: This may or may not be the opinion of my employer, more likely not.  

[-- Attachment #2: tlclk-2.6.14-rc2-mm2.patch --]
[-- Type: text/x-diff, Size: 25847 bytes --]

diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/Kconfig linux-2.6.14-rc2-mm2-tlclk/drivers/char/Kconfig
--- linux-2.6.14-rc2-mm2/drivers/char/Kconfig	2005-10-04 14:55:15.000000000 -0700
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/Kconfig	2005-10-12 15:23:23.000000000 -0700
@@ -1001,5 +1001,17 @@
 
 source "drivers/char/tpm/Kconfig"
 
+config TELCLOCK
+	tristate "Telecom clock driver for MPBL0010 ATCA SBC"
+	depends on EXPERIMENTAL
+	default n
+	help
+	  The telecom clock device is specific to the MPBL0010 ATCA computer and
+	  allows direct userspace access to the configuration of the telecom clock
+	  configuration settings.  This device is used for hardware synchronization
+	  across the ATCA backplane fabric.  Upon loading, the driver exports a
+	  sysfs directory, /sys/devices/platform/telco_clock, with a number of
+	  files for controlling the behavior of this hardware.
+
 endmenu
 
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/Makefile linux-2.6.14-rc2-mm2-tlclk/drivers/char/Makefile
--- linux-2.6.14-rc2-mm2/drivers/char/Makefile	2005-10-04 14:55:15.000000000 -0700
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/Makefile	2005-10-12 10:02:13.000000000 -0700
@@ -82,6 +82,7 @@
 obj-$(CONFIG_SCx200_GPIO) += scx200_gpio.o
 obj-$(CONFIG_GPIO_VR41XX) += vr41xx_giu.o
 obj-$(CONFIG_TANBAC_TB0219) += tb0219.o
+obj-$(CONFIG_TELCLOCK) += tlclk.o
 
 obj-$(CONFIG_SPEAKUP) += speakup/
 obj-$(CONFIG_WATCHDOG)	+= watchdog/
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/drivers/char/tlclk.c linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.c
--- linux-2.6.14-rc2-mm2/drivers/char/tlclk.c	1969-12-31 16:00:00.000000000 -0800
+++ linux-2.6.14-rc2-mm2-tlclk/drivers/char/tlclk.c	2005-10-13 14:11:40.000000000 -0700
@@ -0,0 +1,896 @@
+/*
+ * Telecom Clock driver for Intel NetStructure(tm) MPCBL0010
+ *
+ * Copyright (C) 2005 Kontron Canada
+ *
+ * All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or (at
+ * your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT.  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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * Send feedback to <sebastien.bouchard@ca.kontron.com> and the current
+ * Maintainer  <mark.gross@intel.com>
+ *
+ * Description : This is the TELECOM CLOCK module driver for the ATCA
+ * MPCBL0010 ATCA computer.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/kernel.h>	/* printk() */
+#include <linux/fs.h>		/* everything... */
+#include <linux/errno.h>	/* error codes */
+#include <linux/delay.h>	/* udelay */
+#include <linux/slab.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+#include <linux/timer.h>
+#include <linux/sysfs.h>
+#include <linux/device.h>
+#include <linux/miscdevice.h>
+#include <asm/io.h>		/* inb/outb */
+#include <asm/uaccess.h>
+
+MODULE_AUTHOR("Sebastien Bouchard <sebastien.bouchard@ca.kontron.com>");
+MODULE_LICENSE("GPL");
+
+/*Hardware Reset of the PLL */
+#define RESET_ON	0x00
+#define RESET_OFF	0x01
+
+/* MODE SELECT */
+#define NORMAL_MODE 	0x00
+#define HOLDOVER_MODE	0x10
+#define FREERUN_MODE	0x20
+
+/* FILTER SELECT */
+#define FILTER_6HZ	0x04
+#define FILTER_12HZ	0x00
+
+/* SELECT REFERENCE FREQUENCY */
+#define REF_CLK1_8kHz		0x00
+#define REF_CLK2_19_44MHz	0x02
+
+/* Select primary or secondary redundant clock */
+#define PRIMARY_CLOCK	0x00
+#define SECONDARY_CLOCK	0x01
+
+/* CLOCK TRANSMISSION DEFINE */
+#define CLK_8kHz	0xff
+#define CLK_16_384MHz	0xfb
+
+#define CLK_1_544MHz	0x00
+#define CLK_2_048MHz	0x01
+#define CLK_4_096MHz	0x02
+#define CLK_6_312MHz	0x03
+#define CLK_8_192MHz	0x04
+#define CLK_19_440MHz	0x06
+
+#define CLK_8_592MHz	0x08
+#define CLK_11_184MHz	0x09
+#define CLK_34_368MHz	0x0b
+#define CLK_44_736MHz	0x0a
+
+/* RECEIVED REFERENCE */
+#define AMC_B1 0
+#define AMC_B2 1
+
+/* HARDWARE SWITCHING DEFINE */
+#define HW_ENABLE	0x80
+#define HW_DISABLE	0x00
+
+/* HARDWARE SWITCHING MODE DEFINE */
+#define PLL_HOLDOVER	0x40
+#define LOST_CLOCK	0x00
+
+/* ALARMS DEFINE */
+#define UNLOCK_MASK	0x10
+#define HOLDOVER_MASK	0x20
+#define SEC_LOST_MASK	0x40
+#define PRI_LOST_MASK	0x80
+
+/* INTERRUPT CAUSE DEFINE */
+
+#define PRI_LOS_01_MASK		0x01
+#define PRI_LOS_10_MASK		0x02
+
+#define SEC_LOS_01_MASK		0x04
+#define SEC_LOS_10_MASK		0x08
+
+#define HOLDOVER_01_MASK	0x10
+#define HOLDOVER_10_MASK	0x20
+
+#define UNLOCK_01_MASK		0x40
+#define UNLOCK_10_MASK		0x80
+
+struct tlclk_alarms {
+	__u32 lost_clocks;
+	__u32 lost_primary_clock;
+	__u32 lost_secondary_clock;
+	__u32 primary_clock_back;
+	__u32 secondary_clock_back;
+	__u32 switchover_primary;
+	__u32 switchover_secondary;
+	__u32 pll_holdover;
+	__u32 pll_end_holdover;
+	__u32 pll_lost_sync;
+	__u32 pll_sync;
+};
+/* Telecom clock I/O register definition */
+#define TLCLK_BASE 0xa08
+#define TLCLK_REG0 TLCLK_BASE
+#define TLCLK_REG1 (TLCLK_BASE+1)
+#define TLCLK_REG2 (TLCLK_BASE+2)
+#define TLCLK_REG3 (TLCLK_BASE+3)
+#define TLCLK_REG4 (TLCLK_BASE+4)
+#define TLCLK_REG5 (TLCLK_BASE+5)
+#define TLCLK_REG6 (TLCLK_BASE+6)
+#define TLCLK_REG7 (TLCLK_BASE+7)
+
+#define SET_PORT_BITS(port, mask, val) outb(((inb(port) & mask) | val), port)
+
+/* 0 = Dynamic allocation of the major device number */
+#define TLCLK_MAJOR 0
+
+/* sysfs interface definition:
+Upon loading the driver will create a sysfs directory under
+/sys/devices/platform/telco_clock.
+
+This directory exports the following interfaces.  There operation is
+documented in the MCPBL0010 TPS under the Telecom Clock API section, 11.4.
+alarms				:
+current_ref			:
+enable_clk3a_output		:
+enable_clk3b_output		:
+enable_clka0_output		:
+enable_clka1_output		:
+enable_clkb0_output		:
+enable_clkb1_output		:
+filter_select			:
+hardware_switching		:
+hardware_switching_mode		:
+interrupt_switch		:
+mode_select			:
+refalign			:
+reset				:
+select_amcb1_transmit_clock	:
+select_amcb2_transmit_clock	:
+select_redundant_clock		:
+select_ref_frequency		:
+test_mode			:
+
+All sysfs interfaces are integers in hex format, i.e echo 99 > refalign
+has the same effect as echo 0x99 > refalign.
+*/
+
+static unsigned int telclk_interrupt;
+
+static int int_events;		/* Event that generate a interrupt */
+static int got_event;		/* if events processing have been done */
+
+static void switchover_timeout(unsigned long data);
+static struct timer_list switchover_timer =
+	TIMER_INITIALIZER(switchover_timeout , 0, 0);
+
+static struct tlclk_alarms *alarm_events;
+
+static DEFINE_SPINLOCK(event_lock);
+
+static int tlclk_major = TLCLK_MAJOR;
+
+static irqreturn_t tlclk_interrupt(int irq, void *dev_id, struct pt_regs *regs);
+
+static DECLARE_WAIT_QUEUE_HEAD(wq);
+
+static int tlclk_open(struct inode *inode, struct file *filp)
+{
+	int result;
+
+	/* Make sure there is no interrupt pending while
+	 * initialising interrupt handler */
+	inb(TLCLK_REG6);
+
+	/* This device is wired through the FPGA IO space of the ATCA blade
+	 * we can't share this IRQ */
+	result = request_irq(telclk_interrupt, &tlclk_interrupt,
+			     SA_INTERRUPT, "telco_clock", tlclk_interrupt);
+	if (result == -EBUSY) {
+		printk(KERN_ERR "telco_clock: Interrupt can't be reserved!\n");
+		return -EBUSY;
+	}
+	inb(TLCLK_REG6);	/* Clear interrupt events */
+
+	return 0;
+}
+
+static int tlclk_release(struct inode *inode, struct file *filp)
+{
+	free_irq(telclk_interrupt, tlclk_interrupt);
+
+	return 0;
+}
+
+ssize_t tlclk_read(struct file *filp, char __user *buf, size_t count,
+		loff_t *f_pos)
+{
+	if (count < sizeof(struct tlclk_alarms))
+		return -EIO;
+
+	wait_event_interruptible(wq, got_event);
+	if (copy_to_user(buf, alarm_events, sizeof(struct tlclk_alarms)))
+		return -EFAULT;
+
+	memset(alarm_events, 0, sizeof(struct tlclk_alarms));
+	got_event = 0;
+
+	return  sizeof(struct tlclk_alarms);
+}
+
+ssize_t tlclk_write(struct file *filp, const char __user *buf, size_t count,
+	    loff_t *f_pos)
+{
+	return 0;
+}
+
+static struct file_operations tlclk_fops = {
+	.read = tlclk_read,
+	.write = tlclk_write,
+	.open = tlclk_open,
+	.release = tlclk_release,
+
+};
+
+static struct miscdevice tlclk_miscdev = {
+	.minor = MISC_DYNAMIC_MINOR,
+	.name = "telco_clock",
+	.fops = &tlclk_fops,
+};
+
+static ssize_t show_current_ref(struct device *d,
+		struct device_attribute *attr, char *buf)
+{
+	unsigned long ret_val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+	ret_val = ((inb(TLCLK_REG1) & 0x08) >> 3);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return sprintf(buf, "0x%lX\n", ret_val);
+}
+
+static DEVICE_ATTR(current_ref, S_IRUGO, show_current_ref, NULL);
+
+
+static ssize_t show_interrupt_switch(struct device *d,
+		struct device_attribute *attr, char *buf)
+{
+	unsigned long ret_val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+	ret_val = inb(TLCLK_REG6);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return sprintf(buf, "0x%lX\n", ret_val);
+}
+
+static DEVICE_ATTR(interrupt_switch, S_IRUGO,
+		show_interrupt_switch, NULL);
+
+static ssize_t show_alarms(struct device *d,
+		struct device_attribute *attr,  char *buf)
+{
+	unsigned long ret_val;
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+	ret_val = (inb(TLCLK_REG2) & 0xf0);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return sprintf(buf, "0x%lX\n", ret_val);
+}
+
+static DEVICE_ATTR(alarms, S_IRUGO, show_alarms, NULL);
+
+static ssize_t store_enable_clk3b_output(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, ": tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG3, 0x7f, val << 7);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(enable_clk3b_output, S_IWUGO, NULL,
+		store_enable_clk3b_output);
+
+static ssize_t store_enable_clk3a_output(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG3, 0xbf, val << 6);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(enable_clk3a_output, S_IWUGO, NULL,
+		store_enable_clk3a_output);
+
+static ssize_t store_enable_clkb1_output(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG2, 0xf7, val << 3);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(enable_clkb1_output, S_IWUGO, NULL,
+		store_enable_clkb1_output);
+
+
+static ssize_t store_enable_clka1_output(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG2, 0xfb, val << 2);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(enable_clka1_output, S_IWUGO, NULL,
+		store_enable_clka1_output);
+
+static ssize_t store_enable_clkb0_output(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG2, 0xfd, val << 1);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(enable_clkb0_output, S_IWUGO, NULL,
+		store_enable_clkb0_output);
+
+static ssize_t store_enable_clka0_output(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG2, 0xfe, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(enable_clka0_output, S_IWUGO, NULL,
+		store_enable_clka0_output);
+
+static ssize_t store_test_mode(struct device *d,
+		struct device_attribute *attr,  const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG4, 0xfd, 2);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(test_mode, S_IWUGO, NULL, store_test_mode);
+
+static ssize_t store_select_amcb2_transmit_clock(struct device *d,
+		struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long flags;
+	unsigned long tmp;
+	unsigned char val;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x28);
+			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
+		} else if (val >= CLK_8_592MHz) {
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, 0x38);
+			switch (val) {
+			case CLK_8_592MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
+				break;
+			case CLK_11_184MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
+				break;
+			case CLK_34_368MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
+				break;
+			case CLK_44_736MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
+				break;
+			}
+		} else
+			SET_PORT_BITS(TLCLK_REG3, 0xc7, val << 3);
+
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(select_amcb2_transmit_clock, S_IWUGO, NULL,
+	store_select_amcb2_transmit_clock);
+
+static ssize_t store_select_amcb1_transmit_clock(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+		if ((val == CLK_8kHz) || (val == CLK_16_384MHz)) {
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x5);
+			SET_PORT_BITS(TLCLK_REG1, 0xfb, ~val);
+		} else if (val >= CLK_8_592MHz) {
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, 0x7);
+			switch (val) {
+			case CLK_8_592MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 1);
+				break;
+			case CLK_11_184MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 0);
+				break;
+			case CLK_34_368MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 3);
+				break;
+			case CLK_44_736MHz:
+				SET_PORT_BITS(TLCLK_REG0, 0xfc, 2);
+				break;
+			}
+		} else
+			SET_PORT_BITS(TLCLK_REG3, 0xf8, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(select_amcb1_transmit_clock, S_IWUGO, NULL,
+		store_select_amcb1_transmit_clock);
+
+static ssize_t store_select_redundant_clock(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG1, 0xfe, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(select_redundant_clock, S_IWUGO, NULL,
+		store_select_redundant_clock);
+
+static ssize_t store_select_ref_frequency(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG1, 0xfd, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(select_ref_frequency, S_IWUGO, NULL,
+		store_select_ref_frequency);
+
+static ssize_t store_filter_select(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xfb, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(filter_select, S_IWUGO, NULL, store_filter_select);
+
+static ssize_t store_hardware_switching_mode(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xbf, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(hardware_switching_mode, S_IWUGO, NULL,
+		store_hardware_switching_mode);
+
+static ssize_t store_hardware_switching(struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0x7f, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(hardware_switching, S_IWUGO, NULL,
+		store_hardware_switching);
+
+static ssize_t store_refalign (struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
+	udelay(2);
+	SET_PORT_BITS(TLCLK_REG0, 0xf7, 0x08);
+	udelay(2);
+	SET_PORT_BITS(TLCLK_REG0, 0xf7, 0);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(refalign, S_IWUGO, NULL, store_refalign);
+
+static ssize_t store_mode_select (struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG0, 0xcf, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(mode_select, S_IWUGO, NULL, store_mode_select);
+
+static ssize_t store_reset (struct device *d,
+		 struct device_attribute *attr, const char *buf, size_t count)
+{
+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;
+	spin_lock_irqsave(&event_lock, flags);
+	SET_PORT_BITS(TLCLK_REG4, 0xfd, val);
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return strnlen(buf, count);
+}
+
+static DEVICE_ATTR(reset, S_IWUGO, NULL, store_reset);
+
+static struct attribute *tlclk_sysfs_entries[] = {
+	&dev_attr_current_ref.attr,
+	&dev_attr_interrupt_switch.attr,
+	&dev_attr_alarms.attr,
+	&dev_attr_enable_clk3a_output.attr,
+	&dev_attr_enable_clk3b_output.attr,
+	&dev_attr_enable_clkb1_output.attr,
+	&dev_attr_enable_clka1_output.attr,
+	&dev_attr_enable_clkb0_output.attr,
+	&dev_attr_enable_clka0_output.attr,
+	&dev_attr_test_mode.attr,
+	&dev_attr_select_amcb1_transmit_clock.attr,
+	&dev_attr_select_amcb2_transmit_clock.attr,
+	&dev_attr_select_redundant_clock.attr,
+	&dev_attr_select_ref_frequency.attr,
+	&dev_attr_filter_select.attr,
+	&dev_attr_hardware_switching_mode.attr,
+	&dev_attr_hardware_switching.attr,
+	&dev_attr_refalign.attr,
+	&dev_attr_mode_select.attr,
+	&dev_attr_reset.attr,
+	NULL
+};
+
+static struct attribute_group tlclk_attribute_group = {
+	.name = NULL,		/* put in device directory */
+	.attrs = tlclk_sysfs_entries,
+};
+
+static struct platform_device *tlclk_device;
+
+static int __init tlclk_init(void)
+{
+	int ret;
+
+	ret = register_chrdev(tlclk_major, "telco_clock", &tlclk_fops);
+	if (ret < 0) {
+		printk(KERN_ERR "telco_clock: can't get major! %d\n", tlclk_major);
+		return ret;
+	}
+	alarm_events = kzalloc( sizeof(struct tlclk_alarms), GFP_KERNEL);
+	if (!alarm_events)
+		goto out1;
+
+	/* Read telecom clock IRQ number (Set by BIOS) */
+	if (!request_region(TLCLK_BASE, 8, "telco_clock")) {
+		printk(KERN_ERR "tlclk: request_region failed! 0x%X\n",
+			TLCLK_BASE);
+		ret = -EBUSY;
+		goto out2;
+	}
+	telclk_interrupt = (inb(TLCLK_REG7) & 0x0f);
+
+	if (0x0F == telclk_interrupt ) { /* not MCPBL0010 ? */
+		printk(KERN_ERR "telclk_interrup = 0x%x non-mcpbl0010 hw\n",
+			telclk_interrupt);
+		ret = -ENXIO;
+		goto out3;
+	}
+
+	init_timer(&switchover_timer);
+
+	ret = misc_register(&tlclk_miscdev);
+	if (ret < 0) {
+		printk(KERN_ERR " misc_register retruns %d\n", ret);
+		ret = -EBUSY;
+		goto out3;
+	}
+
+	tlclk_device = platform_device_register_simple("telco_clock",
+				-1, NULL, 0);
+	if (!tlclk_device) {
+		printk(KERN_ERR " platform_device_register retruns 0x%X\n",
+			(unsigned int) tlclk_device);
+		ret = -EBUSY;
+		goto out4;
+	}
+	
+	ret = sysfs_create_group(&tlclk_device->dev.kobj,
+			&tlclk_attribute_group);
+	if (ret) {
+		printk(KERN_ERR "failed to create sysfs device attributes\n");
+		sysfs_remove_group(&tlclk_device->dev.kobj,
+			&tlclk_attribute_group);
+		goto out5;
+	}
+
+	return 0;
+out5:
+	platform_device_unregister(tlclk_device);
+out4:
+	misc_deregister(&tlclk_miscdev);
+out3:
+	release_region(TLCLK_BASE, 8);
+out2:
+	kfree(alarm_events);
+out1:
+	unregister_chrdev(tlclk_major, "telco_clock");
+	return ret;
+}
+
+static void __exit tlclk_cleanup(void)
+{
+	sysfs_remove_group(&tlclk_device->dev.kobj, &tlclk_attribute_group);
+	platform_device_unregister(tlclk_device);
+	misc_deregister(&tlclk_miscdev);
+	unregister_chrdev(tlclk_major, "telco_clock");
+
+	release_region(TLCLK_BASE, 8);
+	del_timer_sync(&switchover_timer);
+	kfree(alarm_events);
+
+}
+
+static void switchover_timeout(unsigned long data)
+{
+	if ((data & 1)) {
+		if ((inb(TLCLK_REG1) & 0x08) != (data & 0x08))
+			alarm_events->switchover_primary++;
+	} else {
+		if ((inb(TLCLK_REG1) & 0x08) != (data & 0x08))
+			alarm_events->switchover_secondary++;
+	}
+
+	/* Alarm processing is done, wake up read task */
+	del_timer(&switchover_timer);
+	got_event = 1;
+	wake_up(&wq);
+}
+
+static irqreturn_t tlclk_interrupt(int irq, void *dev_id, struct pt_regs *regs)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&event_lock, flags);
+	/* Read and clear interrupt events */
+	int_events = inb(TLCLK_REG6);
+
+	/* Primary_Los changed from 0 to 1 ? */
+	if (int_events & PRI_LOS_01_MASK) {
+		if (inb(TLCLK_REG2) & SEC_LOST_MASK)
+			alarm_events->lost_clocks++;
+		else
+			alarm_events->lost_primary_clock++;
+	}
+
+	/* Primary_Los changed from 1 to 0 ? */
+	if (int_events & PRI_LOS_10_MASK) {
+		alarm_events->primary_clock_back++;
+		SET_PORT_BITS(TLCLK_REG1, 0xFE, 1);
+	}
+	/* Secondary_Los changed from 0 to 1 ? */
+	if (int_events & SEC_LOS_01_MASK) {
+		if (inb(TLCLK_REG2) & PRI_LOST_MASK)
+			alarm_events->lost_clocks++;
+		else
+			alarm_events->lost_secondary_clock++;
+	}
+	/* Secondary_Los changed from 1 to 0 ? */
+	if (int_events & SEC_LOS_10_MASK) {
+		alarm_events->secondary_clock_back++;
+		SET_PORT_BITS(TLCLK_REG1, 0xFE, 0);
+	}
+	if (int_events & HOLDOVER_10_MASK)
+		alarm_events->pll_end_holdover++;
+
+	if (int_events & UNLOCK_01_MASK)
+		alarm_events->pll_lost_sync++;
+
+	if (int_events & UNLOCK_10_MASK)
+		alarm_events->pll_sync++;
+
+	/* Holdover changed from 0 to 1 ? */
+	if (int_events & HOLDOVER_01_MASK) {
+		alarm_events->pll_holdover++;
+
+		/* TIMEOUT in ~10ms */
+		switchover_timer.expires = jiffies + msecs_to_jiffies(10);
+		switchover_timer.data = inb(TLCLK_REG1);
+		add_timer(&switchover_timer);
+	} else {
+		got_event = 1;
+		wake_up(&wq);
+	}
+	spin_unlock_irqrestore(&event_lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+module_init(tlclk_init);
+module_exit(tlclk_cleanup);
diff -urN -X dontdiff linux-2.6.14-rc2-mm2/MAINTAINERS linux-2.6.14-rc2-mm2-tlclk/MAINTAINERS
--- linux-2.6.14-rc2-mm2/MAINTAINERS	2005-10-04 14:55:18.000000000 -0700
+++ linux-2.6.14-rc2-mm2-tlclk/MAINTAINERS	2005-10-12 10:02:13.000000000 -0700
@@ -2319,6 +2319,11 @@
 L:	tpmdd-devel@lists.sourceforge.net
 S:	Maintained
 
+Telecom Clock Driver for MCPL0010
+P: Mark Gross
+M: mark.gross@intel.com
+S: Supported
+
 TENSILICA XTENSA PORT (xtensa):
 P:	Chris Zankel
 M:	chris@zankel.net

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade
  2005-10-13 21:36               ` Mark Gross
@ 2005-10-13 22:08                 ` Jesper Juhl
  2005-10-13 22:40                   ` Greg KH
  0 siblings, 1 reply; 18+ messages in thread
From: Jesper Juhl @ 2005-10-13 22:08 UTC (permalink / raw)
  To: Mark Gross; +Cc: Greg KH, akpm, linux-kernel, Sebastien.Bouchard, mark.gross

On 10/13/05, Mark Gross <mgross@linux.intel.com> wrote:
> On Wednesday 12 October 2005 18:14, Greg KH wrote:
> > On Wed, Oct 12, 2005 at 04:36:29PM -0700, Mark Gross wrote:
> > > No, but I'm glad I tested that otherwise the my problem with using dev_dbg
> > > with the kobj->dev devices I got from the misc_device class could have
> gotten
> > > by me.
> >
> > Yeah, it's always good to test the code to make sure it compiles :)
> >
> > This patch looks good, I have no objections to it.
> >
> > thanks,
> >
>
> One minor update, after testing with misc_class device for udev I found that
> it would be nice to have the sysfs file inodes all use the same base name
> "teleco_clock".
>
> Please consider including this driver in the MM tree.
>
> See attached.
>

Hi Mark,

I just took a new look at your patch and I have (again) a few small comments...


+static int tlclk_open(struct inode *inode, struct file *filp)
+{
+	int result;
+
+	/* Make sure there is no interrupt pending while
+	 * initialising interrupt handler */
+	inb(TLCLK_REG6);
+
+	/* This device is wired through the FPGA IO space of the ATCA blade
+	 * we can't share this IRQ */
+	result = request_irq(telclk_interrupt, &tlclk_interrupt,
+			     SA_INTERRUPT, "telco_clock", tlclk_interrupt);
+	if (result == -EBUSY) {
+		printk(KERN_ERR "telco_clock: Interrupt can't be reserved!\n");
+		return -EBUSY;
+	}
+	inb(TLCLK_REG6);	/* Clear interrupt events */
+
+	return 0;
+}

It seems to me that you can get rid of the "result" variable here by
rewriting the funcion like this :

static int tlclk_open(struct inode *inode, struct file *filp)
{
	/* Make sure there is no interrupt pending while
	 * initialising interrupt handler */
	inb(TLCLK_REG6);

	/* This device is wired through the FPGA IO space of the ATCA blade
	 * we can't share this IRQ */
	if (-EBUSY == request_irq(telclk_interrupt, &tlclk_interrupt,
			     SA_INTERRUPT, "telco_clock", tlclk_interrupt)) {
		printk(KERN_ERR "telco_clock: Interrupt can't be reserved!\n");
		return -EBUSY;
	}
	inb(TLCLK_REG6);	/* Clear interrupt events */

	return 0;
}

And btw, what about the other error return values that request_irq can return?
You might get back -ENOMEM or -EINVAL...  So shouldn't you rather be
doing something like

result = request_irq(...);
if (result < 0)
   /* handle error */

?????

(which then of course would bring the "result" variable back into play ;)


+ssize_t tlclk_read(struct file *filp, char __user *buf, size_t count,
...
+	return  sizeof(struct tlclk_alarms);

Why do you have 2 spaces here between "return" and "sizeof..." ?


+static DEVICE_ATTR(current_ref, S_IRUGO, show_current_ref, NULL);
+
+
+static ssize_t show_interrupt_switch(struct device *d,

Surely a single space between these two lines should be enough ;) (ok,
I'm nitpicking, I admit it).


+	unsigned long tmp;
+	unsigned char val;
+	unsigned long flags;
+
+	sscanf(buf, "%lX", &tmp);
+	dev_dbg(d, "tmp = 0x%lX\n", tmp);
+
+	val = (unsigned char)tmp;

You do this a lot, I'm wondering why you don't read directly into
"val" and then get rid of the "tmp" variable?


Maybe I'm missing something, but in tlclk_init() you are calling
request_region() and in case of failure you can end up exiting via the
out3: label which will result in release_region() being called... What
now prevents the release region() in tlclk_cleanup() from being called
on an already released region?




--
Jesper Juhl <jesper.juhl@gmail.com>
Don't top-post  http://www.catb.org/~esr/jargon/html/T/top-post.html
Plain text mails only, please      http://www.expita.com/nomime.html

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade
  2005-10-13 22:08                 ` Jesper Juhl
@ 2005-10-13 22:40                   ` Greg KH
       [not found]                     ` <9a8748490510131547s127f3167j6c9427ff3d97f878@mail.gmail.com>
  0 siblings, 1 reply; 18+ messages in thread
From: Greg KH @ 2005-10-13 22:40 UTC (permalink / raw)
  To: Jesper Juhl
  Cc: Mark Gross, akpm, linux-kernel, Sebastien.Bouchard, mark.gross

On Fri, Oct 14, 2005 at 12:08:28AM +0200, Jesper Juhl wrote:
> 
> I just took a new look at your patch and I have (again) a few small comments...
> 
> 
> +static int tlclk_open(struct inode *inode, struct file *filp)
> +{
> +	int result;
> +
> +	/* Make sure there is no interrupt pending while
> +	 * initialising interrupt handler */
> +	inb(TLCLK_REG6);
> +
> +	/* This device is wired through the FPGA IO space of the ATCA blade
> +	 * we can't share this IRQ */
> +	result = request_irq(telclk_interrupt, &tlclk_interrupt,
> +			     SA_INTERRUPT, "telco_clock", tlclk_interrupt);
> +	if (result == -EBUSY) {
> +		printk(KERN_ERR "telco_clock: Interrupt can't be reserved!\n");
> +		return -EBUSY;
> +	}
> +	inb(TLCLK_REG6);	/* Clear interrupt events */
> +
> +	return 0;
> +}
> 
> It seems to me that you can get rid of the "result" variable here by
> rewriting the funcion like this :
> 
> static int tlclk_open(struct inode *inode, struct file *filp)
> {
> 	/* Make sure there is no interrupt pending while
> 	 * initialising interrupt handler */
> 	inb(TLCLK_REG6);
> 
> 	/* This device is wired through the FPGA IO space of the ATCA blade
> 	 * we can't share this IRQ */
> 	if (-EBUSY == request_irq(telclk_interrupt, &tlclk_interrupt,
> 			     SA_INTERRUPT, "telco_clock", tlclk_interrupt)) {

Ick, no, that's a mess.  Stick with the original version.

Don't call functions within a if() statement, it's harder to read.

> +	unsigned long tmp;
> +	unsigned char val;
> +	unsigned long flags;
> +
> +	sscanf(buf, "%lX", &tmp);
> +	dev_dbg(d, "tmp = 0x%lX\n", tmp);
> +
> +	val = (unsigned char)tmp;
> 
> You do this a lot, I'm wondering why you don't read directly into
> "val" and then get rid of the "tmp" variable?

Because you want to cast it.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade
       [not found]                       ` <20051014001547.GA4647@kroah.com>
@ 2005-10-14  1:28                         ` Jesper Juhl
  0 siblings, 0 replies; 18+ messages in thread
From: Jesper Juhl @ 2005-10-14  1:28 UTC (permalink / raw)
  To: Greg KH; +Cc: LKML List

On 10/14/05, Greg KH <greg@kroah.com> wrote:
> On Fri, Oct 14, 2005 at 12:47:21AM +0200, Jesper Juhl wrote:
> > Ick, no, that's a mess.  Stick with the original version.
> >
> >Don't call functions within a if() statement, it's harder to read.
> direct to me only?  What about everyone on the list?
>
Whoops, wrong button - sorry.  Let's copy LKML.


> > I guess you are right - it /would/ save a variable though ;)
>
> Not worth it.  Ease of maintainability, which means being able to read
> the code better, trumps a single variable on a slow path.
>

Hmm, yeah, I can see the sense in that - if it's not performance
critical, better make it readable.


> > > > +     unsigned long tmp;
> > > > +     unsigned char val;
> > > > +     unsigned long flags;
> > > > +
> > > > +     sscanf(buf, "%lX", &tmp);
> > > > +     dev_dbg(d, "tmp = 0x%lX\n", tmp);
> > > > +
> > > > +     val = (unsigned char)tmp;
> > > >
> > > > You do this a lot, I'm wondering why you don't read directly into
> > > > "val" and then get rid of the "tmp" variable?
> > >
> > > Because you want to cast it.
> > >
> > Ok, I'm feeling a little dense tonight, so bear with me please, but
> > wouldn't the effect of reading into the unsigned char (and potentially
> > getting the value truncated) result in the same thing as casting it
> > later?
>
> I don't think it would be the same if you put a large value in the
> string.  Try it out and see.
>
Ok, I must admit I haven't actually tested it, perhaps I will tomorrow
after I get some sleep.


--
Jesper Juhl <jesper.juhl@gmail.com>
Don't top-post  http://www.catb.org/~esr/jargon/html/T/top-post.html
Plain text mails only, please      http://www.expita.com/nomime.html

^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2005-10-14  1:28 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2005-10-06 15:03 Fwd: Telecom Clock Driver for MPCBL0010 ATCA computer blade Mark Gross
2005-10-06 16:52 ` Jesper Juhl
2005-10-06 17:28   ` Mark Gross
2005-10-07 12:21     ` Jesper Juhl
2005-10-06 17:24 ` Fwd: " Alexey Dobriyan
2005-10-06 17:31   ` Mark Gross
2005-10-06 18:20 ` Greg KH
2005-10-06 22:54   ` Mark Gross
2005-10-06 23:15     ` Greg KH
2005-10-12 22:30       ` Mark Gross
2005-10-12 22:49         ` Greg KH
2005-10-12 23:36           ` Mark Gross
2005-10-13  1:14             ` Greg KH
2005-10-13 21:36               ` Mark Gross
2005-10-13 22:08                 ` Jesper Juhl
2005-10-13 22:40                   ` Greg KH
     [not found]                     ` <9a8748490510131547s127f3167j6c9427ff3d97f878@mail.gmail.com>
     [not found]                       ` <20051014001547.GA4647@kroah.com>
2005-10-14  1:28                         ` Jesper Juhl
2005-10-06 23:45     ` Alexey Dobriyan

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).