📄 tlclk.c
字号:
/* * 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/module.h>#include <linux/init.h>#include <linux/kernel.h> /* printk() */#include <linux/fs.h> /* everything... */#include <linux/errno.h> /* error codes */#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 <linux/platform_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 0x80struct 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 isdocumented in the MCPBL0010 TPS under the Telecom Clock API section, 11.4.alarms :current_ref :received_ref_clk3a :received_ref_clk3b :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 :telclock_version :mode_select :refalign :reset :select_amcb1_transmit_clock :select_amcb2_transmit_clock :select_redundant_clock :select_ref_frequency :All sysfs interfaces are integers in hex format, i.e echo 99 > refalignhas 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 unsigned long tlclk_timer_data;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);static DECLARE_WAIT_QUEUE_HEAD(wq);static unsigned long useflags;static DEFINE_MUTEX(tlclk_mutex);static int tlclk_open(struct inode *inode, struct file *filp){ int result; if (test_and_set_bit(0, &useflags)) return -EBUSY; /* this legacy device is always one per system and it doesn't * know how to handle multiple concurrent clients. */ /* 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, IRQF_DISABLED, "telco_clock", tlclk_interrupt); if (result == -EBUSY) { printk(KERN_ERR "tlclk: 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); clear_bit(0, &useflags); return 0;}static 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; if (mutex_lock_interruptible(&tlclk_mutex)) return -EINTR; wait_event_interruptible(wq, got_event); if (copy_to_user(buf, alarm_events, sizeof(struct tlclk_alarms))) { mutex_unlock(&tlclk_mutex); return -EFAULT; } memset(alarm_events, 0, sizeof(struct tlclk_alarms)); got_event = 0; mutex_unlock(&tlclk_mutex); return sizeof(struct tlclk_alarms);}static const struct file_operations tlclk_fops = { .read = tlclk_read, .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_telclock_version(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_REG5); spin_unlock_irqrestore(&event_lock, flags); return sprintf(buf, "0x%lX\n", ret_val);}static DEVICE_ATTR(telclock_version, S_IRUGO, show_telclock_version, 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_received_ref_clk3a(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, 0xef, val); spin_unlock_irqrestore(&event_lock, flags); return strnlen(buf, count);}static DEVICE_ATTR(received_ref_clk3a, (S_IWUSR|S_IWGRP), NULL, store_received_ref_clk3a);static ssize_t store_received_ref_clk3b(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, 0xdf, val << 1); spin_unlock_irqrestore(&event_lock, flags); return strnlen(buf, count);}static DEVICE_ATTR(received_ref_clk3b, (S_IWUSR|S_IWGRP), NULL, store_received_ref_clk3b);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_IWUSR|S_IWGRP), 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_IWUSR|S_IWGRP), 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_IWUSR|S_IWGRP), 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_IWUSR|S_IWGRP), 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);}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -