Commit d0c02df3 authored by David S. Miller's avatar David S. Miller

SPARC: First pass converting serial drivers to UART layer.

parent 35cbb1e7
/* suncore.c
*
* Generic SUN serial/kbd/ms layer. Based entirely
* Common SUN serial routines. Based entirely
* upon drivers/sbus/char/sunserial.c which is:
*
* Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be)
*
* Port to new UART layer is:
* Adaptation to new UART layer is:
*
* Copyright (C) 2002 David S. Miller (davem@redhat.com)
*/
......@@ -15,9 +15,7 @@
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/kbd_diacr.h>
#include <linux/init.h>
#include <linux/bootmem.h>
#include <asm/oplib.h>
......@@ -25,158 +23,11 @@
int serial_console;
int stop_a_enabled = 1;
int sunserial_current_minor = 64;
int __init con_is_present(void)
{
return serial_console ? 0 : 1;
}
static void __init nop_rs_kgdb_hook(int channel)
{ printk("Oops: nop_rs_kgdb_hook called\n"); }
static void nop_rs_change_mouse_baud(int baud)
{ printk("Oops: nop_rs_change_mouse_baud called\n"); }
static int nop_rs_read_proc(char *page, char **start, off_t off, int count,
int *eof, void *data)
{ printk("Oops: nop_rs_read_proc called\n"); return 0; }
struct sunserial_operations rs_ops = {
0,
nop_rs_kgdb_hook,
nop_rs_change_mouse_baud,
nop_rs_read_proc
};
static int __init serial_sun_init(void)
{
struct sun_initfunc *init;
init = rs_ops.rs_init;
while (init) {
(void) init->init();
init = init->next;
}
return 0;
}
static void __exit serial_sun_exit(void)
{
}
module_init(serial_sun_init);
module_exit(serial_sun_exit);
void __init rs_kgdb_hook(int channel)
{ rs_ops.rs_kgdb_hook(channel); }
void rs_change_mouse_baud(int baud)
{ rs_ops.rs_change_mouse_baud(baud); }
int rs_read_proc(char *page, char **start, off_t off, int count,
int *eof, void *data)
{ return rs_ops.rs_read_proc(page, start, off, count, eof, data); }
static void nop_compute_shiftstate (void)
{ printk("Oops: nop_compute_shiftstate called\n"); }
static void nop_setledstate (struct kbd_struct *kbd, unsigned int ledstate)
{ printk("Oops: nop_setledstate called\n"); }
static unsigned char nop_getledstate (void)
{ printk("Oops: nop_getledstate called\n"); return 0; }
static int nop_setkeycode (unsigned int scancode, unsigned int keycode)
{ printk("Oops: nop_setkeycode called\n"); return -EINVAL; }
static int nop_getkeycode (unsigned int scancode)
{ printk("Oops: nop_getkeycode called\n"); return -EINVAL; }
struct sunkbd_operations kbd_ops = {
0,
nop_compute_shiftstate,
nop_setledstate,
nop_getledstate,
nop_setkeycode,
nop_getkeycode
};
#ifdef CONFIG_USB
extern void pci_compute_shiftstate(void);
extern int pci_setkeycode(unsigned int, unsigned int);
extern int pci_getkeycode(unsigned int);
extern void pci_setledstate(struct kbd_struct *, unsigned int);
extern unsigned char pci_getledstate(void);
extern int pcikbd_init(void);
#endif
int kbd_init(void)
{
struct sun_initfunc *init;
int err = -ENODEV;
init = kbd_ops.kbd_init;
while (init) {
err = init->init();
init = init->next;
}
#ifdef CONFIG_USB
if (!serial_console &&
kbd_ops.compute_shiftstate == nop_compute_shiftstate) {
printk("kbd_init: Assuming USB keyboard.\n");
kbd_ops.compute_shiftstate = pci_compute_shiftstate;
kbd_ops.setledstate = pci_setledstate;
kbd_ops.getledstate = pci_getledstate;
kbd_ops.setkeycode = pci_setkeycode;
kbd_ops.getkeycode = pci_getkeycode;
pcikbd_init();
}
#endif
return err;
}
void compute_shiftstate(void)
{ kbd_ops.compute_shiftstate(); }
void setledstate(struct kbd_struct *kbd, unsigned int ledstate)
{ kbd_ops.setledstate(kbd, ledstate); }
unsigned char getledstate(void)
{ return kbd_ops.getledstate(); }
int setkeycode(unsigned int scancode, unsigned int keycode)
{ return kbd_ops.setkeycode(scancode, keycode); }
int getkeycode(unsigned int scancode)
{ return kbd_ops.getkeycode(scancode); }
void * __init sunserial_alloc_bootmem(unsigned long size)
{
void *ret;
ret = __alloc_bootmem(size, SMP_CACHE_BYTES, 0UL);
if (ret != NULL)
memset(ret, 0, size);
return ret;
}
void
sunserial_setsun_initfunc(int (*init) (void))
{
struct sun_initfunc *rs_initfunc;
rs_initfunc = sunserial_alloc_bootmem(sizeof(*rs_initfunc));
if (rs_initfunc == NULL) {
prom_printf("sunserial_setsun_initfunc: Cannot alloc sun_initfunc.\n");
prom_halt();
}
rs_initfunc->init = init;
rs_initfunc->next = rs_ops.rs_init;
rs_ops.rs_init = rs_initfunc;
}
EXPORT_SYMBOL(serial_console);
EXPORT_SYMBOL(stop_a_enabled);
EXPORT_SYMBOL(sunserial_current_minor);
void
sunserial_console_termios(struct console *con)
......@@ -228,9 +79,9 @@ sunserial_console_termios(struct console *con)
/* XXX: this is unused below. */
}
if (prom_node_has_property(nd, cd_prop)) {
if (prom_node_has_property(nd, dtr_prop)) {
memset(buf, 0, sizeof(buf));
prom_getstring(nd, cd_prop, buf, sizeof(buf));
prom_getstring(nd, dtr_prop, buf, sizeof(buf));
if (!strcmp(buf, "false"))
rtsdtr = 0;
......@@ -286,128 +137,104 @@ sunserial_console_termios(struct console *con)
con->cflag = cflag;
}
EXPORT_SYMBOL(sunserial_console_termios);
void
sunkbd_setsun_initfunc(int (*init)(void))
sun_do_break(void)
{
struct sun_initfunc *kbd_initfunc;
if (!stop_a_enabled)
return;
kbd_initfunc = sunserial_alloc_bootmem(sizeof(*kbd_initfunc));
if (kbd_initfunc == NULL) {
prom_printf("sunkbd_setsun_initfunc: Cannot alloc sun_initfunc.\n");
prom_halt();
}
printk("\n");
flush_user_windows();
kbd_initfunc->init = init;
kbd_initfunc->next = kbd_ops.kbd_init;
kbd_ops.kbd_init = kbd_initfunc;
#ifndef CONFIG_SPARC64
if ((unsigned long)linux_dbvec >= DEBUG_FIRSTVADDR &&
(unsigned long)linux_dbvec <= DEBUG_LASTVADDR)
sp_enter_debugger();
else
#endif
prom_cmdline();
}
#ifdef CONFIG_PCI
void
sunkbd_install_keymaps(ushort **src_key_maps, unsigned int src_keymap_count,
char *src_func_buf, char **src_func_table,
int src_funcbufsize, int src_funcbufleft,
struct kbdiacr *src_accent_table,
unsigned int src_accent_table_size)
EXPORT_SYMBOL(sun_do_break);
/* Sun serial MOUSE auto baud rate detection. */
static struct mouse_baud_cflag {
int baud;
unsigned int cflag;
} mouse_baud_table[] = {
{ 1200, B1200 },
{ 2400, B2400 },
{ 4800, B4800 },
{ 9600, B9600 },
{ -1, ~0 },
{ -1, ~0 },
};
unsigned int suncore_mouse_baud_cflag_next(unsigned int cflag, int *new_baud)
{
extern unsigned int keymap_count;
int i, j;
for (i = 0; i < MAX_NR_KEYMAPS; i++) {
if (src_key_maps[i]) {
if (!key_maps[i]) {
key_maps[i] = (ushort *)
sunserial_alloc_bootmem(NR_KEYS * sizeof(ushort));
if (key_maps[i] == NULL) {
prom_printf("sunkbd_install_keymaps: "
"Cannot alloc key_map(%d).\n", i);
prom_halt();
}
}
for (j = 0; j < NR_KEYS; j++)
key_maps[i][j] = src_key_maps[i][j];
}
key_maps[i] = src_key_maps[i];
}
keymap_count = src_keymap_count;
int i;
for (i = 0; mouse_baud_table[i].baud != -1; i++)
if (mouse_baud_table[i].cflag == (cflag & CBAUD))
break;
for (i = 0; i < MAX_NR_FUNC; i++)
func_table[i] = src_func_table[i];
funcbufptr = src_func_buf;
funcbufsize = src_funcbufsize;
funcbufleft = src_funcbufleft;
i += 1;
if (mouse_baud_table[i].baud == -1)
i = 0;
for (i = 0; i < MAX_DIACR; i++)
accent_table[i] = src_accent_table[i];
accent_table_size = src_accent_table_size;
*new_baud = mouse_baud_table[i].baud;
return mouse_baud_table[i].cflag;
}
#endif
extern int sunsu_probe(void);
extern int sunzilog_probe(void);
#ifdef CONFIG_SAB82532
extern int sab82532_probe(void);
#endif
#ifdef CONFIG_PCI
extern int ps2kbd_probe(void);
#endif
EXPORT_SYMBOL(suncore_mouse_baud_cflag_next);
/* This is called by the sparc32/sparc64 MM init layer right after
* the bootmem allocator has been setup and is ready to use.
/* Basically, when the baud rate is wrong the mouse spits out
* breaks to us.
*/
void __init sun_serial_setup(void)
int suncore_mouse_baud_detection(unsigned char ch, int is_break)
{
int ret = 1;
#if defined(CONFIG_PCI) && !defined(CONFIG_SPARC64)
/*
* Probing sequence on sparc differs from sparc64.
* Keyboard is probed ahead of su because we want su function
* when keyboard is active. su is probed ahead of zs in order to
* get console on MrCoffee with fine but disconnected zs.
*/
if (!serial_console)
ps2kbd_probe();
if (sunsu_probe() == 0)
return;
#endif
if (sunzilog_probe() == 0)
return;
#ifdef CONFIG_SAB82532
ret = sab82532_probe();
#endif
static int mouse_got_break = 0;
static int ctr = 0;
if (is_break) {
/* Let a few normal bytes go by before we jump the gun
* and say we need to try another baud rate.
*/
if (mouse_got_break && ctr < 8)
return 1;
/* Ok, we need to try another baud. */
ctr = 0;
mouse_got_break = 1;
return 2;
}
if (mouse_got_break) {
ctr++;
if (c == 0x87) {
/* Correct baud rate determined. */
mouse_got_break = 0;
}
return 1;
}
return 0;
}
#if defined(CONFIG_PCI) && defined(CONFIG_SPARC64)
/*
* Keyboard serial devices.
*
* Well done, Sun, prom_devopen("/pci@1f,4000/ebus@1/su@14,3083f8")
* hangs the machine if no keyboard is connected to the device...
* All PCI PROMs seem to do this, I have seen this on the Ultra 450
* with version 3.5 PROM, and on the Ultra/AX with 3.1.5 PROM.
*
* So be very careful not to probe for keyboards if we are on a
* serial console.
*/
if (!serial_console)
ps2kbd_probe();
if (sunsu_probe() == 0)
return;
#endif
EXPORT_SYMBOL(suncore_mouse_baud_detection);
if (!ret)
return;
#ifdef CONFIG_SPARC64
{ extern int this_is_starfire;
/* Hello, Starfire. Pleased to meet you :) */
if (this_is_starfire != 0)
return;
}
#endif
static int __init suncore_init(void)
{
return 0;
}
prom_printf("No serial devices found, bailing out.\n");
prom_halt();
static void __exit suncore_exit(void)
{
}
module_init(suncore_init);
module_exit(suncore_exit);
MODULE_AUTHOR("Eddie C. Dost, David S. Miller");
MODULE_DESCRIPTION("Sun serial common layer");
MODULE_LICENSE("GPL");
......@@ -15,41 +15,20 @@
#include <linux/config.h>
struct sun_initfunc {
int (*init) (void);
struct sun_initfunc *next;
};
struct sunserial_operations {
struct sun_initfunc *rs_init;
void (*rs_kgdb_hook) (int);
void (*rs_change_mouse_baud) (int);
int (*rs_read_proc) (char *, char **, off_t, int, int *, void *);
};
struct sunkbd_operations {
struct sun_initfunc *kbd_init;
void (*compute_shiftstate) (void);
void (*setledstate) (struct kbd_struct *, unsigned int);
unsigned char (*getledstate) (void);
int (*setkeycode) (unsigned int, unsigned int);
int (*getkeycode) (unsigned int);
};
extern struct sunserial_operations rs_ops;
extern struct sunkbd_operations kbd_ops;
extern void sunserial_setinitfunc(int (*) (void));
extern void sunkbd_setinitfunc(int (*) (void));
extern void sun_do_break(void);
extern unsigned int suncore_mouse_baud_cflag_next(unsigned int, int *);
extern int suncore_mouse_baud_detection(unsigned char, int);
extern int serial_console;
extern int stop_a_enabled;
extern void sunserial_console_termios(struct console *);
extern int sunserial_current_minor;
#ifdef CONFIG_PCI
extern void sunkbd_install_keymaps(ushort **, unsigned int, char *,
char **, int, int, struct kbdiacr *,
unsigned int);
#endif
static __inline__ int con_is_present(void)
{
return serial_console ? 0 : 1;
}
extern void sunserial_console_termios(struct console *);
#endif /* !(_SERIAL_SUN_H) */
/* keyboard.c: Sun keyboard driver.
*
* Copyright (C) 1995, 1996, 1997 David S. Miller (davem@caip.rutgers.edu)
*
* Added vuid event generation and /dev/kbd device for SunOS
* compatibility - Miguel (miguel@nuclecu.unam.mx)
*
* Added PCI 8042 controller support -DaveM
* Added Magic SysRq support -MJ
*/
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/mm.h>
#include <linux/ptrace.h>
#include <linux/signal.h>
#include <linux/string.h>
#include <linux/fcntl.h>
#include <linux/poll.h>
#include <linux/random.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/sysrq.h>
#include <linux/spinlock.h>
#include <linux/smp_lock.h>
#include <linux/devfs_fs_kernel.h>
#include <asm/kbio.h>
#include <asm/vuid_event.h>
#include <asm/bitops.h>
#include <asm/oplib.h>
#include <asm/uaccess.h>
#include <linux/kbd_kern.h>
#include <linux/kbd_diacr.h>
#include <linux/vt_kern.h>
#ifdef CONFIG_PCI
#include <linux/pci.h>
#include <asm/pbm.h>
#include <asm/ebus.h>
#endif
#include "sunkbd.h"
#define SIZE(x) (sizeof(x)/sizeof((x)[0]))
/* Define this one if you are making a new frame buffer driver */
/* it will not block the keyboard */
/* #define CODING_NEW_DRIVER */
/* KBD device number, temporal */
#define KBD_MAJOR 11
#define KBD_REPORT_ERR
#define KBD_REPORT_UNKN
#ifndef KBD_DEFMODE
#define KBD_DEFMODE ((1 << VC_REPEAT) | (1 << VC_META))
#endif
#ifndef KBD_DEFLEDS
/*
* Some laptops take the 789uiojklm,. keys as number pad when NumLock
* is on. This seems a good reason to start with NumLock off.
*/
#define KBD_DEFLEDS 0
#endif
#ifndef KBD_DEFLOCK
#define KBD_DEFLOCK 0
#endif
extern void poke_blanked_console(void);
extern void ctrl_alt_del(void);
extern void reset_vc(unsigned int new_console);
extern void scrollback(int);
extern void scrollfront(int);
struct l1a_kbd_state l1a_state;
static spinlock_t sunkbd_lock = SPIN_LOCK_UNLOCKED;
/*
* global state includes the following, and various static variables
* in this module: prev_scancode, shift_state, diacr, npadch, dead_key_next.
* (last_console is now a global variable)
*/
/* shift state counters.. */
static unsigned char k_down[NR_SHIFT];
/* keyboard key bitmap */
static unsigned long key_down[256/BITS_PER_LONG];
void push_kbd (int scan);
int kbd_redirected;
static int dead_key_next;
/*
* In order to retrieve the shift_state (for the mouse server), either
* the variable must be global, or a new procedure must be created to
* return the value. I chose the former way.
*/
#ifndef CONFIG_PCI
int shift_state;
struct kbd_struct kbd_table[MAX_NR_CONSOLES];
#endif
static int npadch = -1; /* -1 or number assembled on pad */
static unsigned char diacr;
static char rep; /* flag telling character repeat */
static struct tty_struct **ttytab;
static struct kbd_struct * kbd = kbd_table;
static struct tty_struct * tty;
static int compose_led_on;
static int kbd_delay_ticks = HZ / 5;
static int kbd_rate_ticks = HZ / 20;
void sun_compute_shiftstate(void);
typedef void (*k_hand)(unsigned char value, char up_flag);
typedef void (k_handfn)(unsigned char value, char up_flag);
static k_handfn
do_self, do_fn, do_spec, do_pad, do_dead, do_cons, do_cur, do_shift,
do_meta, do_ascii, do_lock, do_lowercase, do_ignore;
static k_hand key_handler[16] = {
do_self, do_fn, do_spec, do_pad, do_dead, do_cons, do_cur, do_shift,
do_meta, do_ascii, do_lock, do_lowercase,
do_ignore, do_ignore, do_ignore, do_ignore
};
typedef void (*void_fnp)(void);
typedef void (void_fn)(void);
static void_fn do_null, enter, show_ptregs, send_intr, lastcons, caps_toggle,
num, hold, scroll_forw, scroll_back, boot_it, caps_on, compose,
SAK, decr_console, incr_console, spawn_console, bare_num;
static void_fnp spec_fn_table[] = {
do_null, enter, show_ptregs, show_mem,
show_state, send_intr, lastcons, caps_toggle,
num, hold, scroll_forw, scroll_back,
boot_it, caps_on, compose, SAK,
decr_console, incr_console, spawn_console, bare_num
};
/* maximum values each key_handler can handle */
#ifndef CONFIG_PCI
const int max_vals[] = {
255, SIZE(func_table) - 1, SIZE(spec_fn_table) - 1, NR_PAD - 1,
NR_DEAD - 1, 255, 3, NR_SHIFT - 1,
255, NR_ASCII - 1, NR_LOCK - 1, 255,
NR_LOCK - 1
};
const int NR_TYPES = SIZE(max_vals);
#endif
static void put_queue(int);
static unsigned char handle_diacr(unsigned char);
/* pt_regs - set by keyboard_interrupt(), used by show_ptregs() */
static struct pt_regs * pt_regs;
#ifdef CONFIG_MAGIC_SYSRQ
unsigned char sun_sysrq_xlate[128] =
"\0\0\0\0\0\201\202\212\203\213\204\214\205\0\206\0" /* 0x00 - 0x0f */
"\207\210\211\0\0\0\0\0\0\0\0\0\0\03312" /* 0x10 - 0x1f */
"34567890-=`\177\0=/*" /* 0x20 - 0x2f */
"\0\0.\0\0\011qwertyuiop" /* 0x30 - 0x3f */
"[]\177\000789-\0\0\0\0\0asd" /* 0x40 - 0x4f */
"fghjkl;'\\\015\0154560\0" /* 0x50 - 0x5f */
"\0\0\0\0zxcvbnm,./\0\012" /* 0x60 - 0x6f */
"123\0\0\0\0\0\0 \0\0\0\0\0\0"; /* 0x70 - 0x7f */
#endif
volatile unsigned char sunkbd_layout;
volatile unsigned char sunkbd_type;
#define SUNKBD_TYPE2 0x02
#define SUNKBD_TYPE3 0x03
#define SUNKBD_TYPE4 0x04
#define SUNKBD_LOUT_TYP4 0x00
#define SUNKBD_LOUT_TYP5_MASK 0x20
volatile int kbd_reset_pending;
volatile int kbd_layout_pending;
/* commands */
#define SKBDCMD_RESET 0x1
#define SKBDCMD_GLAYOUT 0xf
#define SKBDCMD_BELLON 0x2
#define SKBDCMD_BELLOFF 0x3
#define SKBDCMD_SETLED 0xe
#define SKBDCMD_NOCLICK 0xb
#define SKBDCMD_CLICK 0xa
static unsigned char sunkbd_clickp;
/* The led set commands require sending the SETLED byte then
* a byte encoding which led's to have set. Here are the bit
* values, a bit set = led-on.
*/
#define LED_NLOCK 0x1 /* Num-lock */
#define LED_CMPOSE 0x2 /* Compose */
#define LED_SCRLCK 0x4 /* Scroll-lock */
#define LED_CLOCK 0x8 /* Caps-lock */
/* Special state characters */
#define SKBD_RESET 0xff
#define SKBD_ALLUP 0x7f
#define SKBD_LYOUT 0xfe
/* On the Sparc the keyboard could be one of two things.
* It could be a real keyboard speaking over one of the
* channels of the second zs8530 chip (other channel is
* used by the Sun mouse). Else we have serial console
* going, and thus the other zs8530 chip is who we speak
* to. Either way, we communicate through the zs8530
* driver for all our I/O.
*/
#define SUNKBD_UBIT 0x80 /* If set, key went up */
#define SUNKBD_KMASK 0x7f /* Other bits are the keycode */
#define KEY_LSHIFT 0x81
#define KEY_RSHIFT 0x82
#define KEY_CONTROL 0x83
#define KEY_NILL 0x84
#define KEY_CAPSLOCK 0x85
#define KEY_ALT 0x86
#define KEY_L1 0x87
/* Due to sun_kbd_init() being called before rs_init(), and sun_kbd_init() doing:
*
* tasklet_enable(&keyboard_tasklet);
* tasklet_schedule(&keyboard_tasklet);
*
* this might well be called before some driver has claimed interest in
* handling the keyboard input/output. So we need to assign an initial nop.
*/
static void nop_kbd_put_char(unsigned char c) { }
static void (*kbd_put_char)(unsigned char) = nop_kbd_put_char;
/* Must be invoked under sunkbd_lock. */
static inline void send_cmd(unsigned char c)
{
kbd_put_char(c);
}
/* kbd_bh() calls this to send the SKBDCMD_SETLED to the sun keyboard
* with the proper bit pattern for the leds to be set. It basically
* converts the kbd->ledflagstate values to corresponding sun kbd led
* bit value.
*/
static inline unsigned char vcleds_to_sunkbd(unsigned char vcleds)
{
unsigned char retval = 0;
if(vcleds & (1<<VC_SCROLLOCK))
retval |= LED_SCRLCK;
if(vcleds & (1<<VC_NUMLOCK))
retval |= LED_NLOCK;
if(vcleds & (1<<VC_CAPSLOCK))
retval |= LED_CLOCK;
if(compose_led_on)
retval |= LED_CMPOSE;
return retval;
}
/*
* Translation of escaped scancodes to keycodes.
* This is now user-settable.
* The keycodes 1-88,96-111,119 are fairly standard, and
* should probably not be changed - changing might confuse X.
* X also interprets scancode 0x5d (KEY_Begin).
*
* For 1-88 keycode equals scancode.
*/
#define E0_KPENTER 96
#define E0_RCTRL 97
#define E0_KPSLASH 98
#define E0_PRSCR 99
#define E0_RALT 100
#define E0_BREAK 101 /* (control-pause) */
#define E0_HOME 102
#define E0_UP 103
#define E0_PGUP 104
#define E0_LEFT 105
#define E0_RIGHT 106
#define E0_END 107
#define E0_DOWN 108
#define E0_PGDN 109
#define E0_INS 110
#define E0_DEL 111
#define E1_PAUSE 119
/*
* The keycodes below are randomly located in 89-95,112-118,120-127.
* They could be thrown away (and all occurrences below replaced by 0),
* but that would force many users to use the `setkeycodes' utility, where
* they needed not before. It does not matter that there are duplicates, as
* long as no duplication occurs for any single keyboard.
*/
#define SC_LIM 89
#define FOCUS_PF1 85 /* actual code! */
#define FOCUS_PF2 89
#define FOCUS_PF3 90
#define FOCUS_PF4 91
#define FOCUS_PF5 92
#define FOCUS_PF6 93
#define FOCUS_PF7 94
#define FOCUS_PF8 95
#define FOCUS_PF9 120
#define FOCUS_PF10 121
#define FOCUS_PF11 122
#define FOCUS_PF12 123
#define JAP_86 124
/* tfj@olivia.ping.dk:
* The four keys are located over the numeric keypad, and are
* labelled A1-A4. It's an rc930 keyboard, from
* Regnecentralen/RC International, Now ICL.
* Scancodes: 59, 5a, 5b, 5c.
*/
#define RGN1 124
#define RGN2 125
#define RGN3 126
#define RGN4 127
static unsigned char high_keys[128 - SC_LIM] = {
RGN1, RGN2, RGN3, RGN4, 0, 0, 0, /* 0x59-0x5f */
0, 0, 0, 0, 0, 0, 0, 0, /* 0x60-0x67 */
0, 0, 0, 0, 0, FOCUS_PF11, 0, FOCUS_PF12, /* 0x68-0x6f */
0, 0, 0, FOCUS_PF2, FOCUS_PF9, 0, 0, FOCUS_PF3, /* 0x70-0x77 */
FOCUS_PF4, FOCUS_PF5, FOCUS_PF6, FOCUS_PF7, /* 0x78-0x7b */
FOCUS_PF8, JAP_86, FOCUS_PF10, 0 /* 0x7c-0x7f */
};
/* BTC */
#define E0_MACRO 112
/* LK450 */
#define E0_F13 113
#define E0_F14 114
#define E0_HELP 115
#define E0_DO 116
#define E0_F17 117
#define E0_KPMINPLUS 118
/*
* My OmniKey generates e0 4c for the "OMNI" key and the
* right alt key does nada. [kkoller@nyx10.cs.du.edu]
*/
#define E0_OK 124
/*
* New microsoft keyboard is rumoured to have
* e0 5b (left window button), e0 5c (right window button),
* e0 5d (menu button). [or: LBANNER, RBANNER, RMENU]
* [or: Windows_L, Windows_R, TaskMan]
*/
#define E0_MSLW 125
#define E0_MSRW 126
#define E0_MSTM 127
static unsigned char e0_keys[128] = {
0, 0, 0, 0, 0, 0, 0, 0, /* 0x00-0x07 */
0, 0, 0, 0, 0, 0, 0, 0, /* 0x08-0x0f */
0, 0, 0, 0, 0, 0, 0, 0, /* 0x10-0x17 */
0, 0, 0, 0, E0_KPENTER, E0_RCTRL, 0, 0, /* 0x18-0x1f */
0, 0, 0, 0, 0, 0, 0, 0, /* 0x20-0x27 */
0, 0, 0, 0, 0, 0, 0, 0, /* 0x28-0x2f */
0, 0, 0, 0, 0, E0_KPSLASH, 0, E0_PRSCR, /* 0x30-0x37 */
E0_RALT, 0, 0, 0, 0, E0_F13, E0_F14, E0_HELP, /* 0x38-0x3f */
E0_DO, E0_F17, 0, 0, 0, 0, E0_BREAK, E0_HOME, /* 0x40-0x47 */
E0_UP, E0_PGUP, 0, E0_LEFT, E0_OK, E0_RIGHT, E0_KPMINPLUS, E0_END,/* 0x48-0x4f */
E0_DOWN, E0_PGDN, E0_INS, E0_DEL, 0, 0, 0, 0, /* 0x50-0x57 */
0, 0, 0, E0_MSLW, E0_MSRW, E0_MSTM, 0, 0, /* 0x58-0x5f */
0, 0, 0, 0, 0, 0, 0, 0, /* 0x60-0x67 */
0, 0, 0, 0, 0, 0, 0, E0_MACRO, /* 0x68-0x6f */
0, 0, 0, 0, 0, 0, 0, 0, /* 0x70-0x77 */
0, 0, 0, 0, 0, 0, 0, 0 /* 0x78-0x7f */
};
/* we use this map to determine if a particular key should not be
autorepeated. We don't autorepeat CONTROL, LSHIFT, CAPS,
ALT, LMETA, RSHIFT, RMETA, ALTG and COMPOSE */
static unsigned char norepeat_keys[128] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, /* 0x00-0x0f */
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10-0x1f */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20-0x2f */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30-0x3f */
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, /* 0x40-0x4f */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50-0x5f */
0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, /* 0x60-0x6f */
0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, /* 0x70-0x7f */
};
int sun_setkeycode(unsigned int scancode, unsigned int keycode)
{
if (scancode < SC_LIM || scancode > 255 || keycode > 127)
return -EINVAL;
if (scancode < 128)
high_keys[scancode - SC_LIM] = keycode;
else
e0_keys[scancode - 128] = keycode;
return 0;
}
int sun_getkeycode(unsigned int scancode)
{
return
(scancode < SC_LIM || scancode > 255) ? -EINVAL :
(scancode < 128) ? high_keys[scancode - SC_LIM] :
e0_keys[scancode - 128];
}
static void __sunkbd_inchar(unsigned char ch, struct pt_regs *regs);
void sunkbd_inchar(unsigned char ch, struct pt_regs *regs);
static void keyboard_timer (unsigned long ignored);
static struct timer_list
auto_repeat_timer = { function: keyboard_timer };
/* Keeps track of the last pressed key */
static unsigned char last_keycode;
static void
keyboard_timer (unsigned long ignored)
{
unsigned long flags;
spin_lock_irqsave(&sunkbd_lock, flags);
/* Auto repeat: send regs = 0 to indicate autorepeat */
__sunkbd_inchar (last_keycode, 0);
del_timer (&auto_repeat_timer);
if (kbd_rate_ticks) {
auto_repeat_timer.expires = jiffies + kbd_rate_ticks;
add_timer (&auto_repeat_timer);
}
spin_unlock_irqrestore(&sunkbd_lock, flags);
}
#ifndef CONFIG_PCI
DECLARE_TASKLET_DISABLED(keyboard_tasklet, sun_kbd_bh, 0);
#endif
/* #define SKBD_DEBUG */
/* This is our keyboard 'interrupt' routine.
* Must run under sunkbd_lock.
*/
static void __sunkbd_inchar(unsigned char ch, struct pt_regs *regs)
{
unsigned char keycode;
char up_flag; /* 0 or SUNKBD_UBIT */
char raw_mode;
if(ch == SKBD_RESET) {
kbd_reset_pending = 1;
goto out;
}
if(ch == SKBD_LYOUT) {
kbd_layout_pending = 1;
goto out;
}
if(kbd_reset_pending) {
sunkbd_type = ch;
kbd_reset_pending = 0;
if(ch == SUNKBD_TYPE4)
send_cmd(SKBDCMD_GLAYOUT);
goto out;
} else if(kbd_layout_pending) {
sunkbd_layout = ch;
kbd_layout_pending = 0;
goto out;
} else if(ch == SKBD_ALLUP) {
del_timer (&auto_repeat_timer);
memset(key_down, 0, sizeof(key_down));
sun_compute_shiftstate();
goto out;
}
#ifdef SKBD_DEBUG
if(ch == 0x7f)
printk("KBD<ALL KEYS UP>");
else
printk("KBD<%x %s>", ch,
((ch&0x80) ? "UP" : "DOWN"));
#endif
/* Whee, a real character. */
if(regs) {
pt_regs = regs;
last_keycode = keycode = ch;
} else {
keycode = ch;
}
do_poke_blanked_console = 1;
schedule_console_callback();
add_keyboard_randomness(keycode);
tty = ttytab? ttytab[fg_console]: NULL;
if (tty && (!tty->driver_data)) {
/* This is to workaround ugly bug in tty_io.c, which
does not do locking when it should */
tty = NULL;
}
kbd = kbd_table + fg_console;
if((raw_mode = (kbd->kbdmode == VC_RAW))) {
if (kbd_redirected == fg_console+1)
push_kbd (keycode);
else
put_queue(keycode);
/* we do not return yet, because we want to maintain
* the key_down array, so that we have the correct
* values when finishing RAW mode or when changing VT's.
*/
}
up_flag = (keycode & SUNKBD_UBIT); /* The 'up' bit */
keycode &= SUNKBD_KMASK; /* all the rest */
del_timer (&auto_repeat_timer);
if(up_flag) {
rep = 0;
clear_bit(keycode, key_down);
} else {
if (!norepeat_keys[keycode]) {
if (kbd_rate_ticks) {
auto_repeat_timer.expires =
jiffies + kbd_delay_ticks;
add_timer (&auto_repeat_timer);
}
}
rep = test_and_set_bit(keycode, key_down);
}
#ifdef CONFIG_MAGIC_SYSRQ /* Handle the SysRq hack */
if (l1a_state.l1_down) {
if (!up_flag)
handle_sysrq(sun_sysrq_xlate[keycode], pt_regs, tty);
goto out;
}
#endif
if(raw_mode)
goto out;
if(kbd->kbdmode == VC_MEDIUMRAW) {
put_queue(keycode + up_flag);
goto out;
}
/*
* Small change in philosophy: earlier we defined repetition by
* rep = keycode == prev_keycode;
* prev_keycode = keycode;
* but now by the fact that the depressed key was down already.
* Does this ever make a difference? Yes.
*/
/*
* Repeat a key only if the input buffers are empty or the
* characters get echoed locally. This makes key repeat usable
* with slow applications and under heavy loads.
*/
if (!rep ||
(vc_kbd_mode(kbd,VC_REPEAT) && tty &&
(L_ECHO(tty) || (tty->driver.chars_in_buffer(tty) == 0)))) {
u_short keysym;
u_char type;
/* the XOR below used to be an OR */
int shift_final = shift_state ^ kbd->lockstate ^ kbd->slockstate;
ushort *key_map = key_maps[shift_final];
if (key_map != NULL) {
keysym = key_map[keycode];
type = KTYP(keysym);
if (type >= 0xf0) {
type -= 0xf0;
if (type == KT_LETTER) {
type = KT_LATIN;
if (vc_kbd_led(kbd, VC_CAPSLOCK)) {
key_map = key_maps[shift_final ^ (1<<KG_SHIFT)];
if (key_map)
keysym = key_map[keycode];
}
}
(*key_handler[type])(keysym & 0xff, up_flag);
if (type != KT_SLOCK)
kbd->slockstate = 0;
}
} else {
/* maybe beep? */
/* we have at least to update shift_state */
sun_compute_shiftstate();
}
}
out:
tasklet_schedule(&keyboard_tasklet);
}
void sunkbd_inchar(unsigned char ch, struct pt_regs *regs)
{
unsigned long flags;
spin_lock_irqsave(&sunkbd_lock, flags);
__sunkbd_inchar(ch, regs);
spin_unlock_irqrestore(&sunkbd_lock, flags);
}
static void put_queue(int ch)
{
if (tty) {
tty_insert_flip_char(tty, ch, 0);
con_schedule_flip(tty);
}
}
static void puts_queue(char *cp)
{
if (!tty)
return;
while (*cp) {
tty_insert_flip_char(tty, *cp, 0);
cp++;
}
con_schedule_flip(tty);
}
static void applkey(int key, char mode)
{
static char buf[] = { 0x1b, 'O', 0x00, 0x00 };
buf[1] = (mode ? 'O' : '[');
buf[2] = key;
puts_queue(buf);
}
static void enter(void)
{
put_queue(13);
if (vc_kbd_mode(kbd,VC_CRLF))
put_queue(10);
}
static void caps_toggle(void)
{
if (rep)
return;
chg_vc_kbd_led(kbd, VC_CAPSLOCK);
}
static void caps_on(void)
{
if (rep)
return;
set_vc_kbd_led(kbd, VC_CAPSLOCK);
}
static void show_ptregs(void)
{
if (pt_regs)
show_regs(pt_regs);
}
static void hold(void)
{
if (rep || !tty)
return;
/*
* Note: SCROLLOCK will be set (cleared) by stop_tty (start_tty);
* these routines are also activated by ^S/^Q.
* (And SCROLLOCK can also be set by the ioctl KDSKBLED.)
*/
if (tty->stopped)
start_tty(tty);
else
stop_tty(tty);
}
static void num(void)
{
if (vc_kbd_mode(kbd,VC_APPLIC))
applkey('P', 1);
else
bare_num();
}
/*
* Bind this to Shift-NumLock if you work in application keypad mode
* but want to be able to change the NumLock flag.
* Bind this to NumLock if you prefer that the NumLock key always
* changes the NumLock flag.
*/
static void bare_num(void)
{
if (!rep)
chg_vc_kbd_led(kbd,VC_NUMLOCK);
}
static void lastcons(void)
{
/* switch to the last used console, ChN */
set_console(last_console);
}
static void decr_console(void)
{
int i;
for (i = fg_console-1; i != fg_console; i--) {
if (i == -1)
i = MAX_NR_CONSOLES-1;
if (vc_cons_allocated(i))
break;
}
set_console(i);
}
static void incr_console(void)
{
int i;
for (i = fg_console+1; i != fg_console; i++) {
if (i == MAX_NR_CONSOLES)
i = 0;
if (vc_cons_allocated(i))
break;
}
set_console(i);
}
static void send_intr(void)
{
if (!tty)
return;
tty_insert_flip_char(tty, 0, TTY_BREAK);
con_schedule_flip(tty);
}
static void scroll_forw(void)
{
scrollfront(0);
}
static void scroll_back(void)
{
scrollback(0);
}
static void boot_it(void)
{
extern int obp_system_intr(void);
if (!obp_system_intr())
ctrl_alt_del();
/* sigh.. attempt to prevent multiple entry */
last_keycode=1;
rep = 0;
}
static void compose(void)
{
dead_key_next = 1;
compose_led_on = 1;
set_leds();
}
#ifdef CONFIG_PCI
extern int spawnpid, spawnsig;
#else
int spawnpid, spawnsig;
#endif
static void spawn_console(void)
{
if (spawnpid)
if(kill_proc(spawnpid, spawnsig, 1))
spawnpid = 0;
}
static void SAK(void)
{
do_SAK(tty);
#if 0
/*
* Need to fix SAK handling to fix up RAW/MEDIUM_RAW and
* vt_cons modes before we can enable RAW/MEDIUM_RAW SAK
* handling.
*
* We should do this some day --- the whole point of a secure
* attention key is that it should be guaranteed to always
* work.
*/
reset_vc(fg_console);
do_unblank_screen(); /* not in interrupt routine? */
#endif
}
static void do_ignore(unsigned char value, char up_flag)
{
}
static void do_null()
{
sun_compute_shiftstate();
}
static void do_spec(unsigned char value, char up_flag)
{
if (up_flag)
return;
if (value >= SIZE(spec_fn_table))
return;
spec_fn_table[value]();
}
static void do_lowercase(unsigned char value, char up_flag)
{
printk("keyboard.c: do_lowercase was called - impossible\n");
}
static void do_self(unsigned char value, char up_flag)
{
if (up_flag)
return; /* no action, if this is a key release */
if (diacr) {
value = handle_diacr(value);
compose_led_on = 0;
set_leds();
}
if (dead_key_next) {
dead_key_next = 0;
diacr = value;
return;
}
put_queue(value);
}
#define A_GRAVE '`'
#define A_ACUTE '\''
#define A_CFLEX '^'
#define A_TILDE '~'
#define A_DIAER '"'
#define A_CEDIL ','
static unsigned char ret_diacr[NR_DEAD] =
{A_GRAVE, A_ACUTE, A_CFLEX, A_TILDE, A_DIAER, A_CEDIL };
/* If a dead key pressed twice, output a character corresponding to it, */
/* otherwise just remember the dead key. */
static void do_dead(unsigned char value, char up_flag)
{
if (up_flag)
return;
value = ret_diacr[value];
if (diacr == value) { /* pressed twice */
diacr = 0;
put_queue(value);
return;
}
diacr = value;
}
/* If space is pressed, return the character corresponding the pending */
/* dead key, otherwise try to combine the two. */
unsigned char handle_diacr(unsigned char ch)
{
int d = diacr;
int i;
diacr = 0;
if (ch == ' ')
return d;
for (i = 0; i < accent_table_size; i++) {
if (accent_table[i].diacr == d && accent_table[i].base == ch)
return accent_table[i].result;
}
put_queue(d);
return ch;
}
static void do_cons(unsigned char value, char up_flag)
{
if (up_flag)
return;
set_console(value);
}
static void do_fn(unsigned char value, char up_flag)
{
if (up_flag)
return;
if (value < SIZE(func_table)) {
if (func_table[value])
puts_queue(func_table[value]);
} else
printk("do_fn called with value=%d\n", value);
}
static void do_pad(unsigned char value, char up_flag)
{
static const char *pad_chars = "0123456789+-*/\015,.?";
static const char *app_map = "pqrstuvwxylSRQMnn?";
if (up_flag)
return; /* no action, if this is a key release */
/* kludge... shift forces cursor/number keys */
if (vc_kbd_mode(kbd,VC_APPLIC) && !k_down[KG_SHIFT]) {
applkey(app_map[value], 1);
return;
}
if (!vc_kbd_led(kbd,VC_NUMLOCK))
switch (value) {
case KVAL(K_PCOMMA):
case KVAL(K_PDOT):
do_fn(KVAL(K_REMOVE), 0);
return;
case KVAL(K_P0):
do_fn(KVAL(K_INSERT), 0);
return;
case KVAL(K_P1):
do_fn(KVAL(K_SELECT), 0);
return;
case KVAL(K_P2):
do_cur(KVAL(K_DOWN), 0);
return;
case KVAL(K_P3):
do_fn(KVAL(K_PGDN), 0);
return;
case KVAL(K_P4):
do_cur(KVAL(K_LEFT), 0);
return;
case KVAL(K_P6):
do_cur(KVAL(K_RIGHT), 0);
return;
case KVAL(K_P7):
do_fn(KVAL(K_FIND), 0);
return;
case KVAL(K_P8):
do_cur(KVAL(K_UP), 0);
return;
case KVAL(K_P9):
do_fn(KVAL(K_PGUP), 0);
return;
case KVAL(K_P5):
applkey('G', vc_kbd_mode(kbd, VC_APPLIC));
return;
}
put_queue(pad_chars[value]);
if (value == KVAL(K_PENTER) && vc_kbd_mode(kbd, VC_CRLF))
put_queue(10);
}
static void do_cur(unsigned char value, char up_flag)
{
static const char *cur_chars = "BDCA";
if (up_flag)
return;
applkey(cur_chars[value], vc_kbd_mode(kbd,VC_CKMODE));
}
static void do_shift(unsigned char value, char up_flag)
{
int old_state = shift_state;
if (rep)
return;
/* Mimic typewriter:
a CapsShift key acts like Shift but undoes CapsLock */
if (value == KVAL(K_CAPSSHIFT)) {
value = KVAL(K_SHIFT);
if (!up_flag)
clr_vc_kbd_led(kbd, VC_CAPSLOCK);
}
if (up_flag) {
/* handle the case that two shift or control
keys are depressed simultaneously */
if (k_down[value])
k_down[value]--;
} else
k_down[value]++;
if (k_down[value])
shift_state |= (1 << value);
else
shift_state &= ~ (1 << value);
/* kludge, no joke... */
if (up_flag && shift_state != old_state && npadch != -1) {
put_queue(npadch & 0xff);
npadch = -1;
}
}
/* called after returning from RAW mode or when changing consoles -
recompute k_down[] and shift_state from key_down[] */
/* maybe called when keymap is undefined, so that shiftkey release is seen */
void sun_compute_shiftstate(void)
{
int i, j, k, sym, val;
shift_state = 0;
for(i=0; i < SIZE(k_down); i++)
k_down[i] = 0;
for(i=0; i < SIZE(key_down); i++)
if(key_down[i]) { /* skip this word if not a single bit on */
k = i*BITS_PER_LONG;
for(j=0; j<BITS_PER_LONG; j++,k++)
if(test_bit(k, key_down)) {
sym = U(plain_map[k]);
if(KTYP(sym) == KT_SHIFT) {
val = KVAL(sym);
if (val == KVAL(K_CAPSSHIFT))
val = KVAL(K_SHIFT);
k_down[val]++;
shift_state |= (1<<val);
}
}
}
}
static void do_meta(unsigned char value, char up_flag)
{
if (up_flag)
return;
if (vc_kbd_mode(kbd, VC_META)) {
put_queue('\033');
put_queue(value);
} else
put_queue(value | 0x80);
}
static void do_ascii(unsigned char value, char up_flag)
{
int base;
if (up_flag)
return;
if (value < 10) /* decimal input of code, while Alt depressed */
base = 10;
else { /* hexadecimal input of code, while AltGr depressed */
value -= 10;
base = 16;
}
if (npadch == -1)
npadch = value;
else
npadch = npadch * base + value;
}
static void do_lock(unsigned char value, char up_flag)
{
if (up_flag || rep)
return;
chg_vc_kbd_lock(kbd, value);
}
/*
* The leds display either (i) the status of NumLock, CapsLock, ScrollLock,
* or (ii) whatever pattern of lights people want to show using KDSETLED,
* or (iii) specified bits of specified words in kernel memory.
*/
static unsigned char ledstate = 0xff; /* undefined */
static unsigned char ledioctl;
unsigned char sun_getledstate(void) {
return ledstate;
}
void sun_setledstate(struct kbd_struct *kbd, unsigned int led) {
if (!(led & ~7)) {
ledioctl = led;
kbd->ledmode = LED_SHOW_IOCTL;
} else
kbd->ledmode = LED_SHOW_FLAGS;
set_leds();
}
static struct ledptr {
unsigned int *addr;
unsigned int mask;
unsigned char valid:1;
} ledptrs[3];
void register_leds(int console, unsigned int led,
unsigned int *addr, unsigned int mask) {
struct kbd_struct *kbd = kbd_table + console;
if (led < 3) {
ledptrs[led].addr = addr;
ledptrs[led].mask = mask;
ledptrs[led].valid = 1;
kbd->ledmode = LED_SHOW_MEM;
} else
kbd->ledmode = LED_SHOW_FLAGS;
}
static inline unsigned char getleds(void){
struct kbd_struct *kbd = kbd_table + fg_console;
unsigned char leds;
if (kbd->ledmode == LED_SHOW_IOCTL)
return ledioctl;
leds = kbd->ledflagstate;
if (kbd->ledmode == LED_SHOW_MEM) {
if (ledptrs[0].valid) {
if (*ledptrs[0].addr & ledptrs[0].mask)
leds |= 1;
else
leds &= ~1;
}
if (ledptrs[1].valid) {
if (*ledptrs[1].addr & ledptrs[1].mask)
leds |= 2;
else
leds &= ~2;
}
if (ledptrs[2].valid) {
if (*ledptrs[2].addr & ledptrs[2].mask)
leds |= 4;
else
leds &= ~4;
}
}
return leds;
}
/*
* This routine is the bottom half of the keyboard interrupt
* routine, and runs with all interrupts enabled. It does
* console changing, led setting and copy_to_cooked, which can
* take a reasonably long time.
*
* Aside from timing (which isn't really that important for
* keyboard interrupts as they happen often), using the software
* interrupt routines for this thing allows us to easily mask
* this when we don't want any of the above to happen. Not yet
* used, but this allows for easy and efficient race-condition
* prevention later on.
*/
static unsigned char sunkbd_ledstate = 0xff; /* undefined */
void sun_kbd_bh(unsigned long dummy)
{
unsigned long flags;
unsigned char leds, kbd_leds;
spin_lock_irqsave(&sunkbd_lock, flags);
leds = getleds();
kbd_leds = vcleds_to_sunkbd(leds);
if (kbd_leds != sunkbd_ledstate) {
ledstate = leds;
sunkbd_ledstate = kbd_leds;
send_cmd(SKBDCMD_SETLED);
send_cmd(kbd_leds);
}
spin_unlock_irqrestore(&sunkbd_lock, flags);
}
/* Support for keyboard "beeps". */
/* Timer routine to turn off the beep after the interval expires. */
static void sunkbd_kd_nosound(unsigned long __unused)
{
unsigned long flags;
spin_lock_irqsave(&sunkbd_lock, flags);
send_cmd(SKBDCMD_BELLOFF);
spin_unlock_irqrestore(&sunkbd_lock, flags);
}
/*
* Initiate a keyboard beep. If the frequency is zero, then we stop
* the beep. Any other frequency will start a monotone beep. The beep
* will be stopped by a timer after "ticks" jiffies. If ticks is 0,
* then we do not start a timer.
*/
static void sunkbd_kd_mksound(unsigned int hz, unsigned int ticks)
{
unsigned long flags;
static struct timer_list sound_timer = { function: sunkbd_kd_nosound };
spin_lock_irqsave(&sunkbd_lock, flags);
del_timer(&sound_timer);
if (hz) {
send_cmd(SKBDCMD_BELLON);
if (ticks) {
sound_timer.expires = jiffies + ticks;
add_timer(&sound_timer);
}
} else
send_cmd(SKBDCMD_BELLOFF);
spin_unlock_irqrestore(&sunkbd_lock, flags);
}
extern void (*kd_mksound)(unsigned int hz, unsigned int ticks);
int __init sun_kbd_init(void)
{
int i, opt_node;
struct kbd_struct kbd0;
extern struct tty_driver console_driver;
kbd0.ledflagstate = kbd0.default_ledflagstate = KBD_DEFLEDS;
kbd0.ledmode = LED_SHOW_FLAGS;
kbd0.lockstate = KBD_DEFLOCK;
kbd0.slockstate = 0;
kbd0.modeflags = KBD_DEFMODE;
kbd0.kbdmode = VC_XLATE;
for (i = 0 ; i < MAX_NR_CONSOLES ; i++)
kbd_table[i] = kbd0;
ttytab = console_driver.table;
kd_mksound = sunkbd_kd_mksound;
/* XXX Check keyboard-click? property in 'options' PROM node XXX */
if(sparc_cpu_model != sun4) {
opt_node = prom_getchild(prom_root_node);
opt_node = prom_searchsiblings(opt_node, "options");
i = prom_getintdefault(opt_node, "keyboard-click?", -1);
if(i != -1)
sunkbd_clickp = 1;
else
sunkbd_clickp = 0;
} else {
sunkbd_clickp = 0;
}
keyboard_tasklet.func = sun_kbd_bh;
tasklet_enable(&keyboard_tasklet);
tasklet_schedule(&keyboard_tasklet);
return 0;
}
/* /dev/kbd support */
#define KBD_QSIZE 32
static Firm_event kbd_queue [KBD_QSIZE];
static int kbd_head, kbd_tail;
static spinlock_t kbd_queue_lock = SPIN_LOCK_UNLOCKED;
char kbd_opened;
static int kbd_active = 0;
static DECLARE_WAIT_QUEUE_HEAD(kbd_wait);
static struct fasync_struct *kb_fasync;
void
push_kbd (int scan)
{
unsigned long flags;
int next;
if (scan == KBD_IDLE)
return;
spin_lock_irqsave(&kbd_queue_lock, flags);
next = (kbd_head + 1) % KBD_QSIZE;
if (next != kbd_tail){
kbd_queue [kbd_head].id = scan & KBD_KEYMASK;
kbd_queue [kbd_head].value=scan & KBD_UP ? VKEY_UP : VKEY_DOWN;
kbd_queue [kbd_head].time = xtime;
kbd_head = next;
}
spin_unlock_irqrestore(&kbd_queue_lock, flags);
kill_fasync (&kb_fasync, SIGIO, POLL_IN);
wake_up_interruptible (&kbd_wait);
}
static ssize_t
kbd_read (struct file *f, char *buffer, size_t count, loff_t *ppos)
{
DECLARE_WAITQUEUE(wait, current);
unsigned long flags;
char *end, *p;
/* Return EWOULDBLOCK, because this is what the X server expects */
if (kbd_head == kbd_tail){
if (f->f_flags & O_NONBLOCK)
return -EWOULDBLOCK;
add_wait_queue (&kbd_wait, &wait);
repeat:
set_current_state(TASK_INTERRUPTIBLE);
if (kbd_head == kbd_tail && !signal_pending(current)) {
schedule();
goto repeat;
}
current->state = TASK_RUNNING;
remove_wait_queue (&kbd_wait, &wait);
}
/* There is data in the keyboard, fill the user buffer */
end = buffer+count;
p = buffer;
spin_lock_irqsave(&kbd_queue_lock, flags);
for (; p < end && kbd_head != kbd_tail;){
Firm_event this_event = kbd_queue[kbd_tail];
kbd_tail = (kbd_tail + 1) % KBD_QSIZE;
spin_unlock_irqrestore(&kbd_queue_lock, flags);
#ifdef CONFIG_SPARC32_COMPAT
if (test_thread_flag(TIF_32BIT)) {
if (copy_to_user((Firm_event *)p, &this_event,
sizeof(Firm_event)-sizeof(struct timeval)))
return -EFAULT;
p += sizeof(Firm_event)-sizeof(struct timeval);
if (__put_user(this_event.time.tv_sec, (u32 *)p))
return -EFAULT;
p += sizeof(u32);
if (__put_user(this_event.time.tv_usec, (u32 *)p))
return -EFAULT;
p += sizeof(u32);
} else
#endif
{
if (copy_to_user((Firm_event *)p, &this_event,
sizeof(Firm_event)))
return -EFAULT;
p += sizeof (Firm_event);
}
#ifdef KBD_DEBUG
printk ("[%s]", this_event.value == VKEY_UP ? "UP" : "DOWN");
#endif
spin_lock_irqsave(&kbd_queue_lock, flags);
}
spin_unlock_irqrestore(&kbd_queue_lock, flags);
return p-buffer;
}
/* Needed by X */
static int kbd_fasync (int fd, struct file *filp, int on)
{
int retval;
retval = fasync_helper (fd, filp, on, &kb_fasync);
if (retval < 0)
return retval;
return 0;
}
static unsigned int kbd_poll (struct file *f, poll_table *wait)
{
poll_wait(f, &kbd_wait, wait);
if (kbd_head != kbd_tail)
return POLLIN | POLLRDNORM;
return 0;
}
static int
kbd_ioctl (struct inode *i, struct file *f, unsigned int cmd, unsigned long arg)
{
unsigned char c;
unsigned char leds = 0;
int value;
switch (cmd){
case KIOCTYPE: /* return keyboard type */
if (put_user(sunkbd_type, (int *) arg))
return -EFAULT;
break;
case KIOCGTRANS:
if (put_user(TR_UNTRANS_EVENT, (int *) arg))
return -EFAULT;
break;
case KIOCTRANS:
if (get_user(value, (int *) arg))
return -EFAULT;
if (value != TR_UNTRANS_EVENT)
return -EINVAL;
break;
case KIOCLAYOUT:
if (put_user(sunkbd_layout, (int *) arg))
return -EFAULT;
break;
case KIOCSDIRECT:
#ifndef CODING_NEW_DRIVER
if (get_user(value, (int *) arg))
return -EFAULT;
if(value)
kbd_redirected = fg_console + 1;
else
kbd_redirected = 0;
kbd_table [fg_console].kbdmode = kbd_redirected ? VC_RAW : VC_XLATE;
#endif
break;
case KIOCCMD:
if (get_user(value, (int *) arg))
return -EFAULT;
c = (unsigned char) value;
switch (c) {
case SKBDCMD_CLICK:
case SKBDCMD_NOCLICK:
spin_lock_irq(&sunkbd_lock);
send_cmd(c);
spin_unlock_irq(&sunkbd_lock);
return 0;
case SKBDCMD_BELLON:
kd_mksound(1,0);
return 0;
case SKBDCMD_BELLOFF:
kd_mksound(0,0);
return 0;
default:
return -EINVAL;
}
case KIOCSLED:
if (get_user(c, (unsigned char *) arg))
return -EFAULT;
if (c & LED_SCRLCK) leds |= (1 << VC_SCROLLOCK);
if (c & LED_NLOCK) leds |= (1 << VC_NUMLOCK);
if (c & LED_CLOCK) leds |= (1 << VC_CAPSLOCK);
compose_led_on = !!(c & LED_CMPOSE);
sun_setledstate(kbd_table + fg_console, leds);
break;
case KIOCGLED:
if (put_user(vcleds_to_sunkbd(getleds()), (unsigned char *) arg))
return -EFAULT;
break;
case KIOCGRATE:
{
struct kbd_rate rate;
rate.delay = kbd_delay_ticks;
if (kbd_rate_ticks)
rate.rate = HZ / kbd_rate_ticks;
else
rate.rate = 0;
if (copy_to_user((struct kbd_rate *)arg, &rate,
sizeof(struct kbd_rate)))
return -EFAULT;
return 0;
}
case KIOCSRATE:
{
struct kbd_rate rate;
if (verify_area(VERIFY_READ, (void *)arg,
sizeof(struct kbd_rate)))
return -EFAULT;
copy_from_user(&rate, (struct kbd_rate *)arg,
sizeof(struct kbd_rate));
if (rate.rate > 50)
return -EINVAL;
if (rate.rate == 0)
kbd_rate_ticks = 0;
else
kbd_rate_ticks = HZ / rate.rate;
kbd_delay_ticks = rate.delay;
return 0;
}
case FIONREAD: /* return number of bytes in kbd queue */
{
int count;
count = kbd_head - kbd_tail;
if (put_user((count < 0) ? KBD_QSIZE - count : count, (int *) arg))
return -EFAULT;
return 0;
}
default:
printk ("Unknown Keyboard ioctl: %8.8x\n", cmd);
return -EINVAL;
}
return 0;
}
static int
kbd_open (struct inode *i, struct file *f)
{
spin_lock_irq(&kbd_queue_lock);
kbd_active++;
if (kbd_opened)
goto out;
kbd_opened = fg_console + 1;
kbd_head = kbd_tail = 0;
out:
spin_unlock_irq(&kbd_queue_lock);
return 0;
}
static int
kbd_close (struct inode *i, struct file *f)
{
spin_lock_irq(&kbd_queue_lock);
if (!--kbd_active) {
if (kbd_redirected)
kbd_table [kbd_redirected-1].kbdmode = VC_XLATE;
kbd_redirected = 0;
kbd_opened = 0;
kbd_fasync (-1, f, 0);
}
spin_unlock_irq(&kbd_queue_lock);
return 0;
}
static struct file_operations kbd_fops =
{
.read = kbd_read,
.poll = kbd_poll,
.ioctl = kbd_ioctl,
.open = kbd_open,
.release = kbd_close,
.fasync = kbd_fasync,
};
void __init keyboard_zsinit(void (*put_char)(unsigned char))
{
int timeout = 0;
kbd_put_char = put_char;
if (!kbd_put_char)
panic("keyboard_zsinit: no put_char parameter");
/* Test out the leds */
sunkbd_type = 255;
sunkbd_layout = 0;
send_cmd(SKBDCMD_RESET);
send_cmd(SKBDCMD_RESET);
while((sunkbd_type==255) && timeout++ < 25000) {
udelay(100);
barrier();
}
if(timeout>=25000) {
printk("keyboard: not present\n");
return;
}
if(sunkbd_type != SUNKBD_TYPE4) {
printk("Sun TYPE %d keyboard detected ", sunkbd_type);
} else {
timeout=0;
while((sunkbd_layout==0) && timeout++ < 10000) {
udelay(100);
barrier();
}
printk("Sun TYPE %d keyboard detected ",
((sunkbd_layout & SUNKBD_LOUT_TYP5_MASK) ? 5 : 4));
}
if(sunkbd_type == SUNKBD_TYPE2)
sunkbd_clickp = 0;
spin_lock_irq(&sunkbd_lock);
if(sunkbd_clickp) {
send_cmd(SKBDCMD_CLICK);
printk("with keyclick\n");
} else {
send_cmd(SKBDCMD_NOCLICK);
printk("without keyclick\n");
}
/* Dork with led lights, then turn them all off */
send_cmd(SKBDCMD_SETLED); send_cmd(0xf); /* All on */
send_cmd(SKBDCMD_SETLED); send_cmd(0x0); /* All off */
spin_unlock_irq(&sunkbd_lock);
/* Register the /dev/kbd interface */
devfs_register (NULL, "kbd", DEVFS_FL_DEFAULT,
KBD_MAJOR, 0,
S_IFCHR | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH,
&kbd_fops, NULL);
if (devfs_register_chrdev (KBD_MAJOR, "kbd", &kbd_fops)){
printk ("Could not register /dev/kbd device\n");
return;
}
return;
}
/* $Id: sunkbd.h,v 1.4 2000/02/09 11:15:54 davem Exp $
* sunkbd.h: Defines needed by SUN Keyboard drivers
*
* Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be)
*/
#ifndef _SPARC_SUNKBD_H
#define _SPARC_SUNKBD_H 1
#include <linux/config.h>
/* Keyboard defines for L1-A processing... */
#define SUNKBD_RESET 0xff
#define SUNKBD_L1 0x01
#define SUNKBD_UP 0x80
#define SUNKBD_A 0x4d
struct l1a_kbd_state {
int kbd_id;
int l1_down;
};
extern struct l1a_kbd_state l1a_state;
extern void keyboard_zsinit(void (*kbd_put_char)(unsigned char));
extern void sunkbd_inchar(unsigned char, struct pt_regs *);
extern void batten_down_hatches(void);
extern void sun_kbd_bh(unsigned long);
extern int sun_kbd_init(void);
extern void sun_compute_shiftstate(void);
extern void sun_setledstate(struct kbd_struct *, unsigned int);
extern unsigned char sun_getledstate(void);
extern int sun_setkeycode(unsigned int, unsigned int);
extern int sun_getkeycode(unsigned int);
#ifdef CONFIG_PCI
extern ushort *sun_key_maps[MAX_NR_KEYMAPS];
extern unsigned int sun_keymap_count;
extern char sun_func_buf[];
extern char *sun_func_table[MAX_NR_FUNC];
extern int sun_funcbufsize;
extern int sun_funcbufleft;
extern struct kbdiacr sun_accent_table[MAX_DIACR];
extern unsigned int sun_accent_table_size;
#endif /* CONFIG_PCI */
#endif /* !(_SPARC_SUNKBD_H) */
/* sunmouse.c: Sun mouse driver for the Sparc
*
* Copyright (C) 1995, 1996, 1997 David S. Miller (davem@caip.rutgers.edu)
* Copyright (C) 1995 Miguel de Icaza (miguel@nuclecu.unam.mx)
*
* Parts based on the psaux.c driver written by:
* Johan Myreen.
*
* Dec/19/95 Added SunOS mouse ioctls - miguel.
* Jan/5/96 Added VUID support, sigio support - miguel.
* Mar/5/96 Added proper mouse stream support - miguel.
* Sep/96 Allow more than one reader -miguel.
* Aug/97 Added PCI 8042 controller support -DaveM
*/
/* The mouse is run off of one of the Zilog serial ports. On
* that port is the mouse and the keyboard, each gets a zs channel.
* The mouse itself is mouse-systems in nature. So the protocol is:
*
* Byte 1) Button state which is bit-encoded as
* 0x4 == left-button down, else up
* 0x2 == middle-button down, else up
* 0x1 == right-button down, else up
*
* Byte 2) Delta-x
* Byte 3) Delta-y
* Byte 4) Delta-x again
* Byte 5) Delta-y again
*
* One day this driver will have to support more than one mouse in the system.
*
* This driver has two modes of operation: the default VUID_NATIVE is
* set when the device is opened and allows the application to see the
* mouse character stream as we get it from the serial (for gpm for
* example). The second method, VUID_FIRM_EVENT will provide cooked
* events in Firm_event records as expected by SunOS/Solaris applications.
*
* FIXME: We need to support more than one mouse.
* */
#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fcntl.h>
#include <linux/signal.h>
#include <linux/timer.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/poll.h>
#include <linux/spinlock.h>
#include <linux/smp_lock.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <asm/vuid_event.h>
#include <linux/random.h>
/* The following keeps track of software state for the Sun
* mouse.
*/
#define STREAM_SIZE 2048
#define EV_SIZE (STREAM_SIZE/sizeof (Firm_event))
#define BUTTON_LEFT 4
#define BUTTON_MIDDLE 2
#define BUTTON_RIGHT 1
struct sun_mouse {
unsigned char transaction[5]; /* Each protocol transaction */
unsigned char byte; /* Counter, starts at 0 */
unsigned char button_state; /* Current button state */
unsigned char prev_state; /* Previous button state */
int delta_x; /* Current delta-x */
int delta_y; /* Current delta-y */
int active; /* set if device is open */
int vuid_mode; /* VUID_NATIVE or VUID_FIRM_EVENT */
wait_queue_head_t proc_list;
struct fasync_struct *fasync;
/* The event/stream queue */
spinlock_t lock;
unsigned int head;
unsigned int tail;
union {
char stream [STREAM_SIZE];
Firm_event ev [EV_SIZE];
} queue;
};
static struct sun_mouse sunmouse;
#define gen_events (sunmouse.vuid_mode != VUID_NATIVE)
#define bstate sunmouse.button_state
#define pstate sunmouse.prev_state
extern void mouse_put_char(char ch);
#undef SMOUSE_DEBUG
static int
push_event (Firm_event *ev)
{
unsigned long flags;
int next, ret;
spin_lock_irqsave(&sunmouse.lock, flags);
next = (sunmouse.head + 1) % EV_SIZE;
ret = 0;
if (next != sunmouse.tail) {
sunmouse.queue.ev [sunmouse.head] = *ev;
sunmouse.head = next;
ret = 1;
}
spin_unlock_irqrestore(&sunmouse.lock, flags);
return ret;
}
static int
queue_empty (void)
{
return sunmouse.head == sunmouse.tail;
}
/* Must be invoked under the sunmouse.lock */
static void get_from_queue (Firm_event *p)
{
*p = sunmouse.queue.ev [sunmouse.tail];
sunmouse.tail = (sunmouse.tail + 1) % EV_SIZE;
}
static void
push_char (char c)
{
unsigned long flags;
int next;
spin_lock_irqsave(&sunmouse.lock, flags);
next = (sunmouse.head + 1) % STREAM_SIZE;
if (next != sunmouse.tail) {
#ifdef SMOUSE_DEBUG
printk("P<%02x>\n", (unsigned char)c);
#endif
sunmouse.queue.stream [sunmouse.head] = c;
sunmouse.head = next;
}
spin_unlock_irqrestore(&sunmouse.lock, flags);
kill_fasync (&sunmouse.fasync, SIGIO, POLL_IN);
wake_up_interruptible (&sunmouse.proc_list);
}
/* Auto baud rate "detection". ;-) */
static int mouse_baud = 4800; /* Initial rate set by zilog driver. */
/* Change the baud rate after receiving too many "bogon bytes". */
void sun_mouse_change_baud(void)
{
extern void rs_change_mouse_baud(int newbaud);
if(mouse_baud == 1200)
mouse_baud = 2400;
else if(mouse_baud == 2400)
mouse_baud = 4800;
else if(mouse_baud == 4800)
mouse_baud = 9600;
else
mouse_baud = 1200;
rs_change_mouse_baud(mouse_baud);
}
/* This tries to monitor the mouse state so that it
* can automatically adjust to the correct baud rate.
* The mouse spits out BRKs when the baud rate is
* incorrect.
*
* It returns non-zero if we should ignore this byte.
*/
int mouse_baud_detection(unsigned char c, int is_break)
{
static int mouse_got_break = 0;
static int ctr = 0;
if (is_break) {
/* Let a few normal bytes go by before
* we jump the gun and say we need to
* try another baud rate.
*/
if (mouse_got_break && ctr < 8)
return 1;
/* OK, we need to try another baud rate. */
sun_mouse_change_baud();
ctr = 0;
mouse_got_break = 1;
return 1;
}
if (mouse_got_break) {
ctr++;
if (c == 0x87) {
printk(KERN_INFO "sunmouse: Successfully "
"adjusted to %d baud.\n", mouse_baud);
mouse_got_break = 0;
}
return 1;
}
return 0;
}
/* You ask me, why does this cap the lower bound at -127 and not
* -128? Because the xf86 mouse code is crap and treats -128
* as an illegal value and resets it's protocol state machine
* when it sees this value.
*/
#define CLIP(__X) (((__X) > 127) ? 127 : (((__X) < -127) ? -127 : (__X)))
/* The following is called from the serial driver when bytes/breaks
* are received on the Mouse line.
*/
void
sun_mouse_inbyte(unsigned char byte, int is_break)
{
signed char mvalue;
int d, pushed = 0;
Firm_event ev;
add_mouse_randomness (byte);
#if 0
{
static int xxx = 0;
printk("mouse(%02x:%d) ",
byte, is_break);
if (byte == 0x87) {
xxx = 0;
printk("\n");
}
}
#endif
if (mouse_baud_detection(byte, is_break))
return;
if(!sunmouse.active)
return;
/* Ignore this if it is garbage. */
if (sunmouse.byte == 69) {
if (byte != 0x87)
return;
/* Ok, we've begun the state machine. */
sunmouse.byte = 0;
}
#if 0
/* If the mouse sends us a byte from 0x80 to 0x87
* we are starting at byte zero in the transaction
* protocol.
*/
if((byte & ~0x0f) == 0x80)
sunmouse.byte = 0;
#endif
mvalue = (signed char) byte;
switch(sunmouse.byte) {
case 0:
/* If we get a bogus button byte, just skip it.
* When we get here the baud detection code has
* passed, so the only other things which can
* cause this are dropped serial characters and
* confused mouse. We check this because otherwise
* begin posting erroneous mouse events.
*/
if ((byte & 0xf0) != 0x80)
return;
/* Button state */
sunmouse.button_state = (~byte) & 0x7;
#ifdef SMOUSE_DEBUG
printk("B<Left %s, Middle %s, Right %s>",
((sunmouse.button_state & 0x4) ? "DOWN" : "UP"),
((sunmouse.button_state & 0x2) ? "DOWN" : "UP"),
((sunmouse.button_state & 0x1) ? "DOWN" : "UP"));
#endif
/* To deal with the Sparcbook 3 */
if (byte & 0x8) {
sunmouse.byte += 2;
sunmouse.delta_y = 0;
sunmouse.delta_x = 0;
}
sunmouse.byte++;
return;
case 1:
/* Delta-x 1 */
#ifdef SMOUSE_DEBUG
printk("DX1<%d>", mvalue);
#endif
sunmouse.delta_x = mvalue;
sunmouse.byte++;
return;
case 2:
/* Delta-y 1 */
#ifdef SMOUSE_DEBUG
printk("DY1<%d>", mvalue);
#endif
sunmouse.delta_y = mvalue;
sunmouse.byte++;
return;
case 3:
/* Delta-x 2 */
#ifdef SMOUSE_DEBUG
printk("DX2<%d>", mvalue);
#endif
sunmouse.delta_x += mvalue;
sunmouse.delta_x = CLIP(sunmouse.delta_x);
sunmouse.byte++;
return;
case 4:
/* Last byte, Delta-y 2 */
#ifdef SMOUSE_DEBUG
printk("DY2<%d>", mvalue);
#endif
sunmouse.delta_y += mvalue;
sunmouse.delta_y = CLIP(sunmouse.delta_y);
sunmouse.byte = 0; /* Back to button state */
break;
case 69:
/* Until we get the (0x80 -> 0x87) value we aren't
* in the middle of a real transaction, so just
* return.
*/
return;
default:
printk("sunmouse: bogon transaction state\n");
sunmouse.byte = 69; /* What could cause this? */
return;
};
if (!gen_events) {
push_char (~sunmouse.button_state & 0x87);
push_char (sunmouse.delta_x);
push_char (sunmouse.delta_y);
return;
}
d = bstate ^ pstate;
pstate = bstate;
if (d) {
if (d & BUTTON_LEFT) {
ev.id = MS_LEFT;
ev.value = bstate & BUTTON_LEFT;
}
if (d & BUTTON_RIGHT) {
ev.id = MS_RIGHT;
ev.value = bstate & BUTTON_RIGHT;
}
if (d & BUTTON_MIDDLE) {
ev.id = MS_MIDDLE;
ev.value = bstate & BUTTON_MIDDLE;
}
ev.time = xtime;
ev.value = ev.value ? VKEY_DOWN : VKEY_UP;
pushed += push_event (&ev);
}
if (sunmouse.delta_x) {
ev.id = LOC_X_DELTA;
ev.time = xtime;
ev.value = sunmouse.delta_x;
pushed += push_event (&ev);
sunmouse.delta_x = 0;
}
if (sunmouse.delta_y) {
ev.id = LOC_Y_DELTA;
ev.time = xtime;
ev.value = sunmouse.delta_y;
pushed += push_event (&ev);
}
if (pushed != 0) {
/* We just completed a transaction, wake up whoever is awaiting
* this event.
*/
kill_fasync (&sunmouse.fasync, SIGIO, POLL_IN);
wake_up_interruptible(&sunmouse.proc_list);
}
return;
}
static int
sun_mouse_open(struct inode * inode, struct file * file)
{
spin_lock_irq(&sunmouse.lock);
if (sunmouse.active++)
goto out;
sunmouse.delta_x = sunmouse.delta_y = 0;
sunmouse.button_state = 0x80;
sunmouse.vuid_mode = VUID_NATIVE;
out:
spin_unlock_irq(&sunmouse.lock);
return 0;
}
static int sun_mouse_fasync (int fd, struct file *filp, int on)
{
int retval;
retval = fasync_helper (fd, filp, on, &sunmouse.fasync);
if (retval < 0)
return retval;
return 0;
}
static int
sun_mouse_close(struct inode *inode, struct file *file)
{
sun_mouse_fasync (-1, file, 0);
spin_lock_irq(&sunmouse.lock);
sunmouse.active--;
spin_unlock_irq(&sunmouse.lock);
return 0;
}
static ssize_t
sun_mouse_write(struct file *file, const char *buffer,
size_t count, loff_t *ppos)
{
return -EINVAL; /* foo on you */
}
static ssize_t
sun_mouse_read(struct file *file, char *buffer,
size_t count, loff_t *ppos)
{
DECLARE_WAITQUEUE(wait, current);
unsigned long flags;
if (queue_empty ()) {
if (file->f_flags & O_NONBLOCK)
return -EWOULDBLOCK;
add_wait_queue (&sunmouse.proc_list, &wait);
repeat:
set_current_state(TASK_INTERRUPTIBLE);
if (queue_empty() && !signal_pending(current)) {
schedule();
goto repeat;
}
current->state = TASK_RUNNING;
remove_wait_queue (&sunmouse.proc_list, &wait);
}
if (gen_events) {
char *p = buffer, *end = buffer+count;
spin_lock_irqsave(&sunmouse.lock, flags);
while (p < end && !queue_empty ()){
Firm_event this_event;
get_from_queue(&this_event);
spin_unlock_irqrestore(&sunmouse.lock, flags);
#ifdef CONFIG_SPARC32_COMPAT
if (test_thread_flag(TIF_32BIT)) {
if ((end - p) <
((sizeof(Firm_event) - sizeof(struct timeval) +
(sizeof(u32) * 2))))
break;
if (copy_to_user((Firm_event *)p, &this_event,
sizeof(Firm_event)-sizeof(struct timeval)))
return -EFAULT;
p += sizeof(Firm_event)-sizeof(struct timeval);
if (__put_user(this_event.time.tv_sec, (u32 *)p))
return -EFAULT;
p += sizeof(u32);
if (__put_user(this_event.time.tv_usec, (u32 *)p))
return -EFAULT;
p += sizeof(u32);
} else
#endif
{
if ((end - p) < sizeof(Firm_event))
break;
if (copy_to_user((Firm_event *)p, &this_event,
sizeof(Firm_event)))
return -EFAULT;
p += sizeof (Firm_event);
}
spin_lock_irqsave(&sunmouse.lock, flags);
}
spin_unlock_irqrestore(&sunmouse.lock, flags);
file->f_dentry->d_inode->i_atime = CURRENT_TIME;
return p-buffer;
} else {
int c, limit = 3;
if (count < limit)
limit = count;
for (c = 0; c < limit; c++) {
unsigned char val;
int empty = 0;
spin_lock_irqsave(&sunmouse.lock, flags);
if (queue_empty()) {
empty = 1;
val = 0;
} else {
val = sunmouse.queue.stream[sunmouse.tail];
sunmouse.tail = (sunmouse.tail + 1) % STREAM_SIZE;
}
spin_unlock_irqrestore(&sunmouse.lock, flags);
if (empty)
break;
put_user(val, buffer);
buffer++;
}
while (c < count) {
if (c >= 5)
break;
put_user(0, buffer);
buffer++;
c++;
}
file->f_dentry->d_inode->i_atime = CURRENT_TIME;
return c;
}
/* Only called if nothing was sent */
if (signal_pending(current))
return -ERESTARTSYS;
return 0;
}
static unsigned int sun_mouse_poll(struct file *file, poll_table *wait)
{
poll_wait(file, &sunmouse.proc_list, wait);
if(!queue_empty())
return POLLIN | POLLRDNORM;
return 0;
}
int
sun_mouse_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
int i;
switch (cmd){
/* VUIDGFORMAT - Get input device byte stream format */
case _IOR('v', 2, int):
if (put_user(sunmouse.vuid_mode, (int *) arg))
return -EFAULT;
break;
/* VUIDSFORMAT - Set input device byte stream format*/
case _IOW('v', 1, int):
if (get_user(i, (int *) arg))
return -EFAULT;
if (i == VUID_NATIVE || i == VUID_FIRM_EVENT){
int value;
if (get_user(value, (int *)arg))
return -EFAULT;
spin_lock_irq(&sunmouse.lock);
sunmouse.vuid_mode = value;
sunmouse.head = sunmouse.tail = 0;
spin_unlock_irq(&sunmouse.lock);
} else
return -EINVAL;
break;
case 0x8024540b:
case 0x40245408:
/* This is a buggy application doing termios on the mouse driver */
/* we ignore it. I keep this check here so that we will notice */
/* future mouse vuid ioctls */
return -ENOTTY;
default:
#ifdef DEBUG
printk ("[MOUSE-ioctl: %8.8x]\n", cmd);
#endif
return -EINVAL;
}
return 0;
}
struct file_operations sun_mouse_fops = {
.read = sun_mouse_read,
.write = sun_mouse_write,
.poll = sun_mouse_poll,
.ioctl = sun_mouse_ioctl,
.open = sun_mouse_open,
.release = sun_mouse_close,
.fasync = sun_mouse_fasync,
};
static struct miscdevice sun_mouse_mouse = {
SUN_MOUSE_MINOR, "sunmouse", &sun_mouse_fops
};
void sun_mouse_zsinit(void)
{
printk("Sun Mouse-Systems mouse driver version 1.00\n");
sunmouse.active = 0;
misc_register (&sun_mouse_mouse);
sunmouse.delta_x = sunmouse.delta_y = 0;
sunmouse.button_state = 0x80;
init_waitqueue_head(&sunmouse.proc_list);
spin_lock_init(&sunmouse.lock);
sunmouse.byte = 69;
}
/* $Id: sunmouse.h,v 1.2 1999/11/19 09:56:34 davem Exp $
* sunmouse.h: Interface to the SUN mouse driver.
*
* Copyright (C) 1997 Eddie C. Dost (ecd@skynet.be)
*/
#ifndef _SPARC_SUNMOUSE_H
#define _SPARC_SUNMOUSE_H 1
extern void sun_mouse_zsinit(void);
extern void sun_mouse_inbyte(unsigned char, int);
#endif /* !(_SPARC_SUNMOUSE_H) */
......@@ -53,9 +53,6 @@ static DECLARE_TASK_QUEUE(tq_serial);
static struct tty_driver serial_driver, callout_driver;
static int sab82532_refcount;
/* number of characters left in xmit buffer before we ask for more */
#define WAKEUP_CHARS 256
#undef SERIAL_PARANOIA_CHECK
#define SERIAL_DO_RESTART
......@@ -2618,7 +2615,6 @@ static struct console sab82532_console = {
int __init sab82532_console_init(void)
{
extern int con_is_present(void);
extern int su_console_registered;
if (con_is_present() || su_console_registered)
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -2,11 +2,9 @@
* sunzilog.c
*
* Driver for Zilog serial chips found on Sun workstations and
* servers. This driver could actually be made more generic
* and anyone wanting to work on doing that should contact
* me. -DaveM
* servers. This driver could actually be made more generic.
*
* This is based on the old drivers/sbus/char/zs.c code, a lot
* This is based on the old drivers/sbus/char/zs.c code. A lot
* of code has been simply moved over directly from there but
* much has been rewritten. Credits therefore go out to Eddie
* C. Dost, Peter Zaitcev, Ted Ts'o and Alex Buell for their
......@@ -15,6 +13,33 @@
* Copyright (C) 2002 David S. Miller (davem@redhat.com)
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/delay.h>
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/major.h>
#include <linux/string.h>
#include <linux/ptrace.h>
#include <linux/ioport.h>
#include <linux/slab.h>
#include <linux/circ_buf.h>
#include <linux/serial.h>
#include <linux/console.h>
#include <linux/spinlock.h>
#ifdef CONFIG_SERIO
#include <linux/serio.h>
#endif
#include <linux/init.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <linux/serial_core.h>
#include "suncore.h"
#include "sunzilog.h"
......@@ -23,10 +48,10 @@
* On 64-bit sparc we only need to flush single writes to ensure
* completion.
*/
#ifndef __sparc_v9__
#ifndef CONFIG_SPARC64
#define ZSDELAY() udelay(5)
#define ZSDELAY_LONG() udelay(20)
#define ZS_WSYNC(channel) do { } while(0)
#define ZS_WSYNC(channel) do { } while (0)
#else
#define ZSDELAY()
#define ZSDELAY_LONG()
......@@ -34,11 +59,16 @@
sbus_readb(&((__channel)->control))
#endif
/* Default setting is sun4/sun4c/sun4m, two chips on board. */
static int num_sunzilog = 2;
static int num_sunzilog;
#define NUM_SUNZILOG num_sunzilog
#define NUM_CHANNELS (NUM_SUNZILOG * 2)
#define KEYBOARD_LINE 0x2
#define MOUSE_LINE 0x3
#define ZS_CLOCK 4915200 /* Zilog input clock rate. */
#define ZS_CLOCK_DIVISOR 16 /* Divisor this driver uses. */
/*
* We wrap our port structure around the generic uart_port.
*/
......@@ -58,6 +88,9 @@ struct uart_sunzilog_port {
#define SUNZILOG_FLAG_IS_KGDB 0x00000008
#define SUNZILOG_FLAG_MODEM_STATUS 0x00000010
#define SUNZILOG_FLAG_IS_CHANNEL_A 0x00000020
#define SUNZILOG_FLAG_REGS_HELD 0x00000040
#define SUNZILOG_FLAG_TX_STOPPED 0x00000080
#define SUNZILOG_FLAG_TX_ACTIVE 0x00000100
/* L1-A keyboard break state. */
int kbd_id;
......@@ -65,6 +98,10 @@ struct uart_sunzilog_port {
unsigned char parity_mask;
unsigned char prev_status;
#ifdef CONFIG_SERIO
struct serio serio;
#endif
};
#define ZILOG_CHANNEL_FROM_PORT(PORT) ((struct zilog_channel *)((PORT)->membase))
......@@ -73,56 +110,15 @@ struct uart_sunzilog_port {
(UART_ZILOG(PORT)->curregs[REGNUM])
#define SUNZILOG_SET_CURR_REG(PORT, REGNUM, REGVAL) \
((UART_ZILOG(PORT)->curregs[REGNUM]) = (REGVAL))
static unsigned char sunzilog_initregs_normal[NUM_ZSREGS] = {
RES_EXT_INT, /* R0 */
0, /* R1 */
0, /* R2 */
Rx8, /* R3 */
PAR_EVEN | X16CLK | SB1, /* R4 */
Tx8, /* R5 */
0, /* R6 */
0, /* R7 */
0, /* R8 */
NV | MIE, /* R9 */
NRZ, /* R10 */
RCBR | TCBR, /* R11 */
0, 0, /* R12, R13 Baud Quotient low, high */
BRSRC | BRENAB, /* R14 */
DCDIE | SYNCIE | CTSIE | BRKIE, /* R15 */
};
static unsigned char sunzilog_initregs_console[NUM_ZSREGS] = {
RES_EXT_INT, /* R0 */
EXT_INT_ENAB | INT_ALL_Rx, /* R1 */
0, /* R2 */
Rx8 | RxENAB, /* R3 */
X16CLK, /* R4 */
DTR | Tx8 | TxENAB, /* R5 */
0, /* R6 */
0, /* R7 */
0, /* R8 */
NV | MIE, /* R9 */
NRZ, /* R10 */
RCBR | TCBR, /* R11 */
0, 0, /* R12, R13 Baud Quotient low, high */
BRSRC | BRENAB, /* R14 */
DCDIE | SYNCIE | CTSIE | BRKIE, /* R15 */
};
static unsigned char sunzilog_initregs_kgdb[NUM_ZSREGS] = {
0, 0, 0 /* R0, R1, R2 */
Rx8 | RxENAB, /* R3 */
PAR_EVEN | X16CLK | SB1, /* R4 */
DTR | Tx8 | TxENAB, /* R5 */
0, 0, 0, /* R6, R7, R8 */
NV, /* R9 */
NRZ, /* R10 */
RCBR | TCBR, /* R11 */
0, 0, /* R12, R13 Baud Quotient low, high */
BRSRC | BRENAB, /* R14 */
DCDIE, /* R15 */
};
#define ZS_IS_KEYB(UP) ((UP)->flags & SUNZILOG_FLAG_CONS_KEYB)
#define ZS_IS_MOUSE(UP) ((UP)->flags & SUNZILOG_FLAG_CONS_MOUSE)
#define ZS_IS_CONS(UP) ((UP)->flags & SUNZILOG_FLAG_IS_CONS)
#define ZS_IS_KGDB(UP) ((UP)->flags & SUNZILOG_FLAG_IS_KGDB)
#define ZS_WANTS_MODEM_STATUS(UP) ((UP)->flags & SUNZILOG_FLAG_MODEM_STATUS)
#define ZS_IS_CHANNEL_A(UP) ((UP)->flags & SUNZILOG_FLAG_IS_CHANNEL_A)
#define ZS_REGS_HELD(UP) ((UP)->flags & SUNZILOG_FLAG_REGS_HELD)
#define ZS_TX_STOPPED(UP) ((UP)->flags & SUNZILOG_FLAG_TX_STOPPED)
#define ZS_TX_ACTIVE(UP) ((UP)->flags & SUNZILOG_FLAG_TX_ACTIVE)
/* Reading and writing Zilog8530 registers. The delays are to make this
* driver work on the Sun4 which needs a settling delay after each chip
......@@ -154,99 +150,181 @@ static void write_zsreg(struct zilog_channel *channel,
ZSDELAY();
}
static void load_zsregs(struct zilog_channel *channel, unsigned char *regs,
int is_channel_a)
static void sunzilog_clear_fifo(struct zilog_channel *channel)
{
int i;
for (i = 0; i < 32; i++) {
unsigned char regval;
regval = sbus_readb(&channel->control);
ZSDELAY();
if (regval & Rx_CH_AV)
break;
regval = read_zsreg(channel, R1);
sbus_readb(&channel->data);
ZSDELAY();
if (regval & (PAR_ERR | Rx_OVR | CRC_ERR)) {
sbus_writeb(ERR_RES, &channel->control);
ZSDELAY();
ZS_WSYNC(channel);
}
}
}
/* This function must only be called when the TX is not busy. The UART
* port lock must be held and local interrupts disabled.
*/
static void __load_zsregs(struct zilog_channel *channel, unsigned char *regs)
{
int i;
/* Let pending transmits finish. */
for (i = 0; i < 1000; i++) {
unsigned char stat = read_zsreg(channel, R1);
if (stat & ALL_SNT)
break;
udelay(100);
}
write_zsreg(channel, R3, 0);
ZS_CLEARSTAT(channel);
ZS_CLEARERR(channel);
ZS_CLEARFIFO(channel);
if (is_channel_a)
write_zsreg(channel, R9, CHRA);
else
write_zsreg(channel, R9, CHRB);
ZSDELAY_LONG();
sbus_writeb(ERR_RES, &channel->control);
ZSDELAY();
ZS_WSYNC(channel);
sunzilog_clear_fifo(channel);
/* Disable all interrupts. */
write_zsreg(channel, R1,
regs[R1] & ~(RxINT_MASK | TxINT_ENAB | EXT_INT_ENAB));
/* Set parity, sync config, stop bits, and clock divisor. */
write_zsreg(channel, R4, regs[R4]);
/* Set misc. TX/RX control bits. */
write_zsreg(channel, R10, regs[R10]);
/* Set TX/RX controls sans the enable bits. */
write_zsreg(channel, R3, regs[R3] & ~RxENAB);
write_zsreg(channel, R5, regs[R5] & ~TxENAB);
write_zsreg(channel, R9, regs[R9] & ~MIE);
write_zsreg(channel, R10, regs[R10]);
/* Synchronous mode config. */
write_zsreg(channel, R6, regs[R6]);
write_zsreg(channel, R7, regs[R7]);
/* Don't mess with the interrupt vector (R2, unused by us) and
* master interrupt control (R9). We make sure this is setup
* properly at probe time then never touch it again.
*/
/* Disable baud generator. */
write_zsreg(channel, R14, regs[R14] & ~BRENAB);
/* Clock mode control. */
write_zsreg(channel, R11, regs[R11]);
/* Lower and upper byte of baud rate generator divisor. */
write_zsreg(channel, R12, regs[R12]);
write_zsreg(channel, R13, regs[R13]);
write_zsreg(channel, R14, regs[R14] & ~BRENAB);
/* Now rewrite R14, with BRENAB (if set). */
write_zsreg(channel, R14, regs[R14]);
write_zsreg(channel, R14,
(regs[R14] & ~SNRZI) | BRENAB);
write_zsreg(channel, R3, regs[R3]);
write_zsreg(channel, R5, regs[R5]);
/* External status interrupt control. */
write_zsreg(channel, R15, regs[R15]);
/* Reset external status interrupts. */
write_zsreg(channel, R0, RES_EXT_INT);
write_zsreg(channel, R0, ERR_RES);
write_zsreg(channel, R0, RES_EXT_INT);
/* Rewrite R3/R5, this time without enables masked. */
write_zsreg(channel, R3, regs[R3]);
write_zsreg(channel, R5, regs[R5]);
/* Rewrite R1, this time without IRQ enabled masked. */
write_zsreg(channel, R1, regs[R1]);
write_zsreg(channel, R9, regs[R9]);
}
/* The port->lock must be held and interrupts must be disabled here. */
static __inline__ unsigned char __sunzilog_read_channel_status(struct uart_port *port)
/* Reprogram the Zilog channel HW registers with the copies found in the
* software state struct. If the transmitter is busy, we defer this update
* until the next TX complete interrupt. Else, we do it right now.
*
* The UART port lock must be held and local interrupts disabled.
*/
static void sunzilog_maybe_update_regs(struct zilog_channel *channel,
struct uart_sunzilog_port *up)
{
struct zilog_channel *channel;
unsigned char status;
channel = ZILOG_CHANNEL_FROM_PORT(port);
status = sbus_read(&channel->control);
ZSDELAY();
return status;
if (!ZS_REGS_HELD(up)) {
if (ZS_TX_ACTIVE(up)) {
up->flags |= SUNZILOG_FLAG_REGS_HELD;
} else {
__load_zsregs(channel, up->curregs);
}
}
}
/* A convenient way to quickly get R0 status. The caller must _not_ hold the
* port lock, it is acquired here. If you have the lock already invoke the
* double-underscore variant above.
*/
static unsigned char sunzilog_read_channel_status(struct uart_port *port)
static void sunzilog_change_mouse_baud(struct uart_sunzilog_port *up)
{
unsigned long flags;
unsigned char status;
unsigned int cur_cflag = up->port.cflag;
int brg, new_baud;
spin_lock_irqsave(&port->lock, flags);
status = __sunzilog_read_channel_status(port);
spin_unlock_irqrestore(&port->lock, flags);
up->port.cflag &= ~CBAUD;
up->port.cflag |= suncore_mouse_baud_cflag_next(cur_cflag, &new_baud);
return status;
brg = BPS_TO_BRG(new_baud,
(ZS_CLOCK / ZS_CLOCK_DIVISOR));
up->curregs[R12] = (brg & 0xff);
up->curregs[R13] = (brg >> 8) & 0xff;
sunzilog_maybe_update_regs(ZILOG_CHANNEL_FROM_PORT(&up->port), up);
}
/* A convenient way to set/clear bits in an arbitrary zilog register.
* The caller must the port lock and local interrupts must be disabled.
*/
static void __sunzilog_set_clear_bits(struct uart_port *port, int regnum,
unsigned char set_bits,
unsigned char clear_bits)
static void sunzilog_kbdms_receive_chars(struct uart_sunzilog_port *up,
unsigned char ch, int is_break)
{
unsigned char regval;
if (ZS_IS_KEYB(up)) {
if (ch == SUNKBD_RESET) {
up->kbd_id = 1;
up->l1_down = 0;
} else if (up->kbd_id) {
up->kbd_id = 0;
} else if (ch == SUNKBD_l1) {
up->l1_down = 1;
} else if (ch == (SUNKBD_l1 | SUNKBD_UP)) {
up->l1_down = 0;
} else if (ch == SUNKBD_A && up->l1_down) {
sun_do_break();
up->l1_down = 0;
up->kbd_id = 0;
return;
}
#ifdef CONFIG_SERIO
serio_interrupt(&up->serio, ch, 0);
#endif
} else if (ZS_IS_MOUSE(up)) {
int ret = suncore_mouse_baud_detection(ch, is_break);
switch (ret) {
case 2:
sunzilog_change_mouse_baud(up);
/* fallthru */
case 1:
break;
regval = SUNZILOG_GET_CURR_REG(port, regnum);
regval |= set_bits;
regval &= ~clear_bits;
SUNZILOG_SET_CURR_REG(port, regnum, regval);
write_zsreg(ZILOG_CHANNEL_FROM_PORT(port), regnum, regval);
case 0:
#ifdef CONFIG_SERIO
serio_interrupt(&up->serio, ch, 0);
#endif
break;
};
}
}
static void sunzilog_receive_chars(struct uart_sunzilog_port *sunzilog_port,
static void sunzilog_receive_chars(struct uart_sunzilog_port *up,
struct sunzilog_channel *channel,
struct pt_regs *regs)
{
struct tty_struct *tty = sunzilog_port->port.info->tty;
struct tty_struct *tty = up->port.info->tty;
while (1) {
unsigned char ch, r1;
......@@ -276,40 +354,29 @@ static void sunzilog_receive_chars(struct uart_sunzilog_port *sunzilog_port,
ch = sbus_readb(&channel->data);
ZSDELAY();
ch &= sunzilog_port->parity_mask;
if (sunzilog_port->flags & SUNZILOG_FLAG_CONS_KEYB) {
if (ch == SUNKBD_RESET) {
sunzilog_port->kbd_id = 1;
sunzilog_port->l1_down = 0;
} else if (sunzilog_port->kbd_id) {
sunzilog_port->kbd_id = 0;
} else if (ch == SUNKBD_l1) {
sunzilog_port->l1_down = 1;
} else if (ch == (SUNKBD_l1 | SUNKBD_UP)) {
sunzilog_port->l1_down = 0;
} else if (ch == SUNKBD_A && sunzilog_port->l1_down) {
sun_do_break();
sunzilog_port->l1_down = 0;
sunzilog_port->kbd_id = 0;
return;
}
sunkbd_inchar(ch, regs);
goto next_char;
}
if (sunzilog_port->flags & SUNZILOG_FLAG_CONS_MOUSE) {
sun_mouse_inbyte(ch, 0);
ch &= up->parity_mask;
if (unlikely(ZS_IS_KEYB(up)) || unlikely(ZS_IS_MOUSE(up))) {
sunzilog_kbdms_receive_chars(up, ch, 0);
goto next_char;
}
if ((sunzilog_port->flags & SUNZILOG_FLAG_IS_CONS) &&
(r1 & BRK_ABRT)) {
if (ZS_IS_CONS(up) && (r1 & BRK_ABRT)) {
/* Wait for BREAK to deassert to avoid potentially
* confusing the PROM.
*/
while (1) {
ch = sbus_readb(&channel->control);
ZSDELAY();
if (!(ch & BRK_ABRT))
break;
}
sun_do_break();
return;
}
#ifndef CONFIG_SPARC64
/* Look for kgdb 'stop' character. */
if ((sunzilog_port->flags & SUNZILOG_FLAG_IS_KGDB) &&
(ch == '\003')) {
if (ZS_IS_KGDB(up) && (ch == '\003')) {
breakpoint();
return;
}
......@@ -318,21 +385,21 @@ static void sunzilog_receive_chars(struct uart_sunzilog_port *sunzilog_port,
/* A real serial line, record the character and status. */
*tty->flip.char_buf_ptr = ch;
*tty->flip.flag_buf_ptr = TTY_NORMAL;
sunzilog_port->port.icount.rx++;
up->port.icount.rx++;
if (r1 & (BRK_ABRT | PAR_ERR | Rx_OVR | CRC_ERR)) {
if (r1 & BRK_ABRT) {
r1 &= ~(PAR_ERR | CRC_ERR);
sunzilog_port->port.icount.break++;
if (uart_handle_break(&sunzilog_port->port))
up->port.icount.break++;
if (uart_handle_break(&up->port))
goto next_char;
}
else if (r1 & PAR_ERR)
sunzilog_port->port.icount.parity++;
up->port.icount.parity++;
else if (r1 & CRC_ERR)
sunzilog_port->port.icount.frame++;
up->port.icount.frame++;
if (r1 & Rx_OVR)
sunzilog_port->port.icount.overrun++;
r1 &= sunzilog_port->port.read_status_mask;
up->port.icount.overrun++;
r1 &= up->port.read_status_mask;
if (r1 & BRK_ABRT)
*tty->flip.flag_buf_ptr = TTY_BREAK;
else if (r1 & PAR_ERR)
......@@ -340,11 +407,11 @@ static void sunzilog_receive_chars(struct uart_sunzilog_port *sunzilog_port,
else if (r1 & CRC_ERR)
*tty->flip.flag_buf_ptr = TTY_FRAME;
}
if (uart_handle_sysrq_char(&sunzilog_port->port, ch, regs))
if (uart_handle_sysrq_char(&up->port, ch, regs))
goto next_char;
if (up->ignore_status_mask == 0xff ||
(r1 & sunzilog_port->port.ignore_status_mask) == 0) {
(r1 & up->port.ignore_status_mask) == 0) {
tty->flip.flag_buf_ptr++;
tty->flip.char_buf_ptr++;
tty->flip.count++;
......@@ -366,7 +433,7 @@ static void sunzilog_receive_chars(struct uart_sunzilog_port *sunzilog_port,
tty_flip_buffer_push(tty);
}
static void sunzilog_status_handle(struct uart_sunzilog_port *sunzilog_port,
static void sunzilog_status_handle(struct uart_sunzilog_port *up,
struct sunzilog_channel *channel)
{
unsigned char status;
......@@ -378,111 +445,104 @@ static void sunzilog_status_handle(struct uart_sunzilog_port *sunzilog_port,
ZSDELAY();
ZS_WSYNC(channel);
if ((status & BRK_ABRT) &&
(sunzilog_port->flags & SUNZILOG_FLAG_CONS_MOUSE))
sun_mouse_inbyte(0, 1);
if ((status & BRK_ABRT) && ZS_IS_MOUSE(up))
sunzilog_kbdms_receive_chars(up, 0, 1);
if (sunzilog_port->flags & SUNZILOG_FLAG_MODEM_STATUS) {
if (ZS_WANTS_MODEM_STATUS(up)) {
if (status & SYNC)
sunzilog_port->port.icount.dsr++;
up->port.icount.dsr++;
/* The Zilog just gives us an interrupt when DCD/CTS/etc. change.
* But it does not tell us which bit has changed, we have to keep
* track of this ourselves.
*/
if ((status & DCD) ^ sunzilog_port->prev_status)
uart_handle_dcd_change(&sunzilog_port->port,
(status & DCD));
if ((status & CTS) ^ sunzilog_port->prev_status)
uart_handle_cts_change(&sunzilog_port->port,
if ((status & DCD) ^ up->prev_status)
uart_handle_dcd_change(&up->port,
(status & DCD));
if ((status & CTS) ^ up->prev_status)
uart_handle_cts_change(&up->port,
(status & CTS));
wake_up_interruptible(&sunzilog_port->port.info->delta_msr_wait);
wake_up_interruptible(&up->port.info->delta_msr_wait);
}
sunzilog_port->prev_status = status;
}
#define ZS_PUT_CHAR_MAX_DELAY 2000 /* 10 ms */
static void sunzilog_put_char(struct sunzilog_channel *channel, unsigned char ch)
{
int loops = ZS_PUT_CHAR_MAX_DELAY;
/* This is a timed polling loop so do not switch the explicit
* udelay with ZSDELAY as that is a NOP on some platforms. -DaveM
*/
do {
unsigned char val = sbus_readb(&channel->control);
if (val & Tx_BUF_EMP)
break;
udelay(5);
} while (--loops);
sbus_writeb(ch, &channel->data);
ZSDELAY();
ZS_WSYNC(channel);
up->prev_status = status;
}
static void sunzilog_transmit_chars(struct uart_sunzilog_port *sunzilog_port,
static void sunzilog_transmit_chars(struct uart_sunzilog_port *up,
struct sunzilog_channel *channel)
{
struct circ_buf *xmit = &sunzilog_port->port.info->xmit;
unsigned char status;
struct circ_buf *xmit = &up->port.info->xmit;
int count;
status = sbus_readb(&channel->control);
ZSDELAY();
if (ZS_IS_CONS(up)) {
unsigned char status = sbus_readb(&channel->control);
ZSDELAY();
/* TX still busy? Just wait for the next TX done interrupt.
* XXX This is a bug bug bug if it happens. It can occur because
* XXX of how we used to do serial consoles on Sparc but we should
* XXX transmit console writes just like we normally would for normal
* XXX UART lines (ie. buffered and TX interrupt driven). -DaveM
*/
if (!(status & Tx_BUF_EMP))
return;
/* TX still busy? Just wait for the next TX done interrupt.
*
* It can occur because of how we do serial console writes. It would
* be nice to transmit console writes just like we normally would for
* a TTY line. (ie. buffered and TX interrupt driven). That is not
* easy because console writes cannot sleep. One solution might be
* to poll on enough port->xmit space becomming free. -DaveM
*/
if (!(status & Tx_BUF_EMP))
return;
}
if (ZS_REGS_HELD(up)) {
__load_zsregs(channel, up->curregs);
up->flags &= ~SUNZILOG_FLAG_REGS_HELD;
}
if (ZS_TX_STOPPED(up)) {
up->flags &= ~SUNZILOG_FLAG_TX_STOPPED;
goto disable_tx_int;
}
if (sunzilog_port->port.x_char) {
if (up->port.x_char) {
sbus_writeb(sunzilog->port.x_char, &channel->data);
ZSDELAY();
ZS_WSYNC(channel);
sunzilog_port->port.icount.tx++;
sunzilog_port->port.x_char = 0;
up->port.icount.tx++;
up->port.x_char = 0;
return;
}
if (uart_circ_empty(xmit) || uart_tx_stopped(&sunzilog_port->port)) {
sunzilog_stop_tx(&sunzilog_port->port, 0);
return;
}
if (uart_circ_empty(xmit) || uart_tx_stopped(&up->port))
goto disable_tx_int;
sbus_writeb(xmit->buf[xmit->tail], &channel->data);
ZSDELAY();
ZS_WSYNC(channel);
xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1);
sunzilog_port->port.icount.tx++;
up->port.icount.tx++;
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
uart_event(&sunzilog_port->port, EVT_WRITE_WAKEUP);
uart_event(&up->port, EVT_WRITE_WAKEUP);
if (!uart_circ_empty(xmit))
return;
if (uart_circ_empty(xmit))
sunzilog_stop_tx(&sunzilog_port->port, 0);
disable_tx_int:
up->curregs[R5] &= ~TxENAB;
write_zsreg(ZILOG_CHANNEL_FROM_PORT(&up->port), R5, up->curregs[R5]);
}
static void sunzilog_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
struct uart_sunzilog_port *sunzilog_port = dev_id;
struct uart_sunzilog_port *up = dev_id;
unsigned long flags;
while (sunzilog_port) {
while (up) {
struct sunzilog_channel *channel
= ZILOG_CHANNEL_FROM_PORT(&sunzilog_port.port);
unsigned char r3;
= ZILOG_CHANNEL_FROM_PORT(&up->port);
unsigned char r3, status;
spin_lock(&sunzilog_port->port.lock);
spin_lock(&up->port.lock);
r3 = read_zsreg(channel, 3);
/* Channel A */
......@@ -492,36 +552,55 @@ static void sunzilog_interrupt(int irq, void *dev_id, struct pt_regs *regs)
ZS_WSYNC(channel);
if (r3 & CHARxIP)
sunzilog_receive_chars(sunzilog_port, channel, regs);
sunzilog_receive_chars(up, channel, regs);
if (r3 & CHAEXT)
sunzilog_status_handle(sunzilog_port, channel);
sunzilog_status_handle(up, channel);
if (r3 & CHATxIP)
sunzilog_transmit_chars(sunzilog_port, channel);
sunzilog_transmit_chars(up, channel);
}
spin_unlock(&sunzilog_port->port.lock);
spin_unlock(&up->port.lock);
/* Channel B */
sunzilog_port = sunzilog_port->next;
channel = ZILOG_CHANNEL_FROM_PORT(&sunzilog_port.port);
up = up->next;
channel = ZILOG_CHANNEL_FROM_PORT(&up.port);
spin_lock(&sunzilog_port->port.lock);
spin_lock(&up->port.lock);
if (r3 & (CHBEXT | CHBTxIP | CHBRxIP)) {
sbus_writeb(RES_H_IUS, &channel->control);
ZSDELAY();
ZS_WSYNC(channel);
if (r3 & CHBRxIP)
sunzilog_receive_chars(sunzilog_port, channel, regs);
sunzilog_receive_chars(up, channel, regs);
if (r3 & CHBEXT)
sunzilog_status_handle(sunzilog_port, channel);
sunzilog_status_handle(up, channel);
if (r3 & CHBTxIP)
sunzilog_transmit_chars(sunzilog_port, channel);
sunzilog_transmit_chars(up, channel);
}
spin_unlock(&sunzilog_port->port.lock);
spin_unlock(&up->port.lock);
sunzilog_port = sunzilog_port->next;
up = up->next;
}
spin_lock_irqrestore(&sunzilog_lock, flags);
}
/* A convenient way to quickly get R0 status. The caller must _not_ hold the
* port lock, it is acquired here.
*/
static __inline__ unsigned char sunzilog_read_channel_status(struct uart_port *port)
{
struct zilog_channel *channel;
unsigned long flags;
unsigned char status;
spin_lock_irqsave(&port->lock, flags);
channel = ZILOG_CHANNEL_FROM_PORT(port);
status = sbus_read(&channel->control);
ZSDELAY();
spin_unlock_irqrestore(&port->lock, flags);
return status;
}
/* The port lock is not held. */
......@@ -561,6 +640,8 @@ static unsigned int sunzilog_get_mctrl(struct uart_port *port)
/* The port lock is held and interrupts are disabled. */
static void sunzilog_set_mctrl(struct uart_port *port, unsigned int mctrl)
{
struct uart_sunzilog_port *up = (struct uart_sunzilog_port *) port;
struct sunzilog_channel *channel = ZILOG_CHANNEL_FROM_PORT(port);
unsigned char set_bits, clear_bits;
set_bits = clear_bits = 0;
......@@ -574,26 +655,36 @@ static void sunzilog_set_mctrl(struct uart_port *port, unsigned int mctrl)
else
clear_bits |= DTR;
__sunzilog_set_clear_bits(port, 5, set_bits, clear_bits);
/* NOTE: Not subject to 'transmitter active' rule. */
up->curregs[R5] |= set_bits;
up->curregs[R5] &= ~clear_bits;
write_zsreg(channel, R5, up->curregs[R5]);
}
/* The port lock is held and interrupts are disabled. */
static void sunzilog_stop_tx(struct uart_port *port, unsigned int tty_stop)
{
__sunzilog_set_clear_bits(port, 5, 0, TxENAB);
struct uart_sunzilog_port *up = (struct uart_sunzilog_port *) port;
up->flags |= SUNZILOG_FLAG_TX_STOPPED;
}
/* The port lock is held and interrupts are disabled. */
static void sunzilog_start_tx(struct uart_port *port, unsigned int tty_start)
{
struct uart_sunzilog_port *up = (struct uart_sunzilog_port *) port;
struct sunzilog_channel *channel;
struct sunzilog_channel *channel = ZILOG_CHANNEL_FROM_PORT(port);
unsigned char status;
/* Enable the transmitter. */
__sunzilog_set_clear_bits(port, 5, TxENAB, 0);
up->flags |= SUNZILOG_FLAG_TX_ACTIVE;
up->flags &= ~SUNZILOG_FLAG_TX_STOPPED;
channel = ZILOG_CHANNEL_FROM_PORT(port);
/* Enable the transmitter. */
if (!(up->curregs[R5] & TxENAB)) {
/* NOTE: Not subject to 'transmitter active' rule. */
up->curregs[R5] |= TxENAB;
write_zsreg(channel, R5, up->curregs[R5]);
}
status = sbus_readb(&channel->control);
ZSDELAY();
......@@ -623,33 +714,55 @@ static void sunzilog_start_tx(struct uart_port *port, unsigned int tty_start)
port->icount.tx++;
if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS)
uart_event(&sunzilog_port->port, EVT_WRITE_WAKEUP);
if (uart_circ_empty(xmit))
sunzilog_stop_tx(&sunzilog_port->port, 0);
uart_event(&up->port, EVT_WRITE_WAKEUP);
}
}
/* The port lock is not held. */
static void sunzilog_stop_rx(struct uart_port *port)
{
struct uart_sunzilog_port *up = UART_ZILOG(port);
struct sunzilog_channel *channel;
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
channel = ZILOG_CHANNEL_FROM_PORT(port);
/* Disable all RX interrupts. */
up->curregs[R1] &= ~RxINT_MASK;
sunzilog_maybe_update_regs(up, up->curregs);
spin_unlock_irqrestore(&port->lock, flags);
}
/* The port lock is not held. */
static void sunzilog_enable_ms(struct uart_port *port)
{
struct uart_sunzilog_port *up = (struct uart_sunzilog_port *) port;
struct sunzilog_channel *channel = ZILOG_CHANNEL_FROM_PORT(port);
unsigned char new_reg;
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
__sunzilog_set_clear_bits(port, 15,
(DCDIE | SYNCIE | CTSIE), 0);
new_reg = up->curregs[R15] | (DCDIE | SYNCIE | CTSIE);
if (new_reg != up->curregs[R15]) {
up->curregs[R15] = new_reg;
/* NOTE: Not subject to 'transmitter active' rule. */
write_zsreg(channel, R15, up->curregs[R15]);
}
spin_unlock_irqrestore(&port->lock, flags);
}
/* The port lock is not held. */
static void sunzilog_break_ctl(struct uart_port *port, int break_state)
{
unsigned char set_bits, clear_bits;
struct uart_sunzilog_port *up = (struct uart_sunzilog_port *) port;
struct sunzilog_channel *channel = ZILOG_CHANNEL_FROM_PORT(port);
unsigned char set_bits, clear_bits, new_reg;
unsigned long flags;
set_bits = clear_bits = 0;
......@@ -660,37 +773,86 @@ static void sunzilog_break_ctl(struct uart_port *port, int break_state)
clear_bits |= SND_BRK;
spin_lock_irqsave(&port->lock, flags);
__sunzilog_set_clear_bits(port, 5, set_bits, clear_bits);
new_reg = (up->curregs[R5] | set_bits) & ~clear_bits;
if (new_reg != up->curregs[R5]) {
up->curregs[R5] = new_reg;
/* NOTE: Not subject to 'transmitter active' rule. */
write_zsreg(channel, R5, up->curregs[R5]);
}
spin_unlock_irqrestore(&port->lock, flags);
}
static int sunzilog_startup(struct uart_port *port)
{
struct uart_sunzilog_port *up = UART_ZILOG(port);
struct sunzilog_channel *channel;
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
channel = ZILOG_CHANNEL_FROM_PORT(port);
up->prev_status = sbus_readb(&channel->control);
/* Enable receiver and transmitter. */
up->curregs[R3] |= RxENAB;
up->curregs[R5] |= TxENAB;
/* Enable RX and status interrupts. TX interrupts are enabled
* as needed.
*/
up->curregs[R1] |= EXT_INT_ENAB | INT_ALL_Rx;
up->curregs[R9] |= MIE;
sunzilog_maybe_update_regs(up, up->curregs);
spin_unlock_irqrestore(&port->lock, flags);
}
static void sunzilog_shutdown(struct uart_port *port)
{
struct uart_sunzilog_port *up = UART_ZILOG(port);
struct sunzilog_channel *channel;
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
channel = ZILOG_CHANNEL_FROM_PORT(port);
/* Disable receiver and transmitter. */
up->curregs[R3] &= ~RxENAB;
up->curregs[R5] &= ~TxENAB;
/* Disable all interrupts and BRK assertion. */
up->curregs[R1] &= ~(EXT_INT_ENAB | TxINT_ENAB | RxINT_MASK);
up->curregs[R5] &= ~SND_BRK;
up->curregs[R9] &= ~MIE;
sunzilog_maybe_update_regs(up, up->curregs);
spin_unlock_irqrestore(&port->lock, flags);
}
/* The port lock is not held. */
/* Shared by TTY driver and serial console setup. The port lock is held
* and local interrupts are disabled.
*/
static void
sunzilog_change_speed(struct uart_port *port, unsigned int cflag,
unsigned int iflag, unsigned int quot)
sunzilog_convert_to_zs(struct uart_sunzilog_port *up, unsigned int cflag,
unsigned int iflag, int brg)
{
struct uart_sunzilog_port *up = (struct uart_sunzilog_port *) port;
unsigned long flags;
spin_lock_irqsave(&up->port.lock, flags);
/* Don't modify MIE. */
up->curregs[R9] |= NV;
up->curregs[R10] = NRZ;
up->curregs[11] = TCBR | RCBR;
/* Program BAUD and clock source. */
up->curregs[4] &= ~XCLK_MASK;
up->curregs[4] |= X16CLK;
up->curregs[11] = TCBR | RCBR;
/* XXX verify this stuff XXX */
up->curregs[12] = quot & 0xff;
up->curregs[13] = (quot >> 8) & 0xff;
up->curregs[12] = brg & 0xff;
up->curregs[13] = (brg >> 8) & 0xff;
up->curregs[14] = BRSRC | BRENAB;
up->curregs[15] |= RTS | DTR;
/* Character size, stop bits, and parity. */
up->curregs[3] &= ~RxN_MASK;
......@@ -749,13 +911,30 @@ sunzilog_change_speed(struct uart_port *port, unsigned int cflag,
if ((cflag & CREAD) == 0)
up->ignore_status_mask = 0xff;
}
/* The port lock is not held. */
static void
sunzilog_change_speed(struct uart_port *port, unsigned int cflag,
unsigned int iflag, unsigned int quot)
{
struct uart_sunzilog_port *up = (struct uart_sunzilog_port *) port;
unsigned long flags;
int baud, brg;
spin_lock_irqsave(&up->port.lock, flags);
baud = (ZILOG_CLOCK / (quot * 16));
brg = BPS_TO_BRG(baud, ZS_CLOCK / ZS_CLOCK_DIVISOR);
sunzilog_convert_to_zs(up, cflag, iflag, brg);
if (UART_ENABLE_MS(&up->port, cflag))
up->flags |= SUNZILOG_FLAG_MODEM_STATUS;
else
up->flags &= ~SUNZILOG_FLAG_MODEM_STATUS;
load_zsregs(up, up->curregs);
sunzilog_maybe_update_regs(ZILOG_CHANNEL_FROM_PORT(port), up);
spin_unlock_irqrestore(&up->port.lock, flags);
}
......@@ -774,6 +953,7 @@ static void sunzilog_release_port(struct uart_port *port)
static int sunzilog_request_port(struct uart_port *port)
{
return 0;
}
/* These do not need to do anything interesting either. */
......@@ -781,49 +961,54 @@ static void sunzilog_config_port(struct uart_port *port, int flags)
{
}
/* We do not support letting the user mess with the divisor, IRQ, etc. */
static int sunzilog_verify_port(struct uart_port *port, struct serial_struct *ser)
{
return 0;
return -EINVAL;
}
static struct uart_ops sunzilog_pops = {
tx_empty: sunzilog_tx_empty,
set_mctrl: sunzilog_set_mctrl,
get_mctrl: sunzilog_get_mctrl,
stop_tx: sunzilog_stop_tx,
start_tx: sunzilog_start_tx,
stop_rx: sunzilog_stop_rx,
enable_ms: sunzilog_enable_ms,
break_ctl: sunzilog_break_ctl,
startup: sunzilog_startup,
shutdown: sunzilog_shutdown,
change_speed: sunzilog_change_speed,
type: sunzilog_type,
release_port: sunzilog_release_port,
request_port: sunzilog_request_port,
config_port: sunzilog_config_port,
verify_port: sunzilog_verify_port,
.tx_empty = sunzilog_tx_empty,
.set_mctrl = sunzilog_set_mctrl,
.get_mctrl = sunzilog_get_mctrl,
.stop_tx = sunzilog_stop_tx,
.start_tx = sunzilog_start_tx,
.stop_rx = sunzilog_stop_rx,
.enable_ms = sunzilog_enable_ms,
.break_ctl = sunzilog_break_ctl,
.startup = sunzilog_startup,
.shutdown = sunzilog_shutdown,
.change_speed = sunzilog_change_speed,
.type = sunzilog_type,
.release_port = sunzilog_release_port,
.request_port = sunzilog_request_port,
.config_port = sunzilog_config_port,
.verify_port = sunzilog_verify_port,
};
static struct uart_sunzilog_port sunzilog_ports[UART_NR];
static struct uart_sunzilog_port *sunzilog_port_table;
static struct zilog_layout **sunzilog_chip_regs;
static struct uart_sunzilog_port *sunzilog_irq_chain;
static int zilog_irq = -1;
static struct uart_driver sunzilog_reg = {
owner: THIS_MODULE,
driver_name: "ttyS",
.owner = THIS_MODULE,
.driver_name = "ttyS",
#ifdef CONFIG_DEVFS_FS
dev_name: "ttyS%d",
.dev_name = "ttyS%d",
#else
dev_name: "ttyS",
.dev_name = "ttyS",
#endif
major: TTY_MAJOR,
minor: 64,
.major = TTY_MAJOR,
.minor = 64,
};
static void * __init sunzilog_alloc_bootmem(unsigned long size)
static void * __init sunzilog_alloc_one_table(unsigned long size)
{
void *ret;
ret = __alloc_bootmem(size, SMP_CACHE_BYTES, 0UL);
ret = kmalloc(size, GFP_KERNEL);
if (ret != NULL)
memset(ret, 0, size);
......@@ -832,53 +1017,618 @@ static void * __init sunzilog_alloc_bootmem(unsigned long size)
static void __init sunzilog_alloc_tables(void)
{
zs_chips = (struct sun_zslayout **)
zs_alloc_bootmem(NUM_SERIAL * sizeof(struct sun_zslayout *));
if (zs_chips == NULL)
zs_init_alloc_failure("zs_chips");
zs_channels = (struct sun_zschannel **)
zs_alloc_bootmem(NUM_CHANNELS * sizeof(struct sun_zschannel *));
if (zs_channels == NULL)
zs_init_alloc_failure("zs_channels");
zs_nodes = (int *)
zs_alloc_bootmem(NUM_SERIAL * sizeof(int));
if (zs_nodes == NULL)
zs_init_alloc_failure("zs_nodes");
zs_soft = (struct sun_serial *)
zs_alloc_bootmem(NUM_CHANNELS * sizeof(struct sun_serial));
if (zs_soft == NULL)
zs_init_alloc_failure("zs_soft");
zs_ttys = (struct tty_struct *)
zs_alloc_bootmem(NUM_CHANNELS * sizeof(struct tty_struct));
if (zs_ttys == NULL)
zs_init_alloc_failure("zs_ttys");
serial_table = (struct tty_struct **)
zs_alloc_bootmem(NUM_CHANNELS * sizeof(struct tty_struct *));
if (serial_table == NULL)
zs_init_alloc_failure("serial_table");
serial_termios = (struct termios **)
zs_alloc_bootmem(NUM_CHANNELS * sizeof(struct termios *));
if (serial_termios == NULL)
zs_init_alloc_failure("serial_termios");
serial_termios_locked = (struct termios **)
zs_alloc_bootmem(NUM_CHANNELS * sizeof(struct termios *));
if (serial_termios_locked == NULL)
zs_init_alloc_failure("serial_termios_locked");
}
static int __init sunzilog_probe(void)
sunzilog_port_table = (struct uart_sunzilog_port *)
zs_alloc_one_table(NUM_CHANNELS * sizeof(struct uart_sunzilog_port));
sunzilog_chip_regs = (struct zilog_layout **)
zs_alloc_one_table(NUM_SUNZILOG * sizeof(struct zilog_layout *));
sunzilog_nodes = (int *)
zs_alloc_one_table(NUM_SUNZILOG * sizeof(int));
if (sunzilog_port_table == NULL ||
sunzilog_chip_regs == NULL ||
sunzilog_nodes == NULL) {
prom_printf("sunzilog_init: Cannot alloc SunZilog tables.\n");
prom_halt();
}
}
#ifdef CONFIG_SPARC64
/* We used to attempt to use the address property of the Zilog device node
* but that totally is not necessary on sparc64.
*/
static struct zilog_layout * __init get_zs_sun4u(int chip)
{
unsigned long mapped_addr;
unsigned int sun4u_ino;
int busnode, zsnode, seen;
if (central_bus)
busnode = central_bus->child->prom_node;
else
busnode = prom_searchsiblings(prom_getchild(prom_root_node), "sbus");
if (busnode == 0 || busnode == -1) {
prom_printf("SunZilog: Cannot find bus of Zilog %d in get_zs_sun4u.\n",
chip);
prom_halt();
}
zsnode = prom_getchild(busnode);
seen = 0;
while (zsnode) {
int slave;
zsnode = prom_searchsiblings(zsnode, "zs");
if (zsnode == 0 || zsnode == -1)
break;
slave = prom_getintdefault(zsnode, "slave", -1);
if ((slave == chip) || (seen == chip)) {
struct sbus_bus *sbus = NULL;
struct sbus_dev *sdev = NULL;
int err;
if (central_bus == NULL) {
for_each_sbus(sbus) {
for_each_sbusdev(sdev, sbus) {
if (sdev->prom_node == zsnode)
goto found;
}
}
}
found:
if (sdev == NULL && central_bus == NULL) {
prom_printf("SunZilog: sdev&&central == NULL for "
"Zilog %d in get_zs_sun4u.\n", chip);
prom_halt();
}
if (central_bus == NULL) {
mapped_addr =
sbus_ioremap(&sdev->resource[0], 0,
PAGE_SIZE,
"Zilog Registers");
} else {
struct linux_prom_registers zsregs[1];
err = prom_getproperty(zsnode, "reg",
(char *) &zsregs[0],
sizeof(zsregs));
if (err == -1) {
prom_printf("SunZilog: Cannot map "
"Zilog %d regs on "
"central bus.\n", chip);
prom_halt();
}
apply_fhc_ranges(central_bus->child,
&zsregs[0], 1);
apply_central_ranges(central_bus, &zsregs[0], 1);
mapped_addr =
(((u64)zsregs[0].which_io)<<32UL) |
((u64)zsregs[0].phys_addr);
}
sunzilog_nodes[chip] = zsnode;
if (zilog_irq == -1) {
if (central_bus) {
unsigned long iclr, imap;
iclr = central_bus->child->fhc_regs.uregs
+ FHC_UREGS_ICLR;
imap = central_bus->child->fhc_regs.uregs
+ FHC_UREGS_IMAP;
zilog_irq = build_irq(12, 0, iclr, imap);
} else {
err = prom_getproperty(zsnode, "interrupts",
(char *) &sun4u_ino,
sizeof(sun4u_ino));
zilog_irq = sbus_build_irq(sbus_root, sun4u_ino);
}
}
break;
}
zsnode = prom_getsibling(zsnode);
seen++;
}
if (zsnode == 0 || zsnode == -1) {
prom_printf("SunZilog: Cannot find Zilog %d in get_zs_sun4u.\n", chip);
prom_halt();
}
return (struct zilog_layout *) mapped_addr;
}
#else /* CONFIG_SPARC64 */
static struct zilog_layout * __init get_zs_sun4cmd(int chip)
{
struct linux_prom_irqs irq_info[2];
unsigned long mapped_addr;
int zsnode, chipid, cpunode;
if (sparc_cpu_model == sun4d) {
int bbnode, walk, no;
zsnode = 0;
bbnode = 0;
no = 0;
for (walk = prom_getchild(prom_root_node);
(walk = prom_searchsiblings(walk, "cpu-unit")) != 0;
walk = prom_getsibling(walk)) {
bbnode = prom_getchild(walk);
if (bbnode &&
(bbnode = prom_searchsiblings(bbnode, "bootbus"))) {
if (no == (chip / 2)) {
cpunode = walk;
zsnode = prom_getchild(bbnode);
chipid = (chip & 1);
break;
}
no++;
}
}
if (!walk) {
prom_printf("SunZilog: Cannot find the %d'th bootbus on sun4d.\n",
(chip / 2));
prom_halt();
}
} else {
int tmp;
zsnode = prom_getchild(prom_root_node);
if ((tmp = prom_searchsiblings(zsnode, "obio"))) {
zsnode = prom_getchild(tmp);
if (!zsnode) {
prom_printf("SunZilog: Child of obio node does "
"not exist.\n");
prom_halt();
}
}
chipid = 0;
}
while (zsnode) {
struct linux_prom_registers zsreg[4];
struct resource res;
zsnode = prom_searchsiblings(zsnode, "zs");
if (zsnode == 0 || zsnode == -1)
break;
if (prom_getintdefault(zsnode, "slave", -1) != chipid) {
zsnode = prom_getsibling(zsnode);
continue;
}
if (sparc_cpu_model == sun4d) {
/* Sun4d Zilog nodes lack the address property, so just
* map it like a normal device.
*/
if (prom_getproperty(zsnode, "reg",
(char *) zsreg, sizeof(zsreg)) == -1) {
prom_printf("SunZilog: Cannot map Zilog %d "
"regs on sun4c.\n", chip);
prom_halt();
}
prom_apply_generic_ranges(bbnode, cpunode, zsreg, 1);
res.start = zsreg[0].phys_addr;
res.end = res.start + (8 - 1);
res.flags = zsreg[0].which_io | IORESOURCE_IO;
mapped_addr = sbus_ioremap(&res, 0, 8, "Zilog Serial");
} else {
unsigned int vaddr[2];
if (prom_getproperty(zsnode, "address",
(void *) vaddr, sizeof(vaddr))
% sizeof(unsigned int)) {
prom_printf("SunZilog: Cannot get address property for "
"Zilog %d.\n", chip);
prom_halt();
}
mapped_addr = (unsigned long) vaddr[0];
}
sunzilog_nodes[chip] = zsnode;
if (prom_getproperty(zsnode, "intr",
(char *) irq_info, sizeof(irq_info))
% sizeof(struct linux_prom_irqs)) {
prom_printf("SunZilog: Cannot get IRQ property for "
"Zilog %d.\n", chip);
prom_halt();
}
if (zilog_irq == -1) {
zilog_irq = irq_info[0].pri;
} else if (zilog_irq != irq_info[0].pri) {
prom_printf("SunZilog: Inconsistent IRQ layout for Zilog %d.\n",
chip);
promt_halt();
}
break;
}
if (!zsnode) {
prom_printf("SunZilog: OBP node for Zilog %d not found.\n", chip);
prom_halt();
}
return (struct zilog_layout *) mapped_addr;
}
#endif /* !(CONFIG_SPARC64) */
/* Get the address of the registers for SunZilog instance CHIP. */
static struct zilog_layout * __init get_zs(int chip)
{
if (chip < 0 || chip >= NUM_SUNZILOG) {
prom_printf("SunZilog: Illegal chip number %d in get_zs.\n", chip);
prom_halt();
}
#ifdef CONFIG_SPARC64
return get_zs_sun4u(chip);
#else
if (sparc_cpu_model == sun4) {
struct resource res;
/* Not probe-able, hard code it. */
switch (chip) {
case 0:
res.start = 0xf1000000;
break;
case 1:
res.start = 0xf0000000;
break;
};
sunzilog_nodes[chip] = 0;
zilog_irq = 12;
res.end = (res.start + (8 - 1));
res.flags = IORESOURCE_IO;
return sbus_ioremap(&res, 0, 8, "SunZilog");
}
return get_zs_sun4cmd(chip);
#endif
}
#define ZS_PUT_CHAR_MAX_DELAY 2000 /* 10 ms */
static void sunzilog_put_char(struct sunzilog_channel *channel, unsigned char ch)
{
int loops = ZS_PUT_CHAR_MAX_DELAY;
/* This is a timed polling loop so do not switch the explicit
* udelay with ZSDELAY as that is a NOP on some platforms. -DaveM
*/
do {
unsigned char val = sbus_readb(&channel->control);
if (val & Tx_BUF_EMP) {
ZSDELAY();
break;
}
udelay(5);
} while (--loops);
sbus_writeb(ch, &channel->data);
ZSDELAY();
ZS_WSYNC(channel);
}
#ifdef CONFIG_SERIO
static spinlock_t sunzilog_serio_lock = SPIN_LOCK_UNLOCKED;
static void sunzilog_serio_write(struct serio *serio, unsigned char ch)
{
struct uart_sunzilog_port *up = serio->driver;
unsigned long flags;
spin_lock_irqsave(&sunzilog_serio_lock, flags);
sunzilog_put_char(channel, ch);
spin_lock_irqrestore(&sunzilog_serio_lock, flags);
}
static int sunzilog_serio_open(struct serio *serio)
{
unsigned long flags;
int ret;
spin_lock_irqsave(&sunsu_serio_lock, flags);
if (serio->private == NULL) {
serio->private = (void *) -1L;
ret = 0;
} else
ret = -EBUSY;
spin_unlock_irqrestore(&sunsu_serio_lock, flags);
return ret;
}
static void sunzilog_serio_close(struct serio *serio)
{
unsigned long flags;
spin_lock_irqsave(&sunsu_serio_lock, flags);
serio->private = NULL;
spin_unlock_irqrestore(&sunsu_serio_lock, flags);
}
#endif /* CONFIG_SERIO */
static void
sunzilog_console_write(struct console *cons, const char *s, unsigned int count)
{
struct uart_sunzilog_port *up = &sunzilog_port_table[con->index];
struct sunzilog_channel *channel = ZILOG_CHANNEL_FROM_PORT(&up->port);
unsigned long flags;
int i;
spin_lock_irqsave(&up->port.lock, flags);
for (i = 0; i < count; i++, s++) {
sunzilog_put_char(channel, *s);
if (*s == 10)
sunzilog_put_char(channel, 13);
}
udelay(2);
spin_unlock_irqrestore(&up->port.lock, flags);
}
static kdev_t sunzilog_console_device(struct console *cons)
{
return mk_kdev(TTY_MAJOR, 64 + con->index);
}
static int __init sunzilog_console_setup(struct console *cons, char *options)
{
struct uart_sunzilog_port *up = &sunzilog_port_table[con->index];
unsigned long flags;
int baud, brg;
printk("Console: ttyS%d (Zilog8530)\n", con->index / 2);
/* Get firmware console settings. */
sunserial_console_termios(con);
/* Firmware console speed is limited to 150-->38400 baud so
* this hackish cflag thing is OK.
*/
switch(cflag & CBAUD) {
case B150: baud = 150; break;
case B300: baud = 300; break;
case B600: baud = 600; break;
case B1200: baud = 1200; break;
case B2400: baud = 2400; break;
case B4800: baud = 4800; break;
default: case B9600: baud = 9600; break;
case B19200: baud = 19200; break;
case B38400: baud = 38400; break;
};
brg = BPS_TO_BRG(baud, ZS_CLOCK / ZS_CLOCK_DIVISOR);
/*
* Temporary fix.
*/
spin_lock_init(&up->port.lock);
spin_lock_irqsave(&up->port.lock, flags);
up->curregs[R15] = BRKIE;
sunzilog_convert_to_zs(up, con->cflag, 0, brg);
spin_unlock_irqrestore(&up->port.lock, flags);
sunzilog_set_mctrl(up, TIOCM_DTR | TIOCM_RTS);
sunzilog_startup(up);
}
static struct console sunzilog_console = {
.name = "ttyS",
.write = sunzilog_console_write,
.device = sunzilog_console_device,
.setup = sunzilog_console_setup,
.flags = CON_PRINTBUFFER,
.index = -1,
};
static int __init sunzilog_console_init(void)
{
if (con_is_present())
return 0;
sunzilog_console.index = serial_console - 1;
register_console(&sunzilog_console);
return 0;
}
static void __init sunzilog_prepare(void)
{
struct uart_sunzilog_port *up;
struct zilog_layout *rp;
int channel, chip;
sunzilog_irq_chain = up = &sunzilog_port_table[0];
for (channel = 0; channel < NUM_CHANNELS - 1; channel++)
up[channel].next = &up[channel + 1];
up[channel].next = NULL;
for (chip = 0; chip < NUM_SUNZILOG; chip++) {
if (!sunzilog_chip_regs[chip]) {
sunzilog_chip_regs[chip] = rp = get_zs(chip);
up[(chip * 2) + 0].port.membase = &rp->channelA;
up[(chip * 2) + 1].port.membase = &rp->channelB;
}
/* Channel A */
up[(chip * 2) + 0].port.iotype = SERIAL_IO_MEM;
up[(chip * 2) + 0].port.irq = zilog_irq;
up[(chip * 2) + 0].port.uartclk = ZILOG_CLOCK;
up[(chip * 2) + 0].port.fifosize = 1;
up[(chip * 2) + 0].port.ops = &sunzilog_pops;
up[(chip * 2) + 0].port.flags = 0;
up[(chip * 2) + 0].port.line = (chip * 2) + 0;
up[(chip * 2) + 0].flags |= SUNZILOG_FLAG_IS_CHANNEL_A;
/* Channel B */
up[(chip * 2) + 1].port.iotype = SERIAL_IO_MEM;
up[(chip * 2) + 1].port.irq = zilog_irq;
up[(chip * 2) + 1].port.uartclk = ZILOG_CLOCK;
up[(chip * 2) + 1].port.fifosize = 1;
up[(chip * 2) + 1].port.ops = &sunzilog_pops;
up[(chip * 2) + 1].port.flags = 0;
up[(chip * 2) + 1].port.line = (chip * 2) + 1;
up[(chip * 2) + 1].flags |= 0;
}
}
static void __init sunzilog_init_kbdms(struct uart_sunzilog_port *up, int channel)
{
int baud, brg;
if (channel == KEYBOARD_LINE) {
up->flags |= SUNZILOG_FLAG_CONS_KEYB;
up->port.cflag = B1200 | CS8 | CLOCAL | CREAD;
baud = 1200;
} else {
up->flags |= SUNZILOG_FLAG_CONS_MOUSE;
up->port.cflag = B4800 | CS8 | CLOCAL | CREAD;
baud = 4800;
}
printk(KERN_INFO "zs%d at 0x%p (irq = %s) is a Zilog8530\n",
channel, up->port.membase, __irq_itoa(zilog_irq));
up->curregs[R15] = BRKIE;
brg = BPS_TO_BRG(baud, ZS_CLOCK / ZS_CLOCK_DIVISOR);
sunzilog_convert_to_zs(up, up->port.cflag, 0, brg);
#ifdef CONFIG_SERIO
memset(&up->serio, 0, sizeof(up->serio));
up->serio.driver = up;
up->serio.type = SERIO_RS232;
if (channel == KEYBOARD_LINE) {
up->serio.type |= SERIO_SUNKBD;
up->serio.name = "zskbd";
} else {
up->serio.type |= SERIO_SUN;
up->serio.name = "zsms";
}
up->phys = (channel == KEYBOARD_LINE ?
"zs/serio0" : "zs/serio1");
up->serio.write = sunzilog_serio_write;
up->serio.open = sunzilog_serio_open;
up->serio.close = sunzilog_serio_close;
serio_register_port(&up->serio);
#endif
spin_unlock(&up->port.lock);
sunzilog_set_mctrl(up, TIOCM_DTR | TIOCM_RTS);
sunzilog_startup(&up->port);
spin_lock(&up->port.lock);
}
static void __init sunzilog_init_hw(void)
{
int channel;
for (channel = 0; channel < NUM_CHANNELS; channel++) {
struct uart_sunzilog_port *up = &sunzilog_port_table[channel];
struct zilog_channel *channel = ZILOG_CHANNEL_FROM_PORT(&up->port);
unsigned long flags;
int baud, brg;
spin_lock_irqsave(&up->port.lock, flags);
if (ZS_IS_CHANNEL_A(up)) {
write_zsreg(channel, R9, FHWRES);
ZSDELAY_LONG();
(void) read_zsreg(channel, R0);
}
if (channel == KEYBOARD_LINE || channel == MOUSE_LINE) {
sunzilog_init_kbdms(up, channel);
} else if (ZS_IS_CONS(up)) {
/* sunzilog_console_setup takes care of this */
} else {
/* Normal serial TTY. */
up->parity_mask = 0xff;
up->curregs[R3] = RxENAB;
up->curregs[R5] = TxENAB;
up->curregs[R9] = NV | MIE;
up->curregs[R10] = NRZ;
up->curregs[R11] = TCBR | RCBR;
baud = 9600;
brg = BPS_TO_BRG(baud, (ZS_CLOCK / ZS_CLOCK_DIVISOR));
up->curregs[R12] = (brg & 0xff);
up->curregs[R13] = (brg >> 8) & 0xff;
up->curregs[R14] = BRSRC | BRENAB;
sunzilog_maybe_update_regs(channel, up->curregs);
}
spin_unlock_irqrestore(&up->port.lock, flags);
}
}
static int __init sunzilog_ports_init(void)
{
int ret;
printk(KERN_INFO "Serial: Sun Zilog driver.\n");
sunzilog_prepare();
if (request_irq(zilog_irq, sunzilog_interrupt, SA_SHIRQ,
"SunZilog", sunzilog_irq_chain)) {
prom_printf("SunZilog: Unable to register zs interrupt handler.\n");
prom_halt();
}
sunzilog_init_hw();
/* We can only init this once we have probed the Zilogs
* in the system.
*/
sunzilog_reg.nr = NUM_CHANNELS;
#ifdef CONFIG_SERIAL_CONSOLE
sunzilog_reg.cons = &sunzilog_console;
#else
sunzilog_reg.cons = NULL;
#endif
ret = uart_register_driver(&sunzilog_reg);
if (ret == 0) {
int i;
for (i = 0; i < NUM_CHANNELS; i++) {
if (ZS_IS_KEYB(up) || ZS_IS_MOUSE(up))
continue;
uart_add_one_port(&sunzilog_reg,
&sunzilog_port_table[i].port);
}
}
return ret;
}
static int __init sunzilog_init(void)
{
int node;
/* Sun4 Zilog setup is hard coded, no probing to do. */
if (sparc_cpu_model == sun4)
if (sparc_cpu_model == sun4) {
NUM_SUNZILOG = 2;
goto no_probe;
}
NUM_SUNZILOG = 0;
node = prom_getchild(prom_root_node);
if (sparc_cpu_model == sun4d) {
int bbnode;
NUM_SUNZILOG = 0;
while (node &&
(node = prom_searchsiblings(node, "cpu-unit"))) {
bbnode = prom_getchild(node);
......@@ -887,31 +1637,26 @@ static int __init sunzilog_probe(void)
node = prom_getsibling(node);
}
goto no_probe;
}
#ifdef CONFIG_SPARC64
else if (sparc_cpu_model == sun4u) {
} else if (sparc_cpu_model == sun4u) {
int central_node;
/* Central bus zilogs must be checked for first,
* since Enterprise boxes might have SBUSes as well.
*/
central_node = prom_finddevice("/central");
if(central_node != 0 && central_node != -1)
if (central_node != 0 && central_node != -1)
node = prom_searchsiblings(prom_getchild(central_node), "fhc");
else
node = prom_searchsiblings(node, "sbus");
if(node != 0 && node != -1)
if (node != 0 && node != -1)
node = prom_getchild(node);
if(node == 0 || node == -1)
if (node == 0 || node == -1)
return -ENODEV;
}
#endif /* CONFIG_SPARC64 */
else {
} else {
node = prom_searchsiblings(node, "obio");
if(node)
if (node)
node = prom_getchild(node);
NUM_SERIAL = 2;
NUM_SUNZILOG = 2;
goto no_probe;
}
......@@ -919,51 +1664,28 @@ static int __init sunzilog_probe(void)
if (!node)
return -ENODEV;
NUM_SERIAL = 2;
NUM_SUNZILOG = 2;
no_probe:
sunzilog_alloc_tables();
}
static int __init sunzilog_init(void)
{
int ret;
printk(KERN_INFO "Serial: Sun Zilog driver.\n");
/* We can only init this once we have probed the Zilogs
* in the system.
*/
sunzilog_reg.nr = NUM_CHANNELS;
/* XXX This is probably where this needs to be setup for
* XXX the same reason.
*/
sunzilog_reg.cons = XXX;
ret = uart_register_driver(&sunzilog_reg);
if (ret == 0) {
int i;
sunzilog_alloc_tables();
for (i = 0; i < UART_NR; i++) {
/* XXX For each probed Zilog do this... */
uart_add_one_port(&sunzilog_reg,
&sunzilog_ports[i].port);
}
}
sunzilog_ports_init();
sunzilog_console_init();
return ret;
return 0;
}
static void __exit sunzilog_exit(void)
{
int i;
for (i = 0; i < UART_NR; i++) {
/* XXX For each probed Zilog do this... */
for (i = 0; i < NUM_CHANNELS; i++) {
if (ZS_IS_KEYB(up) || ZS_IS_MOUSE(up))
continue;
uart_remove_one_port(&sunzilog_reg,
&sunzilog_ports[i].port);
&sunzilog_port_table[i].port);
}
uart_unregister_driver(&sunzilog_reg);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment