aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/input/misc/powermate.c
blob: 14f48e10f589e81b8d5f38314cb0d6f6708589bb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

@media only all and (prefers-color-scheme: dark) {
.highlight .hll { background-color: #49483e }
.highlight .c { color: #75715e } /* Comment */
.highlight .err { color: #960050; background-color: #1e0010 } /* Error */
.highlight .k { color: #66d9ef } /* Keyword */
.highlight .l { color: #ae81ff } /* Literal */
.highlight .n { color: #f8f8f2 } /* Name */
.highlight .o { color: #f92672 } /* Operator */
.highlight .p { color: #f8f8f2 } /* Punctuation */
.highlight .ch { color: #75715e } /* Comment.Hashbang */
.highlight .cm { color: #75715e } /* Comment.Multiline */
.highlight .cp { color: #75715e } /* Comment.Preproc */
.highlight .cpf { color: #75715e } /* Comment.PreprocFile */
.highlight .c1 { color: #75715e } /* Comment.Single */
.highlight .cs { color: #75715e } /* Comment.Special */
.highlight .gd { color: #f92672 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gi { color: #a6e22e } /* Generic.Inserted */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #75715e } /* Generic.Subheading */
.highlight .kc { color: #66d9ef } /* Keyword.Constant */
.highlight .kd { color: #66d9ef } /* Keyword.Declaration */
.highlight .kn { color: #f92672 } /* Keyword.Namespace */
.highlight .kp { color: #66d9ef } /* Keyword.Pseudo */
.highlight .kr { color: #66d9ef } /* Keyword.Reserved */
.highlight .kt { color: #66d9ef } /* Keyword.Type */
.highlight .ld { color: #e6db74 } /* Literal.Date */
.highlight .m { color: #ae81ff } /* Literal.Number */
.highlight .s { color: #e6db74 } /* Literal.String */
.highlight .na { color: #a6e22e } /* Name.Attribute */
.highlight .nb { color: #f8f8f2 } /* Name.Builtin */
.highlight .nc { color: #a6e22e } /* Name.Class */
.highlight .no { color: #66d9ef } /* Name.Constant */
.highlight .nd { color: #a6e22e } /* Name.Decorator */
.highlight .ni { color: #f8f8f2 } /* Name.Entity */
.highlight .ne { color: #a6e22e } /* Name.Exception */
.highlight .nf { color: #a6e22e } /* Name.Function */
.highlight .nl { color: #f8f8f2 } /* Name.Label */
.highlight .nn { color: #f8f8f2 } /* Name.Namespace */
.highlight .nx { color: #a6e22e } /* Name.Other */
.highlight .py { color: #f8f8f2 } /* Name.Property */
.highlight .nt { color: #f92672 } /* Name.Tag */
.highlight .nv { color: #f8f8f2 } /* Name.Variable */
.highlight .ow { color: #f92672 } /* Operator.Word */
.highlight .w { color: #f8f8f2 } /* Text.Whitespace */
.highlight .mb { color: #ae81ff } /* Literal.Number.Bin */
.highlight .mf { color: #ae81ff } /* Literal.Number.Float */
.highlight .mh { color: #ae81ff } /* Literal.Number.Hex */
.highlight .mi { color: #ae81ff } /* Literal.Number.Integer */
.highlight .mo { color: #ae81ff } /* Literal.Number.Oct */
.highlight .sa { color: #e6db74 } /* Literal.String.Affix */
.highlight .sb { color: #e6db74 } /* Literal.String.Backtick */
.highlight .sc { color: #e6db74 } /* Literal.String.Char */
.highlight .dl { color: #e6db74 } /* Literal.String.Delimiter */
.highlight .sd { color: #e6db74 } /* Literal.String.Doc */
.highlight .s2 { color: #e6db74 } /* Literal.String.Double */
.highlight .se { color: #ae81ff } /* Literal.String.Escape */
.highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */
.highlight .si { color: #e6db74 } /* Literal.String.Interpol */
.highlight .sx { color: #e6db74 } /* Literal.String.Other */
.highlight .sr { color: #e6db74 } /* Literal.String.Regex */
.highlight .s1 { color: #e6db74 } /* Literal.String.Single */
.highlight .ss { color: #e6db74 } /* Literal.String.Symbol */
.highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #a6e22e } /* Name.Function.Magic */
.highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */
.highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */
.highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */
.highlight .vm { color: #f8f8f2 } /* Name.Variable.Magic */
.highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */
}
@media (prefers-color-scheme: light) {
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
}
/*
 * TTY driver for MIPS EJTAG Fast Debug Channels.
 *
 * Copyright (C) 2007-2015 Imagination Technologies Ltd
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License. See the file COPYING in the main directory of this archive for more
 * details.
 */

#include <linux/atomic.h>
#include <linux/bitops.h>
#include <linux/completion.h>
#include <linux/console.h>
#include <linux/delay.h>
#include <linux/export.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/kgdb.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/serial.h>
#include <linux/serial_core.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/timer.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/tty_flip.h>
#include <linux/uaccess.h>

#include <asm/cdmm.h>
#include <asm/irq.h>

/* Register offsets */
#define REG_FDACSR	0x00	/* FDC Access Control and Status Register */
/*
 * A driver for the Griffin Technology, Inc. "PowerMate" USB controller dial.
 *
 * v1.1, (c)2002 William R Sowerbutts <will@sowerbutts.com>
 *
 * This device is a anodised aluminium knob which connects over USB. It can measure
 * clockwise and anticlockwise rotation. The dial also acts as a pushbutton with
 * a spring for automatic release. The base contains a pair of LEDs which illuminate
 * the translucent base. It rotates without limit and reports its relative rotation
 * back to the host when polled by the USB controller.
 *
 * Testing with the knob I have has shown that it measures approximately 94 "clicks"
 * for one full rotation. Testing with my High Speed Rotation Actuator (ok, it was
 * a variable speed cordless electric drill) has shown that the device can measure
 * speeds of up to 7 clicks either clockwise or anticlockwise between pollings from
 * the host. If it counts more than 7 clicks before it is polled, it will wrap back
 * to zero and start counting again. This was at quite high speed, however, almost
 * certainly faster than the human hand could turn it. Griffin say that it loses a
 * pulse or two on a direction change; the granularity is so fine that I never
 * noticed this in practice.
 *
 * The device's microcontroller can be programmed to set the LED to either a constant
 * intensity, or to a rhythmic pulsing. Several patterns and speeds are available.
 *
 * Griffin were very happy to provide documentation and free hardware for development.
 *
 * Some userspace tools are available on the web: http://sowerbutts.com/powermate/
 *
 */

#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/usb/input.h>

#define POWERMATE_VENDOR	0x077d	/* Griffin Technology, Inc. */
#define POWERMATE_PRODUCT_NEW	0x0410	/* Griffin PowerMate */
#define POWERMATE_PRODUCT_OLD	0x04AA	/* Griffin soundKnob */

#define CONTOUR_VENDOR		0x05f3	/* Contour Design, Inc. */
#define CONTOUR_JOG		0x0240	/* Jog and Shuttle */

/* these are the command codes we send to the device */
#define SET_STATIC_BRIGHTNESS  0x01
#define SET_PULSE_ASLEEP       0x02
#define SET_PULSE_AWAKE        0x03
#define SET_PULSE_MODE         0x04

/* these refer to bits in the powermate_device's requires_update field. */
#define UPDATE_STATIC_BRIGHTNESS (1<<0)
#define UPDATE_PULSE_ASLEEP      (1<<1)
#define UPDATE_PULSE_AWAKE       (1<<2)
#define UPDATE_PULSE_MODE        (1<<3)

/* at least two versions of the hardware exist, with differing payload
   sizes. the first three bytes always contain the "interesting" data in
   the relevant format. */
#define POWERMATE_PAYLOAD_SIZE_MAX 6
#define POWERMATE_PAYLOAD_SIZE_MIN 3
struct powermate_device {
	signed char *data;
	dma_addr_t data_dma;
	struct urb *irq, *config;
	struct usb_ctrlrequest *configcr;
	struct usb_device *udev;
	struct usb_interface *intf;
	struct input_dev *input;
	spinlock_t lock;
	int static_brightness;
	int pulse_speed;
	int pulse_table;
	int pulse_asleep;
	int pulse_awake;
	int requires_update; // physical settings which are out of sync
	char phys[64];
};

static char pm_name_powermate[] = "Griffin PowerMate";
static char pm_name_soundknob[] = "Griffin SoundKnob";

static void powermate_config_complete(struct urb *urb);

/* Callback for data arriving from the PowerMate over the USB interrupt pipe */
static void powermate_irq(struct urb *urb)
{
	struct powermate_device *pm = urb->context;
	struct device *dev = &pm->intf->dev;
	int retval;

	switch (urb->status) {
	case 0:
		/* success */
		break;
	case -ECONNRESET:
	case -ENOENT:
	case -ESHUTDOWN:
		/* this urb is terminated, clean up */
		dev_dbg(dev, "%s - urb shutting down with status: %d\n",
			__func__, urb->status);
		return;
	default:
		dev_dbg(dev, "%s - nonzero urb status received: %d\n",
			__func__, urb->status);
		goto exit;
	}

	/* handle updates to device state */
	input_report_key(pm->input, BTN_0, pm->data[0] & 0x01);
	input_report_rel(pm->input, REL_DIAL, pm->data[1]);
	input_sync(pm->input);

exit:
	retval = usb_submit_urb (urb, GFP_ATOMIC);
	if (retval)
		dev_err(dev, "%s - usb_submit_urb failed with result: %d\n",
			__func__, retval);
}

/* Decide if we need to issue a control message and do so. Must be called with pm->lock taken */
static void powermate_sync_state(struct powermate_device *pm)
{
	if (pm->requires_update == 0)
		return; /* no updates are required */
	if (pm->config->status == -EINPROGRESS)
		return; /* an update is already in progress; it'll issue this update when it completes */

	if (pm->requires_update & UPDATE_PULSE_ASLEEP){
		pm->configcr->wValue = cpu_to_le16( SET_PULSE_ASLEEP );
		pm->configcr->wIndex = cpu_to_le16( pm->pulse_asleep ? 1 : 0 );
		pm->requires_update &= ~UPDATE_PULSE_ASLEEP;
	}else if (pm->requires_update & UPDATE_PULSE_AWAKE){
		pm->configcr->wValue = cpu_to_le16( SET_PULSE_AWAKE );
		pm->configcr->wIndex = cpu_to_le16( pm->pulse_awake ? 1 : 0 );
		pm->requires_update &= ~UPDATE_PULSE_AWAKE;
	}else if (pm->requires_update & UPDATE_PULSE_MODE){
		int op, arg;
		/* the powermate takes an operation and an argument for its pulse algorithm.
		   the operation can be:
		   0: divide the speed
		   1: pulse at normal speed
		   2: multiply the speed
		   the argument only has an effect for operations 0 and 2, and ranges between
		   1 (least effect) to 255 (maximum effect).

		   thus, several states are equivalent and are coalesced into one state.

		   we map this onto a range from 0 to 510, with:
		   0 -- 254    -- use divide (0 = slowest)
		   255         -- use normal speed
		   256 -- 510  -- use multiple (510 = fastest).

		   Only values of 'arg' quite close to 255 are particularly useful/spectacular.
		*/
		if (pm->pulse_speed < 255) {
			op = 0;                   // divide
			arg = 255 - pm->pulse_speed;
		} else if (pm->pulse_speed > 255) {
			op = 2;                   // multiply
			arg = pm->pulse_speed - 255;
		} else {
			op = 1;                   // normal speed
			arg = 0;                  // can be any value
		}
		pm->configcr->wValue = cpu_to_le16( (pm->pulse_table << 8) | SET_PULSE_MODE );
		pm->configcr->wIndex = cpu_to_le16( (arg << 8) | op );
		pm->requires_update &= ~UPDATE_PULSE_MODE;
	} else if (pm->requires_update & UPDATE_STATIC_BRIGHTNESS) {
		pm->configcr->wValue = cpu_to_le16( SET_STATIC_BRIGHTNESS );
		pm->configcr->wIndex = cpu_to_le16( pm->static_brightness );
		pm->requires_update &= ~UPDATE_STATIC_BRIGHTNESS;
	} else {
		printk(KERN_ERR "powermate: unknown update required");
		pm->requires_update = 0; /* fudge the bug */
		return;
	}

/*	printk("powermate: %04x %04x\n", pm->configcr->wValue, pm->configcr->wIndex); */

	pm->configcr->bRequestType = 0x41; /* vendor request */
	pm->configcr->bRequest = 0x01;
	pm->configcr->wLength = 0;

	usb_fill_control_urb(pm->config, pm->udev, usb_sndctrlpipe(pm->udev, 0),
			     (void *) pm->configcr, NULL, 0,
			     powermate_config_complete, pm);

	if (usb_submit_urb(pm->config, GFP_ATOMIC))
		printk(KERN_ERR "powermate: usb_submit_urb(config) failed");
}

/* Called when our asynchronous control message completes. We may need to issue another immediately */
static void powermate_config_complete(struct urb *urb)
{
	struct powermate_device *pm = urb->context;
	unsigned long flags;

	if (urb->status)
		printk(KERN_ERR "powermate: config urb returned %d\n", urb->status);

	spin_lock_irqsave(&pm->lock, flags);
	powermate_sync_state(pm);
	spin_unlock_irqrestore(&pm->lock, flags);
}

/* Set the LED up as described and begin the sync with the hardware if required */
static void powermate_pulse_led(struct powermate_device *pm, int static_brightness, int pulse_speed,
				int pulse_table, int pulse_asleep, int pulse_awake)
{
	unsigned long flags;

	if (pulse_speed < 0)
		pulse_speed = 0;
	if (pulse_table < 0)
		pulse_table = 0;
	if (pulse_speed > 510)
		pulse_speed = 510;
	if (pulse_table > 2)
		pulse_table = 2;

	pulse_asleep = !!pulse_asleep;
	pulse_awake = !!pulse_awake;


	spin_lock_irqsave(&pm->lock, flags);

	/* mark state updates which are required */
	if (static_brightness != pm->static_brightness) {
		pm->static_brightness = static_brightness;
		pm->requires_update |= UPDATE_STATIC_BRIGHTNESS;
	}
	if (pulse_asleep != pm->pulse_asleep) {
		pm->pulse_asleep = pulse_asleep;
		pm->requires_update |= (UPDATE_PULSE_ASLEEP | UPDATE_STATIC_BRIGHTNESS);
	}
	if (pulse_awake != pm->pulse_awake) {
		pm->pulse_awake = pulse_awake;
		pm->requires_update |= (UPDATE_PULSE_AWAKE | UPDATE_STATIC_BRIGHTNESS);
	}
	if (pulse_speed != pm->pulse_speed || pulse_table != pm->pulse_table) {
		pm->pulse_speed = pulse_speed;
		pm->pulse_table = pulse_table;
		pm->requires_update |= UPDATE_PULSE_MODE;
	}

	powermate_sync_state(pm);

	spin_unlock_irqrestore(&pm->lock, flags);
}

/* Callback from the Input layer when an event arrives from userspace to configure the LED */
static int powermate_input_event(struct input_dev *dev, unsigned int type, unsigned int code, int _value)
{
	unsigned int command = (unsigned int)_value;
	struct powermate_device *pm = input_get_drvdata(dev);

	if (type == EV_MSC && code == MSC_PULSELED){
		/*
		    bits  0- 7: 8 bits: LED brightness
		    bits  8-16: 9 bits: pulsing speed modifier (0 ... 510); 0-254 = slower, 255 = standard, 256-510 = faster.
		    bits 17-18: 2 bits: pulse table (0, 1, 2 valid)
		    bit     19: 1 bit : pulse whilst asleep?
		    bit     20: 1 bit : pulse constantly?
		*/
		int static_brightness = command & 0xFF;   // bits 0-7
		int pulse_speed = (command >> 8) & 0x1FF; // bits 8-16
		int pulse_table = (command >> 17) & 0x3;  // bits 17-18
		int pulse_asleep = (command >> 19) & 0x1; // bit 19
		int pulse_awake  = (command >> 20) & 0x1; // bit 20

		powermate_pulse_led(pm, static_brightness, pulse_speed, pulse_table, pulse_asleep, pulse_awake);
	}

	return 0;
}

static int powermate_alloc_buffers(struct usb_device *udev, struct powermate_device *pm)
{
	pm->data = usb_alloc_coherent(udev, POWERMATE_PAYLOAD_SIZE_MAX,
				      GFP_KERNEL, &pm->data_dma);
	if (!pm->data)
		return -1;

	pm->configcr = kmalloc(sizeof(*(pm->configcr)), GFP_KERNEL);
	if (!pm->configcr)
		return -ENOMEM;

	return 0;
}

static void powermate_free_buffers(struct usb_device *udev, struct powermate_device *pm)
{
	usb_free_coherent(udev, POWERMATE_PAYLOAD_SIZE_MAX,
			  pm->data, pm->data_dma);
	kfree(pm->configcr);
}

/* Called whenever a USB device matching one in our supported devices table is connected */
static int powermate_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
	struct usb_device *udev = interface_to_usbdev (intf);
	struct usb_host_interface *interface;
	struct usb_endpoint_descriptor *endpoint;
	struct powermate_device *pm;
	struct input_dev *input_dev;
	int pipe, maxp;
	int error = -ENOMEM;

	interface = intf->cur_altsetting;
	if (interface->desc.bNumEndpoints < 1)
		return -EINVAL;

	endpoint = &interface->endpoint[0].desc;
	if (!usb_endpoint_is_int_in(endpoint))
		return -EIO;

	usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
		0x0a, USB_TYPE_CLASS | USB_RECIP_INTERFACE,
		0, interface->desc.bInterfaceNumber, NULL, 0,
		USB_CTRL_SET_TIMEOUT);

	pm = kzalloc(sizeof(struct powermate_device), GFP_KERNEL);
	input_dev = input_allocate_device();
	if (!pm || !input_dev)
		goto fail1;

	if (powermate_alloc_buffers(udev, pm))
		goto fail2;

	pm->irq = usb_alloc_urb(0, GFP_KERNEL);
	if (!pm->irq)
		goto fail2;

	pm->config = usb_alloc_urb(0, GFP_KERNEL);
	if (!pm->config)
		goto fail3;

	pm->udev = udev;
	pm->intf = intf;
	pm->input = input_dev;

	usb_make_path(udev, pm->phys, sizeof(pm->phys));
	strlcat(pm->phys, "/input0", sizeof(pm->phys));

	spin_lock_init(&pm->lock);

	switch (le16_to_cpu(udev->descriptor.idProduct)) {
	case POWERMATE_PRODUCT_NEW:
		input_dev->name = pm_name_powermate;
		break;
	case POWERMATE_PRODUCT_OLD:
		input_dev->name = pm_name_soundknob;
		break;
	default:
		input_dev->name = pm_name_soundknob;
		printk(KERN_WARNING "powermate: unknown product id %04x\n",
		       le16_to_cpu(udev->descriptor.idProduct));
	}

	input_dev->phys = pm->phys;
	usb_to_input_id(udev, &input_dev->id);
	input_dev->dev.parent = &intf->dev;

	input_set_drvdata(input_dev, pm);

	input_dev->event = powermate_input_event;

	input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REL) |
		BIT_MASK(EV_MSC);
	input_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);
	input_dev->relbit[BIT_WORD(REL_DIAL)] = BIT_MASK(REL_DIAL);
	input_dev->mscbit[BIT_WORD(MSC_PULSELED)] = BIT_MASK(MSC_PULSELED);

	/* get a handle to the interrupt data pipe */
	pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress);
	maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe));

	if (maxp < POWERMATE_PAYLOAD_SIZE_MIN || maxp > POWERMATE_PAYLOAD_SIZE_MAX) {
		printk(KERN_WARNING "powermate: Expected payload of %d--%d bytes, found %d bytes!\n",
			POWERMATE_PAYLOAD_SIZE_MIN, POWERMATE_PAYLOAD_SIZE_MAX, maxp);
		maxp = POWERMATE_PAYLOAD_SIZE_MAX;
	}

	usb_fill_int_urb(pm->irq, udev, pipe, pm->data,
			 maxp, powermate_irq,
			 pm, endpoint->bInterval);
	pm->irq->transfer_dma = pm->data_dma;
	pm->irq->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

	/* register our interrupt URB with the USB system */
	if (usb_submit_urb(pm->irq, GFP_KERNEL)) {
		error = -EIO;
		goto fail4;
	}

	error = input_register_device(pm->input);
	if (error)
		goto fail5;


	/* force an update of everything */
	pm->requires_update = UPDATE_PULSE_ASLEEP | UPDATE_PULSE_AWAKE | UPDATE_PULSE_MODE | UPDATE_STATIC_BRIGHTNESS;
	powermate_pulse_led(pm, 0x80, 255, 0, 1, 0); // set default pulse parameters

	usb_set_intfdata(intf, pm);
	return 0;

 fail5:	usb_kill_urb(pm->irq);
 fail4:	usb_free_urb(pm->config);
 fail3:	usb_free_urb(pm->irq);
 fail2:	powermate_free_buffers(udev, pm);
 fail1:	input_free_device(input_dev);
	kfree(pm);
	return error;
}

/* Called when a USB device we've accepted ownership of is removed */
static void powermate_disconnect(struct usb_interface *intf)
{
	struct powermate_device *pm = usb_get_intfdata (intf);

	usb_set_intfdata(intf, NULL);
	if (pm) {
		pm->requires_update = 0;
		usb_kill_urb(pm->irq);
		input_unregister_device(pm->input);
		usb_kill_urb(pm->config);
		usb_free_urb(pm->irq);
		usb_free_urb(pm->config);
		powermate_free_buffers(interface_to_usbdev(intf), pm);

		kfree(pm);
	}
}

static const struct usb_device_id powermate_devices[] = {
	{ USB_DEVICE(POWERMATE_VENDOR, POWERMATE_PRODUCT_NEW) },
	{ USB_DEVICE(POWERMATE_VENDOR, POWERMATE_PRODUCT_OLD) },
	{ USB_DEVICE(CONTOUR_VENDOR, CONTOUR_JOG) },
	{ } /* Terminating entry */
};

MODULE_DEVICE_TABLE (usb, powermate_devices);

static struct usb_driver powermate_driver = {
        .name =         "powermate",
        .probe =        powermate_probe,
        .disconnect =   powermate_disconnect,
        .id_table =     powermate_devices,
};

module_usb_driver(powermate_driver);

MODULE_AUTHOR( "William R Sowerbutts" );
MODULE_DESCRIPTION( "Griffin Technology, Inc PowerMate driver" );
MODULE_LICENSE("GPL");
n> struct mips_ejtag_fdc_tty *priv = (void *)opaque; mips_ejtag_fdc_handle(priv); if (!priv->removing) mod_timer(&priv->poll_timer, jiffies + FDC_TTY_POLL); } /* TTY Port operations */ static int mips_ejtag_fdc_tty_port_activate(struct tty_port *port, struct tty_struct *tty) { struct mips_ejtag_fdc_tty_port *dport = container_of(port, struct mips_ejtag_fdc_tty_port, port); void *rx_buf; /* Allocate the buffer we use for writing data */ if (tty_port_alloc_xmit_buf(port) < 0) goto err; /* Allocate the buffer we use for reading data */ rx_buf = kzalloc(RX_BUF_SIZE, GFP_KERNEL); if (!rx_buf) goto err_free_xmit; raw_spin_lock_irq(&dport->rx_lock); dport->rx_buf = rx_buf; raw_spin_unlock_irq(&dport->rx_lock); return 0; err_free_xmit: tty_port_free_xmit_buf(port); err: return -ENOMEM; } static void mips_ejtag_fdc_tty_port_shutdown(struct tty_port *port) { struct mips_ejtag_fdc_tty_port *dport = container_of(port, struct mips_ejtag_fdc_tty_port, port); struct mips_ejtag_fdc_tty *priv = dport->driver; void *rx_buf; unsigned int count; spin_lock(&dport->xmit_lock); count = dport->xmit_cnt; spin_unlock(&dport->xmit_lock); if (count) { /* * There's still data to write out, so wake and wait for the * writer thread to drain the buffer. */ wake_up_interruptible(&priv->waitqueue); wait_for_completion(&dport->xmit_empty); } /* Null the read buffer (timer could still be running!) */ raw_spin_lock_irq(&dport->rx_lock); rx_buf = dport->rx_buf; dport->rx_buf = NULL; raw_spin_unlock_irq(&dport->rx_lock); /* Free the read buffer */ kfree(rx_buf); /* Free the write buffer */ tty_port_free_xmit_buf(port); } static const struct tty_port_operations mips_ejtag_fdc_tty_port_ops = { .activate = mips_ejtag_fdc_tty_port_activate, .shutdown = mips_ejtag_fdc_tty_port_shutdown, }; /* TTY operations */ static int mips_ejtag_fdc_tty_install(struct tty_driver *driver, struct tty_struct *tty) { struct mips_ejtag_fdc_tty *priv = driver->driver_state; tty->driver_data = &priv->ports[tty->index]; return tty_port_install(&priv->ports[tty->index].port, driver, tty); } static int mips_ejtag_fdc_tty_open(struct tty_struct *tty, struct file *filp) { return tty_port_open(tty->port, tty, filp); } static void mips_ejtag_fdc_tty_close(struct tty_struct *tty, struct file *filp) { return tty_port_close(tty->port, tty, filp); } static void mips_ejtag_fdc_tty_hangup(struct tty_struct *tty) { struct mips_ejtag_fdc_tty_port *dport = tty->driver_data; struct mips_ejtag_fdc_tty *priv = dport->driver; /* Drop any data in the xmit buffer */ spin_lock(&dport->xmit_lock); if (dport->xmit_cnt) { atomic_sub(dport->xmit_cnt, &priv->xmit_total); dport->xmit_cnt = 0; dport->xmit_head = 0; dport->xmit_tail = 0; complete(&dport->xmit_empty); } spin_unlock(&dport->xmit_lock); tty_port_hangup(tty->port); } static int mips_ejtag_fdc_tty_write(struct tty_struct *tty, const unsigned char *buf, int total) { int count, block; struct mips_ejtag_fdc_tty_port *dport = tty->driver_data; struct mips_ejtag_fdc_tty *priv = dport->driver; /* * Write to output buffer. * * The reason that we asynchronously write the buffer is because if we * were to write the buffer synchronously then because the channels are * per-CPU the buffer would be written to the channel of whatever CPU * we're running on. * * What we actually want to happen is have all input and output done on * one CPU. */ spin_lock(&dport->xmit_lock); /* Work out how many bytes we can write to the xmit buffer */ total = min(total, (int)(priv->xmit_size - dport->xmit_cnt)); atomic_add(total, &priv->xmit_total); dport->xmit_cnt += total; /* Write the actual bytes (may need splitting if it wraps) */ for (count = total; count; count -= block) { block = min(count, (int)(priv->xmit_size - dport->xmit_head)); memcpy(dport->port.xmit_buf + dport->xmit_head, buf, block); dport->xmit_head += block; if (dport->xmit_head >= priv->xmit_size) dport->xmit_head -= priv->xmit_size; buf += block; } count = dport->xmit_cnt; /* Xmit buffer no longer empty? */ if (count) reinit_completion(&dport->xmit_empty); spin_unlock(&dport->xmit_lock); /* Wake up the kthread */ if (total) wake_up_interruptible(&priv->waitqueue); return total; } static int mips_ejtag_fdc_tty_write_room(struct tty_struct *tty) { struct mips_ejtag_fdc_tty_port *dport = tty->driver_data; struct mips_ejtag_fdc_tty *priv = dport->driver; int room; /* Report the space in the xmit buffer */ spin_lock(&dport->xmit_lock); room = priv->xmit_size - dport->xmit_cnt; spin_unlock(&dport->xmit_lock); return room; } static int mips_ejtag_fdc_tty_chars_in_buffer(struct tty_struct *tty) { struct mips_ejtag_fdc_tty_port *dport = tty->driver_data; int chars; /* Report the number of bytes in the xmit buffer */ spin_lock(&dport->xmit_lock); chars = dport->xmit_cnt; spin_unlock(&dport->xmit_lock); return chars; } static const struct tty_operations mips_ejtag_fdc_tty_ops = { .install = mips_ejtag_fdc_tty_install, .open = mips_ejtag_fdc_tty_open, .close = mips_ejtag_fdc_tty_close, .hangup = mips_ejtag_fdc_tty_hangup, .write = mips_ejtag_fdc_tty_write, .write_room = mips_ejtag_fdc_tty_write_room, .chars_in_buffer = mips_ejtag_fdc_tty_chars_in_buffer, }; int __weak get_c0_fdc_int(void) { return -1; } static int mips_ejtag_fdc_tty_probe(struct mips_cdmm_device *dev) { int ret, nport; struct mips_ejtag_fdc_tty_port *dport; struct mips_ejtag_fdc_tty *priv; struct tty_driver *driver; unsigned int cfg, tx_fifo; priv = devm_kzalloc(&dev->dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; priv->cpu = dev->cpu; priv->dev = &dev->dev; mips_cdmm_set_drvdata(dev, priv); atomic_set(&priv->xmit_total, 0); raw_spin_lock_init(&priv->lock); priv->reg = devm_ioremap_nocache(priv->dev, dev->res.start, resource_size(&dev->res)); if (!priv->reg) { dev_err(priv->dev, "ioremap failed for resource %pR\n", &dev->res); return -ENOMEM; } cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); tx_fifo = (cfg & REG_FDCFG_TXFIFOSIZE) >> REG_FDCFG_TXFIFOSIZE_SHIFT; /* Disable interrupts */ cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES); cfg |= REG_FDCFG_TXINTTHRES_DISABLED; cfg |= REG_FDCFG_RXINTTHRES_DISABLED; mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); /* Make each port's xmit FIFO big enough to fill FDC TX FIFO */ priv->xmit_size = min(tx_fifo * 4, (unsigned int)SERIAL_XMIT_SIZE); driver = tty_alloc_driver(NUM_TTY_CHANNELS, TTY_DRIVER_REAL_RAW); if (IS_ERR(driver)) return PTR_ERR(driver); priv->driver = driver; driver->driver_name = "ejtag_fdc"; snprintf(priv->fdc_name, sizeof(priv->fdc_name), "ttyFDC%u", dev->cpu); snprintf(priv->driver_name, sizeof(priv->driver_name), "%sc", priv->fdc_name); driver->name = priv->driver_name; driver->major = 0; /* Auto-allocate */ driver->minor_start = 0; driver->type = TTY_DRIVER_TYPE_SERIAL; driver->subtype = SERIAL_TYPE_NORMAL; driver->init_termios = tty_std_termios; driver->init_termios.c_cflag |= CLOCAL; driver->driver_state = priv; tty_set_operations(driver, &mips_ejtag_fdc_tty_ops); for (nport = 0; nport < NUM_TTY_CHANNELS; nport++) { dport = &priv->ports[nport]; dport->driver = priv; tty_port_init(&dport->port); dport->port.ops = &mips_ejtag_fdc_tty_port_ops; raw_spin_lock_init(&dport->rx_lock); spin_lock_init(&dport->xmit_lock); /* The xmit buffer starts empty, i.e. completely written */ init_completion(&dport->xmit_empty); complete(&dport->xmit_empty); } /* Set up the console */ mips_ejtag_fdc_con.regs[dev->cpu] = priv->reg; if (dev->cpu == 0) mips_ejtag_fdc_con.tty_drv = driver; init_waitqueue_head(&priv->waitqueue); priv->thread = kthread_create(mips_ejtag_fdc_put, priv, priv->fdc_name); if (IS_ERR(priv->thread)) { ret = PTR_ERR(priv->thread); dev_err(priv->dev, "Couldn't create kthread (%d)\n", ret); goto err_destroy_ports; } /* * Bind the writer thread to the right CPU so it can't migrate. * The channels are per-CPU and we want all channel I/O to be on a * single predictable CPU. */ kthread_bind(priv->thread, dev->cpu); wake_up_process(priv->thread); /* Look for an FDC IRQ */ priv->irq = get_c0_fdc_int(); /* Try requesting the IRQ */ if (priv->irq >= 0) { /* * IRQF_SHARED, IRQF_COND_SUSPEND: The FDC IRQ may be shared with * other local interrupts such as the timer which sets * IRQF_TIMER (including IRQF_NO_SUSPEND). * * IRQF_NO_THREAD: The FDC IRQ isn't individually maskable so it * cannot be deferred and handled by a thread on RT kernels. For * this reason any spinlocks used from the ISR are raw. */ ret = devm_request_irq(priv->dev, priv->irq, mips_ejtag_fdc_isr, IRQF_PERCPU | IRQF_SHARED | IRQF_NO_THREAD | IRQF_COND_SUSPEND, priv->fdc_name, priv); if (ret) priv->irq = -1; } if (priv->irq >= 0) { /* IRQ is usable, enable RX interrupt */ raw_spin_lock_irq(&priv->lock); cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); cfg &= ~REG_FDCFG_RXINTTHRES; cfg |= REG_FDCFG_RXINTTHRES_NOTEMPTY; mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); raw_spin_unlock_irq(&priv->lock); } else { /* If we didn't get an usable IRQ, poll instead */ setup_pinned_timer(&priv->poll_timer, mips_ejtag_fdc_tty_timer, (unsigned long)priv); priv->poll_timer.expires = jiffies + FDC_TTY_POLL; /* * Always attach the timer to the right CPU. The channels are * per-CPU so all polling should be from a single CPU. */ add_timer_on(&priv->poll_timer, dev->cpu); dev_info(priv->dev, "No usable IRQ, polling enabled\n"); } ret = tty_register_driver(driver); if (ret < 0) { dev_err(priv->dev, "Couldn't install tty driver (%d)\n", ret); goto err_stop_irq; } return 0; err_stop_irq: if (priv->irq >= 0) { raw_spin_lock_irq(&priv->lock); cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); /* Disable interrupts */ cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES); cfg |= REG_FDCFG_TXINTTHRES_DISABLED; cfg |= REG_FDCFG_RXINTTHRES_DISABLED; mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); raw_spin_unlock_irq(&priv->lock); } else { priv->removing = true; del_timer_sync(&priv->poll_timer); } kthread_stop(priv->thread); err_destroy_ports: if (dev->cpu == 0) mips_ejtag_fdc_con.tty_drv = NULL; for (nport = 0; nport < NUM_TTY_CHANNELS; nport++) { dport = &priv->ports[nport]; tty_port_destroy(&dport->port); } put_tty_driver(priv->driver); return ret; } static int mips_ejtag_fdc_tty_cpu_down(struct mips_cdmm_device *dev) { struct mips_ejtag_fdc_tty *priv = mips_cdmm_get_drvdata(dev); unsigned int cfg; if (priv->irq >= 0) { raw_spin_lock_irq(&priv->lock); cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); /* Disable interrupts */ cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES); cfg |= REG_FDCFG_TXINTTHRES_DISABLED; cfg |= REG_FDCFG_RXINTTHRES_DISABLED; mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); raw_spin_unlock_irq(&priv->lock); } else { priv->removing = true; del_timer_sync(&priv->poll_timer); } kthread_stop(priv->thread); return 0; } static int mips_ejtag_fdc_tty_cpu_up(struct mips_cdmm_device *dev) { struct mips_ejtag_fdc_tty *priv = mips_cdmm_get_drvdata(dev); unsigned int cfg; int ret = 0; if (priv->irq >= 0) { /* * IRQ is usable, enable RX interrupt * This must be before kthread is restarted, as kthread may * enable TX interrupt. */ raw_spin_lock_irq(&priv->lock); cfg = mips_ejtag_fdc_read(priv, REG_FDCFG); cfg &= ~(REG_FDCFG_TXINTTHRES | REG_FDCFG_RXINTTHRES); cfg |= REG_FDCFG_TXINTTHRES_DISABLED; cfg |= REG_FDCFG_RXINTTHRES_NOTEMPTY; mips_ejtag_fdc_write(priv, REG_FDCFG, cfg); raw_spin_unlock_irq(&priv->lock); } else { /* Restart poll timer */ priv->removing = false; add_timer_on(&priv->poll_timer, dev->cpu); } /* Restart the kthread */ priv->thread = kthread_create(mips_ejtag_fdc_put, priv, priv->fdc_name); if (IS_ERR(priv->thread)) { ret = PTR_ERR(priv->thread); dev_err(priv->dev, "Couldn't re-create kthread (%d)\n", ret); goto out; } /* Bind it back to the right CPU and set it off */ kthread_bind(priv->thread, dev->cpu); wake_up_process(priv->thread); out: return ret; } static struct mips_cdmm_device_id mips_ejtag_fdc_tty_ids[] = { { .type = 0xfd }, { } }; static struct mips_cdmm_driver mips_ejtag_fdc_tty_driver = { .drv = { .name = "mips_ejtag_fdc", }, .probe = mips_ejtag_fdc_tty_probe, .cpu_down = mips_ejtag_fdc_tty_cpu_down, .cpu_up = mips_ejtag_fdc_tty_cpu_up, .id_table = mips_ejtag_fdc_tty_ids, }; builtin_mips_cdmm_driver(mips_ejtag_fdc_tty_driver); static int __init mips_ejtag_fdc_init_console(void) { return mips_ejtag_fdc_console_init(&mips_ejtag_fdc_con); } console_initcall(mips_ejtag_fdc_init_console); #ifdef CONFIG_MIPS_EJTAG_FDC_EARLYCON static struct mips_ejtag_fdc_console mips_ejtag_fdc_earlycon = { .cons = { .name = "early_fdc", .write = mips_ejtag_fdc_console_write, .flags = CON_PRINTBUFFER | CON_BOOT, .index = CONSOLE_CHANNEL, }, .lock = __RAW_SPIN_LOCK_UNLOCKED(mips_ejtag_fdc_earlycon.lock), }; int __init setup_early_fdc_console(void) { return mips_ejtag_fdc_console_init(&mips_ejtag_fdc_earlycon); } #endif #ifdef CONFIG_MIPS_EJTAG_FDC_KGDB /* read buffer to allow decompaction */ static unsigned int kgdbfdc_rbuflen; static unsigned int kgdbfdc_rpos; static char kgdbfdc_rbuf[4]; /* write buffer to allow compaction */ static unsigned int kgdbfdc_wbuflen; static char kgdbfdc_wbuf[4]; static void __iomem *kgdbfdc_setup(void) { void __iomem *regs; unsigned int cpu; /* Find address, piggy backing off console percpu regs */ cpu = smp_processor_id(); regs = mips_ejtag_fdc_con.regs[cpu]; /* First console output on this CPU? */ if (!regs) { regs = mips_cdmm_early_probe(0xfd); mips_ejtag_fdc_con.regs[cpu] = regs; } /* Already tried and failed to find FDC on this CPU? */ if (IS_ERR(regs)) return regs; return regs; } /* read a character from the read buffer, filling from FDC RX FIFO */ static int kgdbfdc_read_char(void) { unsigned int stat, channel, data; void __iomem *regs; /* No more data, try and read another FDC word from RX FIFO */ if (kgdbfdc_rpos >= kgdbfdc_rbuflen) { kgdbfdc_rpos = 0; kgdbfdc_rbuflen = 0; regs = kgdbfdc_setup(); if (IS_ERR(regs)) return NO_POLL_CHAR; /* Read next word from KGDB channel */ do { stat = __raw_readl(regs + REG_FDSTAT); /* No data waiting? */ if (stat & REG_FDSTAT_RXE) return NO_POLL_CHAR; /* Read next word */ channel = (stat & REG_FDSTAT_RXCHAN) >> REG_FDSTAT_RXCHAN_SHIFT; data = __raw_readl(regs + REG_FDRX); } while (channel != CONFIG_MIPS_EJTAG_FDC_KGDB_CHAN); /* Decode into rbuf */ kgdbfdc_rbuflen = mips_ejtag_fdc_decode(data, kgdbfdc_rbuf); } pr_devel("kgdbfdc r %c\n", kgdbfdc_rbuf[kgdbfdc_rpos]); return kgdbfdc_rbuf[kgdbfdc_rpos++]; } /* push an FDC word from write buffer to TX FIFO */ static void kgdbfdc_push_one(void) { const char *bufs[1] = { kgdbfdc_wbuf }; struct fdc_word word; void __iomem *regs; unsigned int i; /* Construct a word from any data in buffer */ word = mips_ejtag_fdc_encode(bufs, &kgdbfdc_wbuflen, 1); /* Relocate any remaining data to beginnning of buffer */ kgdbfdc_wbuflen -= word.bytes; for (i = 0; i < kgdbfdc_wbuflen; ++i) kgdbfdc_wbuf[i] = kgdbfdc_wbuf[i + word.bytes]; regs = kgdbfdc_setup(); if (IS_ERR(regs)) return; /* Busy wait until there's space in fifo */ while (__raw_readl(regs + REG_FDSTAT) & REG_FDSTAT_TXF) ; __raw_writel(word.word, regs + REG_FDTX(CONFIG_MIPS_EJTAG_FDC_KGDB_CHAN)); } /* flush the whole write buffer to the TX FIFO */ static void kgdbfdc_flush(void) { while (kgdbfdc_wbuflen) kgdbfdc_push_one(); } /* write a character into the write buffer, writing out if full */ static void kgdbfdc_write_char(u8 chr) { pr_devel("kgdbfdc w %c\n", chr); kgdbfdc_wbuf[kgdbfdc_wbuflen++] = chr; if (kgdbfdc_wbuflen >= sizeof(kgdbfdc_wbuf)) kgdbfdc_push_one(); } static struct kgdb_io kgdbfdc_io_ops = { .name = "kgdbfdc", .read_char = kgdbfdc_read_char, .write_char = kgdbfdc_write_char, .flush = kgdbfdc_flush, }; static int __init kgdbfdc_init(void) { kgdb_register_io_module(&kgdbfdc_io_ops); return 0; } early_initcall(kgdbfdc_init); #endif