Re: Telecom Clock driver for MPCBL0010 ATCA compute blade.
From: Mark Gross
Date: Tue Aug 30 2005 - 15:38:39 EST
On Tuesday 30 August 2005 13:31, Mark Gross wrote:
> On Tuesday 30 August 2005 12:16, Marcelo Tosatti wrote:
> >
> > Mark,
> >
> > Please fix identation accordingly to CodingStyle and repost, it
> > looks quite ugly at the moment.
> >
> Sorry about that.
>
My email client is f-ing with me. See attached.
--mgross
> > On Tue, Aug 30, 2005 at 11:59:33AM -0700, Mark Gross wrote:
> > > The following is a driver I would like to see included in the base kernel.
> > >
> > > It allows OS controll of a device that synchronizes signaling hardware across a ATCA chassis.
> > >
> > > The telecom clock hardware doesn't interact much with the operating system, and is controlled
> > > via registers in the FPGA on the hardware. It is hardware that is unique to this computer.
> > >
> > > Thanks for looking at this,
> >
> diff -urN -X dontdiff_osdl vanilla/linux-2.6.13/drivers/char/Kconfig linux-2.6.13/drivers/char/Kconfig
> --- vanilla/linux-2.6.13/drivers/char/Kconfig 2005-08-28 16:41:01.000000000 -0700
> +++ linux-2.6.13/drivers/char/Kconfig 2005-08-30 10:52:25.950853752 -0700
> @@ -1002,5 +1002,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_osdl vanilla/linux-2.6.13/drivers/char/Makefile linux-2.6.13/drivers/char/Makefile
> --- vanilla/linux-2.6.13/drivers/char/Makefile 2005-08-28 16:41:01.000000000 -0700
> +++ linux-2.6.13/drivers/char/Makefile 2005-08-30 10:52:25.952853448 -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_WATCHDOG) += watchdog/
> obj-$(CONFIG_MWAVE) += mwave/
> diff -urN -X dontdiff_osdl vanilla/linux-2.6.13/drivers/char/tlclk.c linux-2.6.13/drivers/char/tlclk.c
> --- vanilla/linux-2.6.13/drivers/char/tlclk.c 1969-12-31 16:00:00.000000000 -0800
> +++ linux-2.6.13/drivers/char/tlclk.c 2005-08-30 13:21:00.275670392 -0700
> @@ -0,0 +1,938 @@
> +/*
> + * 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@xxxxxxxxxxxxxx>
> + *
> + * 2.6 driver version being maintained by <mark.gross@xxxxxxxxx>
> + *
> + * 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@xxxxxxxxxxxxxx>");
> +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;
> +
> + if (!request_region(TLCLK_BASE, 8, "telclock")) {
> + printk(KERN_ERR "tlclk: request_region failed! 0x%X\n",
> + TLCLK_BASE);
> + ret = -EBUSY;
> + goto out2;
> + }
> +
> +/* Read telecom clock IRQ number (Set by BIOS)
> + * This is also the probe opperation to avoid driver use on
> + * non MCPBL0010 hardware. */
> + 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_osdl vanilla/linux-2.6.13/drivers/char/tlclk.h linux-2.6.13/drivers/char/tlclk.h
> --- vanilla/linux-2.6.13/drivers/char/tlclk.h 1969-12-31 16:00:00.000000000 -0800
> +++ linux-2.6.13/drivers/char/tlclk.h 2005-08-30 10:52:25.961852080 -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@xxxxxxxxxxxxxx>
> + *
> + */
> +
> +/* 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;
> +};
>
>
--
--mgross
BTW: This may or may not be the opinion of my employer, more likely not.
diff -urN -X dontdiff_osdl vanilla/linux-2.6.13/drivers/char/Kconfig linux-2.6.13/drivers/char/Kconfig
--- vanilla/linux-2.6.13/drivers/char/Kconfig 2005-08-28 16:41:01.000000000 -0700
+++ linux-2.6.13/drivers/char/Kconfig 2005-08-30 10:52:25.950853752 -0700
@@ -1002,5 +1002,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_osdl vanilla/linux-2.6.13/drivers/char/Makefile linux-2.6.13/drivers/char/Makefile
--- vanilla/linux-2.6.13/drivers/char/Makefile 2005-08-28 16:41:01.000000000 -0700
+++ linux-2.6.13/drivers/char/Makefile 2005-08-30 10:52:25.952853448 -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_WATCHDOG) += watchdog/
obj-$(CONFIG_MWAVE) += mwave/
diff -urN -X dontdiff_osdl vanilla/linux-2.6.13/drivers/char/tlclk.c linux-2.6.13/drivers/char/tlclk.c
--- vanilla/linux-2.6.13/drivers/char/tlclk.c 1969-12-31 16:00:00.000000000 -0800
+++ linux-2.6.13/drivers/char/tlclk.c 2005-08-30 13:21:00.275670392 -0700
@@ -0,0 +1,938 @@
+/*
+ * 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@xxxxxxxxxxxxxx>
+ *
+ * 2.6 driver version being maintained by <mark.gross@xxxxxxxxx>
+ *
+ * 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@xxxxxxxxxxxxxx>");
+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;
+
+ if (!request_region(TLCLK_BASE, 8, "telclock")) {
+ printk(KERN_ERR "tlclk: request_region failed! 0x%X\n",
+ TLCLK_BASE);
+ ret = -EBUSY;
+ goto out2;
+ }
+
+/* Read telecom clock IRQ number (Set by BIOS)
+ * This is also the probe opperation to avoid driver use on
+ * non MCPBL0010 hardware. */
+ 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_osdl vanilla/linux-2.6.13/drivers/char/tlclk.h linux-2.6.13/drivers/char/tlclk.h
--- vanilla/linux-2.6.13/drivers/char/tlclk.h 1969-12-31 16:00:00.000000000 -0800
+++ linux-2.6.13/drivers/char/tlclk.h 2005-08-30 10:52:25.961852080 -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@xxxxxxxxxxxxxx>
+ *
+ */
+
+/* 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;
+};