// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 1999-2001 Vojtech Pavlik
*
* Based on the work of:
* David Thompson
*/
/*
* SpaceTec SpaceOrb 360 and Avenger 6dof controller driver for Linux
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/input.h>
#include <linux/serio.h>
#define DRIVER_DESC "SpaceTec SpaceOrb 360 and Avenger 6dof controller driver"
MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>" );
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_LICENSE("GPL" );
/*
* Constants.
*/
#define SPACEORB_MAX_LENGTH 64
static int spaceorb_buttons[] = { BTN_TL, BTN_TR, BTN_Y, BTN_X, BTN_B, BTN_A };
static int spaceorb_axes[] = { ABS_X, ABS_Y, ABS_Z, ABS_RX, ABS_RY, ABS_RZ };
/*
* Per-Orb data.
*/
struct spaceorb {
struct input_dev *dev;
int idx;
unsigned char data[SPACEORB_MAX_LENGTH];
char phys[32 ];
};
static unsigned char spaceorb_xor[] = "SpaceWare" ;
static unsigned char *spaceorb_errors[] = { "EEPROM storing 0 failed" , "Receive queue overflow" , "Transmit queue timeout" ,
"Bad packet" , "Power brown-out" , "EEPROM checksum error" , "Hardware fault" };
/*
* spaceorb_process_packet() decodes packets the driver receives from the
* SpaceOrb.
*/
static void spaceorb_process_packet(struct spaceorb *spaceorb)
{
struct input_dev *dev = spaceorb->dev;
unsigned char *data = spaceorb->data;
unsigned char c = 0 ;
int axes[6 ];
int i;
if (spaceorb->idx < 2 ) return ;
for (i = 0 ; i < spaceorb->idx; i++) c ^= data[i];
if (c) return ;
switch (data[0 ]) {
case 'R' : /* Reset packet */
spaceorb->data[spaceorb->idx - 1 ] = 0 ;
for (i = 1 ; i < spaceorb->idx && spaceorb->data[i] == ' ' ; i++);
printk(KERN_INFO "input: %s [%s] is %s\n" ,
dev->name, spaceorb->data + i, spaceorb->phys);
break ;
case 'D' : /* Ball + button data */
if (spaceorb->idx != 12 ) return ;
for (i = 0 ; i < 9 ; i++) spaceorb->data[i+2 ] ^= spaceorb_xor[i];
axes[0 ] = ( data[2 ] << 3 ) | (data[ 3 ] >> 4 );
axes[1 ] = ((data[3 ] & 0 x0f) << 6 ) | (data[ 4 ] >> 1 );
axes[2 ] = ((data[4 ] & 0 x01) << 9 ) | (data[ 5 ] << 2 ) | (data[4 ] >> 5 );
axes[3 ] = ((data[6 ] & 0 x1f) << 5 ) | (data[ 7 ] >> 2 );
axes[4 ] = ((data[7 ] & 0 x03) << 8 ) | (data[ 8 ] << 1 ) | (data[7 ] >> 6 );
axes[5 ] = ((data[9 ] & 0 x3f) << 4 ) | (data[10 ] >> 3 );
for (i = 0 ; i < 6 ; i++)
input_report_abs(dev, spaceorb_axes[i], axes[i] - ((axes[i] & 0 x200) ? 1024 : 0 ));
for (i = 0 ; i < 6 ; i++)
input_report_key(dev, spaceorb_buttons[i], (data[1 ] >> i) & 1 );
break ;
case 'K' : /* Button data */
if (spaceorb->idx != 5 ) return ;
for (i = 0 ; i < 6 ; i++)
input_report_key(dev, spaceorb_buttons[i], (data[2 ] >> i) & 1 );
break ;
case 'E' : /* Error packet */
if (spaceorb->idx != 4 ) return ;
printk(KERN_ERR "spaceorb: Device error. [ " );
for (i = 0 ; i < 7 ; i++) if (data[1 ] & (1 << i)) printk("%s " , spaceorb_errors[i]);
printk("]\n" );
break ;
}
input_sync(dev);
}
static irqreturn_t spaceorb_interrupt(struct serio *serio,
unsigned char data, unsigned int flags)
{
struct spaceorb* spaceorb = serio_get_drvdata(serio);
if (~data & 0 x80) {
if (spaceorb->idx) spaceorb_process_packet(spaceorb);
spaceorb->idx = 0 ;
}
if (spaceorb->idx < SPACEORB_MAX_LENGTH)
spaceorb->data[spaceorb->idx++] = data & 0 x7f;
return IRQ_HANDLED;
}
/*
* spaceorb_disconnect() is the opposite of spaceorb_connect()
*/
static void spaceorb_disconnect(struct serio *serio)
{
struct spaceorb* spaceorb = serio_get_drvdata(serio);
serio_close(serio);
serio_set_drvdata(serio, NULL);
input_unregister_device(spaceorb->dev);
kfree(spaceorb);
}
/*
* spaceorb_connect() is the routine that is called when someone adds a
* new serio device that supports SpaceOrb/Avenger protocol and registers
* it as an input device.
*/
static int spaceorb_connect(struct serio *serio, struct serio_driver *drv)
{
struct spaceorb *spaceorb;
struct input_dev *input_dev;
int err = -ENOMEM;
int i;
spaceorb = kzalloc(sizeof (*spaceorb), GFP_KERNEL);
input_dev = input_allocate_device();
if (!spaceorb || !input_dev)
goto fail1;
spaceorb->dev = input_dev;
snprintf(spaceorb->phys, sizeof (spaceorb->phys), "%s/input0" , serio->phys);
input_dev->name = "SpaceTec SpaceOrb 360 / Avenger" ;
input_dev->phys = spaceorb->phys;
input_dev->id.bustype = BUS_RS232;
input_dev->id.vendor = SERIO_SPACEORB;
input_dev->id.product = 0 x0001;
input_dev->id.version = 0 x0100;
input_dev->dev.parent = &serio->dev;
input_dev->evbit[0 ] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
for (i = 0 ; i < 6 ; i++)
set_bit(spaceorb_buttons[i], input_dev->keybit);
for (i = 0 ; i < 6 ; i++)
input_set_abs_params(input_dev, spaceorb_axes[i], -508 , 508 , 0 , 0 );
serio_set_drvdata(serio, spaceorb);
err = serio_open(serio, drv);
if (err)
goto fail2;
err = input_register_device(spaceorb->dev);
if (err)
goto fail3;
return 0 ;
fail3: serio_close(serio);
fail2: serio_set_drvdata(serio, NULL);
fail1: input_free_device(input_dev);
kfree(spaceorb);
return err;
}
/*
* The serio driver structure.
*/
static const struct serio_device_id spaceorb_serio_ids[] = {
{
.type = SERIO_RS232,
.proto = SERIO_SPACEORB,
.id = SERIO_ANY,
.extra = SERIO_ANY,
},
{ 0 }
};
MODULE_DEVICE_TABLE(serio, spaceorb_serio_ids);
static struct serio_driver spaceorb_drv = {
.driver = {
.name = "spaceorb" ,
},
.description = DRIVER_DESC,
.id_table = spaceorb_serio_ids,
.interrupt = spaceorb_interrupt,
.connect = spaceorb_connect,
.disconnect = spaceorb_disconnect,
};
module_serio_driver(spaceorb_drv);
Messung V0.5 in Prozent C=95 H=93 G=93
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet am 2026-06-07)
¤
*© Formatika GbR, Deutschland