diff options
author | Stephen Rothwell <sfr@canb.auug.org.au> | 2008-05-26 14:50:26 +1000 |
---|---|---|
committer | Stephen Rothwell <sfr@canb.auug.org.au> | 2008-05-26 14:50:26 +1000 |
commit | 077eee7503bbc9a5ca80ac4c27fa8c6980d75a5a (patch) | |
tree | 764e89d6c962a1bbd079ecc1024d89961f562807 | |
parent | 634c98e8cdc1340957e9bcd91ef726faaf4dc296 (diff) | |
parent | d8661bc33f001c87fec2934c5be7c62705db95e4 (diff) |
Merge branch 'quilt/ldp.next'
-rw-r--r-- | drivers/hid/usbhid/hid-quirks.c | 2 | ||||
-rw-r--r-- | drivers/input/misc/Kconfig | 12 | ||||
-rw-r--r-- | drivers/input/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/input/misc/appleir.c | 361 | ||||
-rw-r--r-- | drivers/media/video/Kconfig | 9 | ||||
-rw-r--r-- | drivers/media/video/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/video/s2255drv.c | 2486 | ||||
-rw-r--r-- | drivers/net/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/usb/Kconfig | 10 | ||||
-rw-r--r-- | drivers/net/usb/Makefile | 1 | ||||
-rw-r--r-- | drivers/net/usb/hso.c | 2836 |
11 files changed, 5720 insertions, 0 deletions
diff --git a/drivers/hid/usbhid/hid-quirks.c b/drivers/hid/usbhid/hid-quirks.c index b98ec426b4d4..589bd1804225 100644 --- a/drivers/hid/usbhid/hid-quirks.c +++ b/drivers/hid/usbhid/hid-quirks.c @@ -77,6 +77,7 @@ #define USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS 0x022e #define USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY 0x030a #define USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY 0x030b +#define USB_DEVICE_ID_APPLE_IRCONTROL 0x8240 #define USB_DEVICE_ID_APPLE_IRCONTROL4 0x8242 #define USB_VENDOR_ID_ASUS 0x0b05 @@ -450,6 +451,7 @@ static const struct hid_blacklist { { USB_VENDOR_ID_AFATECH, USB_DEVICE_ID_AFATECH_AF9016, HID_QUIRK_FULLSPEED_INTERVAL }, { USB_VENDOR_ID_BELKIN, USB_DEVICE_ID_FLIP_KVM, HID_QUIRK_HIDDEV }, + { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL, HID_QUIRK_IGNORE }, { USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL4, HID_QUIRK_HIDDEV | HID_QUIRK_IGNORE_HIDINPUT }, { USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE, HID_QUIRK_HIDDEV | HID_QUIRK_IGNORE_HIDINPUT }, { USB_VENDOR_ID_MICROSOFT, USB_DEVICE_ID_SIDEWINDER_GV, HID_QUIRK_HIDINPUT }, diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 3ad8bd9f7543..ce38a8ece503 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig @@ -149,6 +149,18 @@ config INPUT_KEYSPAN_REMOTE To compile this driver as a module, choose M here: the module will be called keyspan_remote. +config INPUT_APPLEIR + tristate "Apple Mac Mini USB IR receiver (built in)" + depends on USB_ARCH_HAS_HCD + select USB + help + Say Y here if you want to use a Apple USB remote control. This + device is traditionally inside an Intel Apple Mac Mini, but might + show up in other places. + + To compile this driver as a module, choose M here: the module will + be called appleir. + config INPUT_POWERMATE tristate "Griffin PowerMate and Contour Jog support" depends on USB_ARCH_HAS_HCD diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index ebd39f291d25..a3cfff0e0e7b 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile @@ -19,3 +19,4 @@ obj-$(CONFIG_INPUT_YEALINK) += yealink.o obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o obj-$(CONFIG_INPUT_UINPUT) += uinput.o obj-$(CONFIG_INPUT_APANEL) += apanel.o +obj-$(CONFIG_INPUT_APPLEIR) += appleir.o diff --git a/drivers/input/misc/appleir.c b/drivers/input/misc/appleir.c new file mode 100644 index 000000000000..50a0bf7d2b30 --- /dev/null +++ b/drivers/input/misc/appleir.c @@ -0,0 +1,361 @@ +/* + * appleir: USB driver for the apple ir device + * + * Original driver written by James McKenzie + * Ported to recent 2.6 kernel versions by Greg Kroah-Hartman <gregkh@suse.de> + * + * Copyright (C) 2006 James McKenzie + * Copyright (C) 2008 Greg Kroah-Hartman <greg@kroah.com> + * Copyright (C) 2008 Novell Inc. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation, version 2. + * + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/input.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/usb/input.h> +#include <asm/unaligned.h> +#include <asm/byteorder.h> + +#define DRIVER_VERSION "v1.2" +#define DRIVER_AUTHOR "James McKenzie" +#define DRIVER_DESC "USB Apple MacMini IR Receiver driver" +#define DRIVER_LICENSE "GPL" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE(DRIVER_LICENSE); + +#define USB_VENDOR_ID_APPLE 0x05ac +#define USB_DEVICE_ID_APPLE_IR 0x8240 + +#define URB_SIZE 32 + +#define MAX_KEYS 8 +#define MAX_KEYS_MASK (MAX_KEYS - 1) + +struct appleir { + struct input_dev *input_dev; + u8 *data; + dma_addr_t dma_buf; + struct usb_device *usbdev; + struct urb *urb; + int timer_initted; + struct timer_list key_up_timer; + int current_key; + char phys[32]; +}; + +static struct usb_device_id appleir_ids[] = { + {USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IR)}, + {} +}; +MODULE_DEVICE_TABLE(usb, appleir_ids); + +/* I have two devices both of which report the following */ +/* 25 87 ee 83 0a + */ +/* 25 87 ee 83 0c - */ +/* 25 87 ee 83 09 << */ +/* 25 87 ee 83 06 >> */ +/* 25 87 ee 83 05 >" */ +/* 25 87 ee 83 03 menu */ +/* 26 00 00 00 00 for key repeat*/ + +/* Thomas Glanzmann reports the following responses */ +/* 25 87 ee ca 0b + */ +/* 25 87 ee ca 0d - */ +/* 25 87 ee ca 08 << */ +/* 25 87 ee ca 07 >> */ +/* 25 87 ee ca 04 >" */ +/* 25 87 ee ca 02 menu */ +/* 26 00 00 00 00 for key repeat*/ +/* He also observes the following event sometimes */ +/* sent after a key is release, which I interpret */ +/* as a flat battery message */ +/* 25 87 e0 ca 06 flat battery */ + +static int keymap[MAX_KEYS] = { + KEY_RESERVED, + KEY_MENU, + KEY_PLAYPAUSE, + KEY_NEXTSONG, + KEY_PREVIOUSSONG, + KEY_VOLUMEUP, + KEY_VOLUMEDOWN, + KEY_RESERVED, +}; + +static void dump_packet(struct appleir *appleir, char *msg, u8 *data, int len) +{ + int i; + + printk(KERN_ERR "appleir: %s (%d bytes)", msg, len); + + for (i = 0; i < len; ++i) + printk(" %02x", data[i]); + printk("\n"); +} + +static void key_up(struct appleir *appleir, int key) +{ + /* printk (KERN_ERR "key %d up\n", key); */ + input_report_key(appleir->input_dev, key, 0); + input_sync(appleir->input_dev); +} + +static void key_down(struct appleir *appleir, int key) +{ + /* printk (KERN_ERR "key %d down\n", key); */ + input_report_key(appleir->input_dev, key, 1); + input_sync(appleir->input_dev); +} + +static void battery_flat(struct appleir *appleir) +{ + dev_err(&appleir->input_dev->dev, "possible flat battery?\n"); +} + +static void key_up_tick(unsigned long data) +{ + struct appleir *appleir = (struct appleir *)data; + + if (appleir->current_key) { + key_up(appleir, appleir->current_key); + appleir->current_key = 0; + } +} + +static void new_data(struct appleir *appleir, u8 *data, int len) +{ + static const u8 keydown[] = { 0x25, 0x87, 0xee }; + static const u8 keyrepeat[] = { 0x26, 0x00, 0x00, 0x00, 0x00 }; + static const u8 flatbattery[] = { 0x25, 0x87, 0xe0 }; + +#if 0 + dump_packet(appleir, "received", data, len); +#endif + + if (len != 5) + return; + + if (!memcmp(data, keydown, sizeof(keydown))) { + /*If we already have a key down, take it up before marking */ + /*this one down */ + if (appleir->current_key) + key_up(appleir, appleir->current_key); + appleir->current_key = keymap[(data[4] >> 1) & MAX_KEYS_MASK]; + + key_down(appleir, appleir->current_key); + /*remote doesn't do key up, either pull them up, in the test */ + /*above, or here set a timer which pulls them up after 1/8 s */ + mod_timer(&appleir->key_up_timer, jiffies + HZ / 8); + + return; + } + + if (!memcmp(data, keyrepeat, sizeof(keyrepeat))) { + key_down(appleir, appleir->current_key); + /*remote doesn't do key up, either pull them up, in the test */ + /*above, or here set a timer which pulls them up after 1/8 s */ + mod_timer(&appleir->key_up_timer, jiffies + HZ / 8); + return; + } + + if (!memcmp(data, flatbattery, sizeof(flatbattery))) { + battery_flat(appleir); + /* Fall through */ + } + + dump_packet(appleir, "unknown packet", data, len); +} + +static void appleir_urb(struct urb *urb) +{ + struct appleir *appleir = urb->context; + int status = urb->status; + int retval; + + switch (status) { + case 0: + new_data(appleir, urb->transfer_buffer, urb->actual_length); + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dbg("%s - urb shutting down with status: %d", __func__, + urb->status); + return; + default: + dbg("%s - nonzero urb status received: %d", __func__, + urb->status); + } + + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + err("%s - usb_submit_urb failed with result %d", __func__, + retval); +} + +static int appleir_open(struct input_dev *dev) +{ + struct appleir *appleir = input_get_drvdata(dev); + + if (usb_submit_urb(appleir->urb, GFP_KERNEL)) + return -EIO; + + return 0; +} + +static void appleir_close(struct input_dev *dev) +{ + struct appleir *appleir = input_get_drvdata(dev); + + usb_kill_urb(appleir->urb); + del_timer_sync(&appleir->key_up_timer); +} + +static int appleir_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + struct usb_endpoint_descriptor *endpoint; + struct appleir *appleir = NULL; + struct input_dev *input_dev; + int retval = -ENOMEM; + int i; + + appleir = kzalloc(sizeof(struct appleir), GFP_KERNEL); + if (!appleir) + goto fail; + + appleir->data = usb_buffer_alloc(dev, URB_SIZE, GFP_KERNEL, + &appleir->dma_buf); + if (!appleir->data) + goto fail; + + appleir->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!appleir->urb) + goto fail; + + appleir->usbdev = dev; + + input_dev = input_allocate_device(); + if (!input_dev) + goto fail; + + appleir->input_dev = input_dev; + + usb_make_path(dev, appleir->phys, sizeof(appleir->phys)); + strlcpy(appleir->phys, "/input0", sizeof(appleir->phys)); + + input_dev->name = "Apple Mac mini infrared remote control driver"; + input_dev->phys = appleir->phys; + usb_to_input_id(dev, &input_dev->id); + input_dev->dev.parent = &intf->dev; + input_set_drvdata(input_dev, appleir); + + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + input_dev->ledbit[0] = 0; + + for (i = 0; i < MAX_KEYS; i++) + set_bit(keymap[i], input_dev->keybit); + + clear_bit(0, input_dev->keybit); + + input_dev->open = appleir_open; + input_dev->close = appleir_close; + + endpoint = &intf->cur_altsetting->endpoint[0].desc; + + usb_fill_int_urb(appleir->urb, dev, + usb_rcvintpipe(dev, endpoint->bEndpointAddress), + appleir->data, 8, + appleir_urb, appleir, endpoint->bInterval); + + appleir->urb->transfer_dma = appleir->dma_buf; + appleir->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + usb_set_intfdata(intf, appleir); + + init_timer(&appleir->key_up_timer); + + appleir->key_up_timer.function = key_up_tick; + appleir->key_up_timer.data = (unsigned long)appleir; + + appleir->timer_initted++; + + retval = input_register_device(appleir->input_dev); + if (retval) + goto fail; + + return 0; + +fail: + if (appleir) { + if (appleir->data) + usb_buffer_free(dev, URB_SIZE, appleir->data, + appleir->dma_buf); + + if (appleir->timer_initted) + del_timer_sync(&appleir->key_up_timer); + + if (appleir->input_dev) + input_free_device(appleir->input_dev); + + kfree(appleir); + } + + return retval; +} + +static void appleir_disconnect(struct usb_interface *intf) +{ + struct appleir *appleir = usb_get_intfdata(intf); + + usb_set_intfdata(intf, NULL); + if (appleir) { + input_unregister_device(appleir->input_dev); + if (appleir->timer_initted) + del_timer_sync(&appleir->key_up_timer); + usb_kill_urb(appleir->urb); + usb_free_urb(appleir->urb); + usb_buffer_free(interface_to_usbdev(intf), URB_SIZE, + appleir->data, appleir->dma_buf); + kfree(appleir); + } +} + +static struct usb_driver appleir_driver = { + .name = "appleir", + .probe = appleir_probe, + .disconnect = appleir_disconnect, + .id_table = appleir_ids, +}; + +static int __init appleir_init(void) +{ + int retval; + + retval = usb_register(&appleir_driver); + if (retval) + goto out; + info(DRIVER_VERSION ":" DRIVER_DESC); +out: + return retval; +} + +static void __exit appleir_exit(void) +{ + usb_deregister(&appleir_driver); +} + +module_init(appleir_init); +module_exit(appleir_exit); diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 3b26fbd3e558..7b48b75246bb 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -897,6 +897,15 @@ config USB_STKWEBCAM To compile this driver as a module, choose M here: the module will be called stkwebcam. +config USB_S2255 + tristate "USB Sensoray 2255 video capture device" + depends on VIDEO_V4L2 + select VIDEOBUF_VMALLOC + default n + help + Say Y here if you want support for the Sensoray 2255 USB device. + This driver can be compiled as a module, called s2255drv. + endif # V4L_USB_DRIVERS config SOC_CAMERA diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index dff0d6abe917..b6cabe03456f 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -122,6 +122,7 @@ obj-$(CONFIG_USB_IBMCAM) += usbvideo/ obj-$(CONFIG_USB_KONICAWC) += usbvideo/ obj-$(CONFIG_USB_VICAM) += usbvideo/ obj-$(CONFIG_USB_QUICKCAM_MESSENGER) += usbvideo/ +obj-$(CONFIG_USB_S2255) += s2255drv.o obj-$(CONFIG_VIDEO_IVTV) += ivtv/ obj-$(CONFIG_VIDEO_CX18) += cx18/ diff --git a/drivers/media/video/s2255drv.c b/drivers/media/video/s2255drv.c new file mode 100644 index 000000000000..44ad9962b909 --- /dev/null +++ b/drivers/media/video/s2255drv.c @@ -0,0 +1,2486 @@ +/* + * s2255drv.c - a driver for the Sensoray 2255 USB video capture device + * + * Copyright (C) 2007-2008 by Sensoray Company Inc. + * Dean Anderson + * + * Some video buffer code based on vivi driver: + * + * Sensoray 2255 device supports 4 simultaneous channels. + * The channels are not "crossbar" inputs, they are physically + * attached to separate video decoders. + * + * Because of USB2.0 bandwidth limitations. There is only a + * certain amount of data which may be transferred at one time. + * + * Example maximum bandwidth utilization: + * + * -full size, color mode YUYV or YUV422P: 2 channels at once + * + * -full or half size Grey scale: all 4 channels at once + * + * -half size, color mode YUYV or YUV422P: all 4 channels at once + * + * -full size, color mode YUYV or YUV422P 1/2 frame rate: all 4 channels + * at once. + * (TODO: Incorporate videodev2 frame rate(FR) enumeration, + * which is currently experimental.) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/firmware.h> +#include <linux/kernel.h> +#include <linux/mutex.h> +#include <linux/videodev2.h> +#include <linux/version.h> +#include <media/videobuf-vmalloc.h> +#include <media/v4l2-common.h> +#include <linux/vmalloc.h> +#include <linux/usb.h> + +#define FIRMWARE_FILE_NAME "f2255usb.bin" + + + +/* vendor request in */ +#define S2255_VR_IN 0 +/* vendor request out */ +#define S2255_VR_OUT 1 +/* firmware query */ +#define S2255_VR_FW 0x30 +/* USB endpoint number for configuring the device */ +#define S2255_CONFIG_EP 2 +/* maximum time for DSP to start responding after last FW word loaded(ms) */ +#define S2255_DSP_BOOTTIME 400 +/* maximum time to wait for firmware to load (ms) */ +#define S2255_LOAD_TIMEOUT (5000+S2255_DSP_BOOTTIME) +#define S2255_DEF_BUFS 16 +#define MAX_CHANNELS 4 +#define FRAME_MARKER 0x2255DA4AL +#define MAX_PIPE_USBBLOCK (40*1024) +#define DEFAULT_PIPE_USBBLOCK (16*1024) +#define MAX_CHANNELS 4 +#define MAX_PIPE_BUFFERS 1 +#define SYS_FRAMES 4 +/* maximum size is PAL full size plus room for the marker header(s) */ +#define SYS_FRAMES_MAXSIZE (720*288*2*2 + 4096) +#define DEF_USB_BLOCK (4096) +#define LINE_SZ_4CIFS_NTSC 640 +#define LINE_SZ_2CIFS_NTSC 640 +#define LINE_SZ_1CIFS_NTSC 320 +#define LINE_SZ_4CIFS_PAL 704 +#define LINE_SZ_2CIFS_PAL 704 +#define LINE_SZ_1CIFS_PAL 352 +#define NUM_LINES_4CIFS_NTSC 240 +#define NUM_LINES_2CIFS_NTSC 240 +#define NUM_LINES_1CIFS_NTSC 240 +#define NUM_LINES_4CIFS_PAL 288 +#define NUM_LINES_2CIFS_PAL 288 +#define NUM_LINES_1CIFS_PAL 288 +#define LINE_SZ_DEF 640 +#define NUM_LINES_DEF 240 + + +/* predefined settings */ +#define FORMAT_NTSC 1 +#define FORMAT_PAL 2 + +#define SCALE_4CIFS 1 /* 640x480(NTSC) or 704x576(PAL) */ +#define SCALE_2CIFS 2 /* 640x240(NTSC) or 704x288(PAL) */ +#define SCALE_1CIFS 3 /* 320x240(NTSC) or 352x288(PAL) */ + +#define COLOR_YUVPL 1 /* YUV planar */ +#define COLOR_YUVPK 2 /* YUV packed */ +#define COLOR_Y8 4 /* monochrome */ + +/* frame decimation. Not implemented by V4L yet(experimental in V4L) */ +#define FDEC_1 1 /* capture every frame. default */ +#define FDEC_2 2 /* capture every 2nd frame */ +#define FDEC_3 3 /* capture every 3rd frame */ +#define FDEC_5 5 /* capture every 5th frame */ + +/*------------------------------------------------------- + * Default mode parameters. + *-------------------------------------------------------*/ +#define DEF_SCALE SCALE_4CIFS +#define DEF_COLOR COLOR_YUVPL +#define DEF_FDEC FDEC_1 +#define DEF_BRIGHT 0 +#define DEF_CONTRAST 0x5c +#define DEF_SATURATION 0x80 +#define DEF_HUE 0 + +/* usb config commands */ +#define IN_DATA_TOKEN 0x2255c0de +#define CMD_2255 0xc2255000 +#define CMD_SET_MODE (CMD_2255 | 0x10) +#define CMD_START (CMD_2255 | 0x20) +#define CMD_STOP (CMD_2255 | 0x30) +#define CMD_STATUS (CMD_2255 | 0x40) + +struct s2255_mode { + u32 format; /* input video format (NTSC, PAL) */ + u32 scale; /* output video scale */ + u32 color; /* output video color format */ + u32 fdec; /* frame decimation */ + u32 bright; /* brightness */ + u32 contrast; /* contrast */ + u32 saturation; /* saturation */ + u32 hue; /* hue (NTSC only)*/ + u32 single; /* capture 1 frame at a time (!=0), continuously (==0)*/ + u32 usb_block; /* block size. should be 4096 of DEF_USB_BLOCK */ + u32 restart; /* if DSP requires restart */ +}; + +/* frame structure */ +#define FRAME_STATE_UNUSED 0 +#define FRAME_STATE_FILLING 1 +#define FRAME_STATE_FULL 2 + + +struct s2255_framei { + unsigned long size; + + unsigned long ulState; /* ulState ==0 unused, 1 being filled, 2 full */ + void *lpvbits; /* image data */ + unsigned long cur_size; /* current data copied to it */ +}; + +/* image buffer structure */ +struct s2255_bufferi { + unsigned long dwFrames; /* number of frames in buffer */ + struct s2255_framei frame[SYS_FRAMES]; /* array of FRAME structures */ +}; + +#define DEF_MODEI_NTSC_CONT {FORMAT_NTSC, DEF_SCALE, DEF_COLOR, \ + DEF_FDEC, DEF_BRIGHT, DEF_CONTRAST, DEF_SATURATION, \ + DEF_HUE, 0, DEF_USB_BLOCK, 0 } + +struct s2255_dmaqueue { + struct list_head active; + /* thread for acquisition */ + struct task_struct *kthread; + int frame; + struct s2255_dev *dev; + int channel; +}; + +/* for firmware loading, fw_state */ +#define S2255_FW_NOTLOADED 0 +#define S2255_FW_LOADED_DSPWAIT 1 +#define S2255_FW_SUCCESS 2 +#define S2255_FW_FAILED 3 + +struct s2255_fw { + int fw_loaded; + int fw_size; + struct urb *fw_urb; + atomic_t fw_state; + void *pfw_data; + wait_queue_head_t wait_fw; + struct timer_list dsp_wait; + const struct firmware *fw; +}; + +struct s2255_pipeinfo { + u32 max_transfer_size; + u32 cur_transfer_size; + u8 *transfer_buffer; + u32 transfer_flags;; + u32 state; + u32 prev_state; + u32 urb_size; + void *stream_urb; + void *dev; /* back pointer to s2255_dev struct*/ + u32 err_count; + u32 buf_index; + u32 idx; + u32 priority_set; +}; + +struct s2255_fmt; /*forward declaration */ + +struct s2255_dev { + int frames; + int users[MAX_CHANNELS]; + struct mutex lock; + struct mutex open_lock; + int resources[MAX_CHANNELS]; + struct usb_device *udev; + struct usb_interface *interface; + u8 read_endpoint; + + struct s2255_dmaqueue vidq[MAX_CHANNELS]; + struct video_device *vdev[MAX_CHANNELS]; + struct list_head s2255_devlist; + struct timer_list timer; + struct s2255_fw *fw_data; + int board_num; + int is_open; + struct s2255_pipeinfo pipes[MAX_PIPE_BUFFERS]; + struct s2255_bufferi buffer[MAX_CHANNELS]; + struct s2255_mode mode[MAX_CHANNELS]; + const struct s2255_fmt *cur_fmt[MAX_CHANNELS]; + int cur_frame[MAX_CHANNELS]; + int last_frame[MAX_CHANNELS]; + u32 cc; /* current channel */ + int b_acquire[MAX_CHANNELS]; + unsigned long req_image_size[MAX_CHANNELS]; + int bad_payload[MAX_CHANNELS]; + unsigned long frame_count[MAX_CHANNELS]; + int frame_ready; + struct kref kref; + spinlock_t slock; +}; +#define to_s2255_dev(d) container_of(d, struct s2255_dev, kref) + +struct s2255_fmt { + char *name; + u32 fourcc; + int depth; +}; + +/* buffer for one video frame */ +struct s2255_buffer { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + const struct s2255_fmt *fmt; +}; + +struct s2255_fh { + struct s2255_dev *dev; + unsigned int resources; + const struct s2255_fmt *fmt; + unsigned int width; + unsigned int height; + struct videobuf_queue vb_vidq; + enum v4l2_buf_type type; + int channel; + /* mode below is the desired mode. + mode in s2255_dev is the current mode that was last set */ + struct s2255_mode mode; +}; + +#define S2255_MAX_USERS 1 + +#define CUR_USB_FWVER 774 /* current cypress EEPROM firmware version */ +#define S2255_MAJOR_VERSION 1 +#define S2255_MINOR_VERSION 13 +#define S2255_RELEASE 0 +#define S2255_VERSION KERNEL_VERSION(S2255_MAJOR_VERSION, \ + S2255_MINOR_VERSION, \ + S2255_RELEASE) + +/* vendor ids */ +#define USB_S2255_VENDOR_ID 0x1943 +#define USB_S2255_PRODUCT_ID 0x2255 +#define S2255_NORMS (V4L2_STD_PAL | V4L2_STD_NTSC) +/* frame prefix size (sent once every frame) */ +#define PREFIX_SIZE 512 + +/* Channels on box are in reverse order */ +static unsigned long G_chnmap[MAX_CHANNELS] = { 3, 2, 1, 0 }; + +static LIST_HEAD(s2255_devlist); + +static int debug; +static int *s2255_debug = &debug; + +static int s2255_start_readpipe(struct s2255_dev *dev); +static void s2255_stop_readpipe(struct s2255_dev *dev); +static int s2255_start_acquire(struct s2255_dev *dev, unsigned long chn); +static int s2255_stop_acquire(struct s2255_dev *dev, unsigned long chn); +static void s2255_fillbuff(struct s2255_dev *dev, struct s2255_buffer *buf, + int chn); +static int s2255_set_mode(struct s2255_dev *dev, unsigned long chn, + struct s2255_mode *mode); +static int s2255_board_shutdown(struct s2255_dev *dev); +static void s2255_exit_v4l(struct s2255_dev *dev); +static void s2255_fwload_start(struct s2255_dev *dev); + +#define dprintk(level, fmt, arg...) \ + do { \ + if (*s2255_debug >= (level)) { \ + printk(KERN_DEBUG "s2255: " fmt, ##arg); \ + } \ + } while (0) + + +static struct usb_driver s2255_driver; + + +/* Declare static vars that will be used as parameters */ +static unsigned int vid_limit = 16; /* Video memory limit, in Mb */ + +/* start video number */ +static int video_nr = -1; /* /dev/videoN, -1 for autodetect */ + +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug level(0-100) default 0"); +module_param(vid_limit, int, 0); +MODULE_PARM_DESC(vid_limit, "video memory limit(Mb)"); +module_param(video_nr, int, 0); +MODULE_PARM_DESC(video_nr, "start video minor(-1 default autodetect)"); + +/* USB device table */ +static struct usb_device_id s2255_table[] = { + {USB_DEVICE(USB_S2255_VENDOR_ID, USB_S2255_PRODUCT_ID)}, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, s2255_table); + + +#define BUFFER_TIMEOUT msecs_to_jiffies(400) + +/* supported controls */ +static struct v4l2_queryctrl s2255_qctrl[] = { + { + .id = V4L2_CID_BRIGHTNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Brightness", + .minimum = -127, + .maximum = 128, + .step = 1, + .default_value = 0, + .flags = 0, + }, { + .id = V4L2_CID_CONTRAST, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Contrast", + .minimum = 0, + .maximum = 255, + .step = 0x1, + .default_value = DEF_CONTRAST, + .flags = 0, + }, { + .id = V4L2_CID_SATURATION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Saturation", + .minimum = 0, + .maximum = 255, + .step = 0x1, + .default_value = DEF_SATURATION, + .flags = 0, + }, { + .id = V4L2_CID_HUE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Hue", + .minimum = 0, + .maximum = 255, + .step = 0x1, + .default_value = DEF_HUE, + .flags = 0, + } +}; + +static int qctl_regs[ARRAY_SIZE(s2255_qctrl)]; + +/* image formats. */ +static const struct s2255_fmt formats[] = { + { + .name = "4:2:2, planar, YUV422P", + .fourcc = V4L2_PIX_FMT_YUV422P, + .depth = 16 + + }, { + .name = "4:2:2, packed, YUYV", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16 + + }, { + .name = "4:2:2, packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16 + }, { + .name = "8bpp GREY", + .fourcc = V4L2_PIX_FMT_GREY, + .depth = 8 + } +}; + +static int norm_maxw(struct video_device *vdev) +{ + return (vdev->current_norm & V4L2_STD_NTSC) ? + LINE_SZ_4CIFS_NTSC : LINE_SZ_4CIFS_PAL; +} + +static int norm_maxh(struct video_device *vdev) +{ + return (vdev->current_norm & V4L2_STD_NTSC) ? + (NUM_LINES_1CIFS_NTSC * 2) : (NUM_LINES_1CIFS_PAL * 2); +} + +static int norm_minw(struct video_device *vdev) +{ + return (vdev->current_norm & V4L2_STD_NTSC) ? + LINE_SZ_1CIFS_NTSC : LINE_SZ_1CIFS_PAL; +} + +static int norm_minh(struct video_device *vdev) +{ + return (vdev->current_norm & V4L2_STD_NTSC) ? + (NUM_LINES_1CIFS_NTSC) : (NUM_LINES_1CIFS_PAL); +} + + +/* converts 2255 planar format to yuyv or uyvy */ +static void planar422p_to_yuv_packed(const unsigned char *in, + unsigned char *out, + int width, int height, + int fmt) +{ + unsigned char *pY; + unsigned char *pCb; + unsigned char *pCr; + unsigned long size = height * width; + unsigned int i; + pY = (unsigned char *)in; + pCr = (unsigned char *)in + height * width; + pCb = (unsigned char *)in + height * width + (height * width / 2); + for (i = 0; i < size * 2; i += 4) { + out[i] = (fmt == V4L2_PIX_FMT_YUYV) ? *pY++ : *pCr++; + out[i + 1] = (fmt == V4L2_PIX_FMT_YUYV) ? *pCr++ : *pY++; + out[i + 2] = (fmt == V4L2_PIX_FMT_YUYV) ? *pY++ : *pCb++; + out[i + 3] = (fmt == V4L2_PIX_FMT_YUYV) ? *pCb++ : *pY++; + } + return; +} + + +/* kickstarts the firmware loading. from probe + */ +static void s2255_timer(unsigned long user_data) +{ + struct s2255_fw *data = (struct s2255_fw *)user_data; + dprintk(100, "s2255 timer\n"); + if (usb_submit_urb(data->fw_urb, GFP_ATOMIC) < 0) { + printk(KERN_ERR "s2255: can't submit urb\n"); + if (data->fw) { + release_firmware(data->fw); + data->fw = NULL; + } + return; + } +} + +/* called when DSP is up and running. DSP is guaranteed to + be running after S2255_DSP_BOOTTIME */ +static void s2255_dsp_running(unsigned long user_data) +{ + struct s2255_fw *data = (struct s2255_fw *)user_data; + dprintk(1, "dsp running\n"); + atomic_set(&data->fw_state, S2255_FW_SUCCESS); + wake_up(&data->wait_fw); + printk(KERN_INFO "s2255: firmware loaded successfully\n"); + return; +} + + +/* this loads the firmware asynchronously. + Originally this was done synchroously in probe. + But it is better to load it asynchronously here than block + inside the probe function. Blocking inside probe affects boot time. + FW loading is triggered by the timer in the probe function +*/ +static void s2255_fwchunk_complete(struct urb *urb) +{ + struct s2255_fw *data = urb->context; + struct usb_device *udev = urb->dev; + int len; + dprintk(100, "udev %p urb %p", udev, urb); + + if (urb->status) { + dev_err(&udev->dev, "URB failed with status %d", urb->status); + return; + } + if (data->fw_urb == NULL) { + dev_err(&udev->dev, "early disconncect\n"); + return; + } +#define CHUNK_SIZE 512 + /* all USB transfers must be done with continuous kernel memory. + can't allocate more than 128k in current linux kernel, so + upload the firmware in chunks + */ + if (data->fw_loaded < data->fw_size) { + len = (data->fw_loaded + CHUNK_SIZE) > data->fw_size ? + data->fw_size % CHUNK_SIZE : CHUNK_SIZE; + + if (len < CHUNK_SIZE) + memset(data->pfw_data, 0, CHUNK_SIZE); + + dprintk(100, "completed len %d, loaded %d \n", len, + data->fw_loaded); + + memcpy(data->pfw_data, + (char *) data->fw->data + data->fw_loaded, len); + + usb_fill_bulk_urb(data->fw_urb, udev, usb_sndbulkpipe(udev, 2), + data->pfw_data, CHUNK_SIZE, + s2255_fwchunk_complete, data); + if (usb_submit_urb(data->fw_urb, GFP_ATOMIC) < 0) { + dev_err(&udev->dev, "failed submit URB\n"); + atomic_set(&data->fw_state, S2255_FW_FAILED); + /* wake up anything waiting for the firmware */ + wake_up(&data->wait_fw); + return; + } + data->fw_loaded += len; + } else { + init_timer(&data->dsp_wait); + data->dsp_wait.function = s2255_dsp_running; + data->dsp_wait.data = (unsigned long)data; + atomic_set(&data->fw_state, S2255_FW_LOADED_DSPWAIT); + mod_timer(&data->dsp_wait, msecs_to_jiffies(S2255_DSP_BOOTTIME) + + jiffies); + } + dprintk(100, "2255 complete done\n"); + return; + +} + +static int s2255_got_frame(struct s2255_dev *dev, int chn) +{ + struct s2255_dmaqueue *dma_q = &dev->vidq[chn]; + struct s2255_buffer *buf; + unsigned long flags = 0; + int rc = 0; + dprintk(2, "wakeup: %p channel: %d\n", &dma_q, chn); + spin_lock_irqsave(&dev->slock, flags); + + if (list_empty(&dma_q->active)) { + dprintk(1, "No active queue to serve\n"); + rc = -1; + goto unlock; + } + buf = list_entry(dma_q->active.next, + struct s2255_buffer, vb.queue); + + if (!waitqueue_active(&buf->vb.done)) { + /* no one active */ + rc = -1; + goto unlock; + } + list_del(&buf->vb.queue); + do_gettimeofday(&buf->vb.ts); + dprintk(100, "[%p/%d] wakeup\n", buf, buf->vb.i); + + s2255_fillbuff(dev, buf, dma_q->channel); + wake_up(&buf->vb.done); + dprintk(2, "wakeup [buf/i] [%p/%d]\n", buf, buf->vb.i); +unlock: + spin_unlock_irqrestore(&dev->slock, flags); + return 0; +} + + +static const struct s2255_fmt *format_by_fourcc(int fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (-1 == formats[i].fourcc) + continue; + if (formats[i].fourcc == fourcc) + return formats + i; + } + return NULL; +} + + + + +/* video buffer vmalloc implementation based partly on VIVI driver which is + * Copyright (c) 2006 by + * Mauro Carvalho Chehab <mchehab--a.t--infradead.org> + * Ted Walther <ted--a.t--enumera.com> + * John Sokol <sokol--a.t--videotechnology.com> + * http://v4l.videotechnology.com/ + * + */ +static void s2255_fillbuff(struct s2255_dev *dev, struct s2255_buffer *buf, + int chn) +{ + int pos = 0; + struct timeval ts; + const char *tmpbuf; + char *vbuf = videobuf_to_vmalloc(&buf->vb); + unsigned long last_frame; + struct s2255_framei *frm; + + if (!vbuf) + return; + + last_frame = dev->last_frame[chn]; + if (last_frame != -1) { + frm = &dev->buffer[chn].frame[last_frame]; + tmpbuf = + (const char *)dev->buffer[chn].frame[last_frame].lpvbits; + switch (buf->fmt->fourcc) { + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_UYVY: + planar422p_to_yuv_packed((const unsigned char *)tmpbuf, + vbuf, buf->vb.width, + buf->vb.height, + buf->fmt->fourcc); + break; + case V4L2_PIX_FMT_GREY: + memcpy(vbuf, tmpbuf, buf->vb.width * buf->vb.height); + break; + case V4L2_PIX_FMT_YUV422P: + memcpy(vbuf, tmpbuf, + buf->vb.width * buf->vb.height * 2); + break; + default: + printk(KERN_DEBUG "s2255: unknown format?\n"); + } + dev->last_frame[chn] = -1; + /* done with the frame, free it */ + frm->ulState = 0; + dprintk(4, "freeing buffer\n"); + } else { + printk(KERN_ERR "s2255: =======no frame\n"); + return; + + } + dprintk(2, "s2255fill at : Buffer 0x%08lx size= %d\n", + (unsigned long)vbuf, pos); + /* tell v4l buffer was filled */ + + buf->vb.field_count++; + do_gettimeofday(&ts); + buf->vb.ts = ts; + buf->vb.state = VIDEOBUF_DONE; +} + + +/* ------------------------------------------------------------------ + Videobuf operations + ------------------------------------------------------------------*/ + +static int buffer_setup(struct videobuf_queue *vq, unsigned int *count, + unsigned int *size) +{ + struct s2255_fh *fh = vq->priv_data; + + *size = fh->width * fh->height * (fh->fmt->depth >> 3); + + if (0 == *count) + *count = S2255_DEF_BUFS; + + while (*size * *count > vid_limit * 1024 * 1024) + (*count)--; + + return 0; +} + +static void free_buffer(struct videobuf_queue *vq, struct s2255_buffer *buf) +{ + dprintk(4, "%s\n", __func__); + + videobuf_waiton(&buf->vb, 0, 0); + videobuf_vmalloc_free(&buf->vb); + buf->vb.state = VIDEOBUF_NEEDS_INIT; +} + +static int buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct s2255_fh *fh = vq->priv_data; + struct s2255_buffer *buf = container_of(vb, struct s2255_buffer, vb); + int rc; + dprintk(4, "%s, field=%d\n", __func__, field); + if (fh->fmt == NULL) + return -EINVAL; + + if ((fh->width < norm_minw(fh->dev->vdev[fh->channel])) || + (fh->width > norm_maxw(fh->dev->vdev[fh->channel])) || + (fh->height < norm_minh(fh->dev->vdev[fh->channel])) || + (fh->height > norm_maxh(fh->dev->vdev[fh->channel]))) { + dprintk(4, "invalid buffer prepare\n"); + return -EINVAL; + } + + buf->vb.size = fh->width * fh->height * (fh->fmt->depth >> 3); + + if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size) { + dprintk(4, "invalid buffer prepare\n"); + return -EINVAL; + } + + buf->fmt = fh->fmt; + buf->vb.width = fh->width; + buf->vb.height = fh->height; + buf->vb.field = field; + + + if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { + rc = videobuf_iolock(vq, &buf->vb, NULL); + if (rc < 0) + goto fail; + } + + buf->vb.state = VIDEOBUF_PREPARED; + return 0; +fail: + free_buffer(vq, buf); + return rc; +} + +static void buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) +{ + struct s2255_buffer *buf = container_of(vb, struct s2255_buffer, vb); + struct s2255_fh *fh = vq->priv_data; + struct s2255_dev *dev = fh->dev; + struct s2255_dmaqueue *vidq = &dev->vidq[fh->channel]; + + dprintk(1, "%s\n", __func__); + + buf->vb.state = VIDEOBUF_QUEUED; + list_add_tail(&buf->vb.queue, &vidq->active); +} + +static void buffer_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct s2255_buffer *buf = container_of(vb, struct s2255_buffer, vb); + struct s2255_fh *fh = vq->priv_data; + dprintk(4, "%s %d\n", __func__, fh->channel); + free_buffer(vq, buf); +} + +static struct videobuf_queue_ops s2255_video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + + +static int res_get(struct s2255_dev *dev, struct s2255_fh *fh) +{ + /* is it free? */ + mutex_lock(&dev->lock); + if (dev->resources[fh->channel]) { + /* no, someone else uses it */ + mutex_unlock(&dev->lock); + return 0; + } + /* it's free, grab it */ + dev->resources[fh->channel] = 1; + dprintk(1, "res: get\n"); + mutex_unlock(&dev->lock); + return 1; +} + +static int res_locked(struct s2255_dev *dev, struct s2255_fh *fh) +{ + return (dev->resources[fh->channel]); +} + +static void res_free(struct s2255_dev *dev, struct s2255_fh *fh) +{ + dev->resources[fh->channel] = 0; + dprintk(1, "res: put\n"); +} + + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct s2255_fh *fh = file->private_data; + struct s2255_dev *dev = fh->dev; + strlcpy(cap->driver, "s2255", sizeof(cap->driver)); + strlcpy(cap->card, "s2255", sizeof(cap->card)); + strlcpy(cap->bus_info, dev->udev->dev.bus_id, sizeof(cap->bus_info)); + cap->version = S2255_VERSION; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + return 0; +} + +static int vidioc_enum_fmt_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + int index = 0; + if (f) + index = f->index; + + if (index >= ARRAY_SIZE(formats)) + return -EINVAL; + + dprintk(4, "name %s\n", formats[index].name); + strlcpy(f->description, formats[index].name, sizeof(f->description)); + f->pixelformat = formats[index].fourcc; + return 0; +} + +static int vidioc_g_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct s2255_fh *fh = priv; + + f->fmt.pix.width = fh->width; + f->fmt.pix.height = fh->height; + f->fmt.pix.field = fh->vb_vidq.field; + f->fmt.pix.pixelformat = fh->fmt->fourcc; + f->fmt.pix.bytesperline = f->fmt.pix.width * (fh->fmt->depth >> 3); + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + + return (0); +} + +static int vidioc_try_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + const struct s2255_fmt *fmt; + enum v4l2_field field; + int b_any_field = 0; + struct s2255_fh *fh = priv; + struct s2255_dev *dev = fh->dev; + int is_ntsc; + + is_ntsc = + (dev->vdev[fh->channel]->current_norm & V4L2_STD_NTSC) ? 1 : 0; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + + if (fmt == NULL) + return -EINVAL; + + field = f->fmt.pix.field; + if (field == V4L2_FIELD_ANY) + b_any_field = 1; + + dprintk(4, "try format %d \n", is_ntsc); + /* supports 3 sizes. see s2255drv.h */ + dprintk(50, "width test %d, height %d\n", + f->fmt.pix.width, f->fmt.pix.height); + if (is_ntsc) { + /* NTSC */ + if (f->fmt.pix.height >= NUM_LINES_1CIFS_NTSC * 2) { + f->fmt.pix.height = NUM_LINES_1CIFS_NTSC * 2; + if (b_any_field) { + field = V4L2_FIELD_SEQ_TB; + } else if (!((field == V4L2_FIELD_INTERLACED) || + (field == V4L2_FIELD_SEQ_TB) || + (field == V4L2_FIELD_INTERLACED_TB))) { + dprintk(1, "unsupported field setting\n"); + return -EINVAL; + } + } else { + f->fmt.pix.height = NUM_LINES_1CIFS_NTSC; + if (b_any_field) { + field = V4L2_FIELD_TOP; + } else if (!((field == V4L2_FIELD_TOP) || + (field == V4L2_FIELD_BOTTOM))) { + dprintk(1, "unsupported field setting\n"); + return -EINVAL; + } + + } + if (f->fmt.pix.width >= LINE_SZ_4CIFS_NTSC) + f->fmt.pix.width = LINE_SZ_4CIFS_NTSC; + else if (f->fmt.pix.width >= LINE_SZ_2CIFS_NTSC) + f->fmt.pix.width = LINE_SZ_2CIFS_NTSC; + else if (f->fmt.pix.width >= LINE_SZ_1CIFS_NTSC) + f->fmt.pix.width = LINE_SZ_1CIFS_NTSC; + else + f->fmt.pix.width = LINE_SZ_1CIFS_NTSC; + } else { + /* PAL */ + if (f->fmt.pix.height >= NUM_LINES_1CIFS_PAL * 2) { + f->fmt.pix.height = NUM_LINES_1CIFS_PAL * 2; + if (b_any_field) { + field = V4L2_FIELD_SEQ_TB; + } else if (!((field == V4L2_FIELD_INTERLACED) || + (field == V4L2_FIELD_SEQ_TB) || + (field == V4L2_FIELD_INTERLACED_TB))) { + dprintk(1, "unsupported field setting\n"); + return -EINVAL; + } + } else { + f->fmt.pix.height = NUM_LINES_1CIFS_PAL; + if (b_any_field) { + field = V4L2_FIELD_TOP; + } else if (!((field == V4L2_FIELD_TOP) || + (field == V4L2_FIELD_BOTTOM))) { + dprintk(1, "unsupported field setting\n"); + return -EINVAL; + } + } + if (f->fmt.pix.width >= LINE_SZ_4CIFS_PAL) { + dprintk(50, "pal 704\n"); + f->fmt.pix.width = LINE_SZ_4CIFS_PAL; + field = V4L2_FIELD_SEQ_TB; + } else if (f->fmt.pix.width >= LINE_SZ_2CIFS_PAL) { + dprintk(50, "pal 352A\n"); + f->fmt.pix.width = LINE_SZ_2CIFS_PAL; + field = V4L2_FIELD_TOP; + } else if (f->fmt.pix.width >= LINE_SZ_1CIFS_PAL) { + dprintk(50, "pal 352B\n"); + f->fmt.pix.width = LINE_SZ_1CIFS_PAL; + field = V4L2_FIELD_TOP; + } else { + dprintk(50, "pal 352C\n"); + f->fmt.pix.width = LINE_SZ_1CIFS_PAL; + field = V4L2_FIELD_TOP; + } + } + + dprintk(50, "width %d height %d field %d \n", f->fmt.pix.width, + f->fmt.pix.height, f->fmt.pix.field); + f->fmt.pix.field = field; + f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + return 0; +} + +static int vidioc_s_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct s2255_fh *fh = priv; + const struct s2255_fmt *fmt; + struct videobuf_queue *q = &fh->vb_vidq; + int ret; + int norm; + + ret = vidioc_try_fmt_cap(file, fh, f); + + if (ret < 0) + return (ret); + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + + if (fmt == NULL) + return -EINVAL; + + mutex_lock(&q->vb_lock); + + if (videobuf_queue_is_busy(&fh->vb_vidq)) { + dprintk(1, "queue busy\n"); + ret = -EBUSY; + goto out_s_fmt; + } + + if (res_locked(fh->dev, fh)) { + dprintk(1, "can't change format after started\n"); + ret = -EBUSY; + goto out_s_fmt; + } + + fh->fmt = fmt; + fh->width = f->fmt.pix.width; + fh->height = f->fmt.pix.height; + fh->vb_vidq.field = f->fmt.pix.field; + fh->type = f->type; + norm = norm_minw(fh->dev->vdev[fh->channel]); + if (fh->width > norm_minw(fh->dev->vdev[fh->channel])) { + if (fh->height > norm_minh(fh->dev->vdev[fh->channel])) + fh->mode.scale = SCALE_4CIFS; + else + fh->mode.scale = SCALE_2CIFS; + + } else { + fh->mode.scale = SCALE_1CIFS; + } + + /* color mode */ + switch (fh->fmt->fourcc) { + case V4L2_PIX_FMT_GREY: + fh->mode.color = COLOR_Y8; + break; + case V4L2_PIX_FMT_YUV422P: + fh->mode.color = COLOR_YUVPL; + break; + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_UYVY: + default: + fh->mode.color = COLOR_YUVPK; + break; + } + ret = 0; +out_s_fmt: + mutex_unlock(&q->vb_lock); + return ret; +} + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p) +{ + int rc; + struct s2255_fh *fh = priv; + rc = videobuf_reqbufs(&fh->vb_vidq, p); + return rc; +} + +static int vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + int rc; + struct s2255_fh *fh = priv; + rc = videobuf_querybuf(&fh->vb_vidq, p); + return rc; +} + +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + int rc; + struct s2255_fh *fh = priv; + rc = videobuf_qbuf(&fh->vb_vidq, p); + return rc; +} + +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p) +{ + int rc; + struct s2255_fh *fh = priv; + rc = videobuf_dqbuf(&fh->vb_vidq, p, file->f_flags & O_NONBLOCK); + return rc; +} + +#ifdef CONFIG_VIDEO_V4L1_COMPAT +static int vidioc_cgmbuf(struct file *file, void *priv, struct video_mbuf *mbuf) +{ + struct s2255_fh *fh = priv; + + return videobuf_cgmbuf(&fh->vb_vidq, mbuf, 8); +} +#endif + +/* write to the configuration pipe, synchronously */ +static int s2255_write_config(struct usb_device *udev, unsigned char *pbuf, + int size) +{ + int pipe; + int done; + long retval = -1; + if (udev) { + pipe = usb_sndbulkpipe(udev, S2255_CONFIG_EP); + retval = usb_bulk_msg(udev, pipe, pbuf, size, &done, 500); + } + return retval; +} + +static u32 get_transfer_size(struct s2255_mode *mode) +{ + int linesPerFrame = LINE_SZ_DEF; + int pixelsPerLine = NUM_LINES_DEF; + u32 outImageSize; + u32 usbInSize; + unsigned int mask_mult; + + if (mode == NULL) + return 0; + + if (mode->format == FORMAT_NTSC) { + switch (mode->scale) { + case SCALE_4CIFS: + linesPerFrame = NUM_LINES_4CIFS_NTSC * 2; + pixelsPerLine = LINE_SZ_4CIFS_NTSC; + break; + case SCALE_2CIFS: + linesPerFrame = NUM_LINES_2CIFS_NTSC; + pixelsPerLine = LINE_SZ_2CIFS_NTSC; + break; + case SCALE_1CIFS: + linesPerFrame = NUM_LINES_1CIFS_NTSC; + pixelsPerLine = LINE_SZ_1CIFS_NTSC; + break; + default: + break; + } + } else if (mode->format == FORMAT_PAL) { + switch (mode->scale) { + case SCALE_4CIFS: + linesPerFrame = NUM_LINES_4CIFS_PAL * 2; + pixelsPerLine = LINE_SZ_4CIFS_PAL; + break; + case SCALE_2CIFS: + linesPerFrame = NUM_LINES_2CIFS_PAL; + pixelsPerLine = LINE_SZ_2CIFS_PAL; + break; + case SCALE_1CIFS: + linesPerFrame = NUM_LINES_1CIFS_PAL; + pixelsPerLine = LINE_SZ_1CIFS_PAL; + break; + default: + break; + } + } + outImageSize = linesPerFrame * pixelsPerLine; + if (mode->color != COLOR_Y8) { + /* 2 bytes/pixel if not monochrome */ + outImageSize *= 2; + } + + /* total bytes to send including prefix and 4K padding; + must be a multiple of USB_READ_SIZE */ + usbInSize = outImageSize + PREFIX_SIZE; /* always send prefix */ + mask_mult = 0xffffffffUL - DEF_USB_BLOCK + 1; + /* if size not a multiple of USB_READ_SIZE */ + if (usbInSize & ~mask_mult) + usbInSize = (usbInSize & mask_mult) + (DEF_USB_BLOCK); + return usbInSize; +} + +static void dump_verify_mode(struct s2255_dev *sdev, struct s2255_mode *mode) +{ + struct device *dev = &sdev->udev->dev; + dev_info(dev, "------------------------------------------------\n"); + dev_info(dev, "verify mode\n"); + dev_info(dev, "format: %d\n", mode->format); + dev_info(dev, "scale: %d\n", mode->scale); + dev_info(dev, "fdec: %d\n", mode->fdec); + dev_info(dev, "color: %d\n", mode->color); + dev_info(dev, "bright: 0x%x\n", mode->bright); + dev_info(dev, "restart: 0x%x\n", mode->restart); + dev_info(dev, "usb_block: 0x%x\n", mode->usb_block); + dev_info(dev, "single: 0x%x\n", mode->single); + dev_info(dev, "------------------------------------------------\n"); +} + +/* + * set mode is the function which controls the DSP. + * the restart parameter in struct s2255_mode should be set whenever + * the image size could change via color format, video system or image + * size. + * When the restart parameter is set, we sleep for ONE frame to allow the + * DSP time to get the new frame + */ +static int s2255_set_mode(struct s2255_dev *dev, unsigned long chn, + struct s2255_mode *mode) +{ + int res; + u32 *buffer; + unsigned long chn_rev; + + chn_rev = G_chnmap[chn]; + dprintk(3, "mode scale [%ld] %p %d\n", chn, mode, mode->scale); + dprintk(3, "mode scale [%ld] %p %d\n", chn, &dev->mode[chn], + dev->mode[chn].scale); + dprintk(2, "mode contrast %x\n", mode->contrast); + + /* save the mode */ + dev->mode[chn] = *mode; + dev->req_image_size[chn] = get_transfer_size(mode); + dprintk(1, "transfer size %ld\n", dev->req_image_size[chn]); + + buffer = kzalloc(512, GFP_KERNEL); + if (buffer == NULL) { + dev_err(&dev->udev->dev, "out of mem\n"); + return -ENOMEM; + } + + /* set the mode */ + buffer[0] = IN_DATA_TOKEN; + buffer[1] = (u32) chn_rev; + buffer[2] = CMD_SET_MODE; + memcpy(&buffer[3], &dev->mode[chn], sizeof(struct s2255_mode)); + res = s2255_write_config(dev->udev, (unsigned char *)buffer, 512); + if (debug) + dump_verify_mode(dev, mode); + kfree(buffer); + dprintk(1, "set mode done chn %lu, %d\n", chn, res); + + /* wait at least 3 frames before continuing */ + if (mode->restart) + msleep(125); + + /* clear the restart flag */ + dev->mode[chn].restart = 0; + + return res; +} + +static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i) +{ + int res; + struct s2255_fh *fh = priv; + struct s2255_dev *dev = fh->dev; + struct s2255_mode *new_mode; + struct s2255_mode *old_mode; + int chn; + int j; + dprintk(4, "%s\n", __func__); + if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + dev_err(&dev->udev->dev, "invalid fh type0\n"); + return -EINVAL; + } + if (i != fh->type) { + dev_err(&dev->udev->dev, "invalid fh type1\n"); + return -EINVAL; + } + + if (!res_get(dev, fh)) { + dev_err(&dev->udev->dev, "res get busy\n"); + return -EBUSY; + } + + /* send a set mode command everytime with restart. + in case we switch resolutions or other parameters */ + chn = fh->channel; + new_mode = &fh->mode; + old_mode = &fh->dev->mode[chn]; + + if (new_mode->color != old_mode->color) + new_mode->restart = 1; + else if (new_mode->scale != old_mode->scale) + new_mode->restart = 1; + else if (new_mode->format != old_mode->format) + new_mode->restart = 1; + + s2255_set_mode(dev, chn, new_mode); + new_mode->restart = 0; + *old_mode = *new_mode; + dev->cur_fmt[chn] = fh->fmt; + dprintk(1, "%s[%d]\n", __func__, chn); + dev->last_frame[chn] = -1; + dev->bad_payload[chn] = 0; + dev->cur_frame[chn] = 0; + for (j = 0; j < SYS_FRAMES; j++) { + dev->buffer[chn].frame[j].ulState = 0; + dev->buffer[chn].frame[j].cur_size = 0; + } + res = videobuf_streamon(&fh->vb_vidq); + if (res == 0) { + s2255_start_acquire(dev, chn); + dev->b_acquire[chn] = 1; + } else { + res_free(dev, fh); + } + return res; +} + +static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + int res; + struct s2255_fh *fh = priv; + struct s2255_dev *dev = fh->dev; + + dprintk(4, "%s\n, channel: %d", __func__, fh->channel); + if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + printk(KERN_ERR "invalid fh type0\n"); + return -EINVAL; + } + if (i != fh->type) { + printk(KERN_ERR "invalid type i\n"); + return -EINVAL; + } + s2255_stop_acquire(dev, fh->channel); + res = videobuf_streamoff(&fh->vb_vidq); + res_free(dev, fh); + return res; +} + +static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id *i) +{ + struct s2255_fh *fh = priv; + struct s2255_mode *mode; + struct videobuf_queue *q = &fh->vb_vidq; + int ret = 0; + + mutex_lock(&q->vb_lock); + if (videobuf_queue_is_busy(q)) { + dprintk(1, "queue busy\n"); + ret = -EBUSY; + goto out_s_std; + } + + if (res_locked(fh->dev, fh)) { + dprintk(1, "can't change standard after started\n"); + ret = -EBUSY; + goto out_s_std; + } + mode = &fh->mode; + + if (*i & V4L2_STD_NTSC) { + dprintk(4, "vidioc_s_std NTSC\n"); + mode->format = FORMAT_NTSC; + } else if (*i & V4L2_STD_PAL) { + dprintk(4, "vidioc_s_std PAL\n"); + mode->format = FORMAT_PAL; + } else { + ret = -EINVAL; + } +out_s_std: + mutex_unlock(&q->vb_lock); + return ret; +} + +/* Sensoray 2255 is a multiple channel capture device. + It does not have a "crossbar" of inputs. + We use one V4L device per channel. The user must + be aware that certain combinations are not allowed. + For instance, you cannot do full FPS on more than 2 channels(2 videodevs) + at once in color(you can do full fps on 4 channels with greyscale. +*/ +static int vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + if (inp->index != 0) + return -EINVAL; + + inp->type = V4L2_INPUT_TYPE_CAMERA; + inp->std = S2255_NORMS; + strlcpy(inp->name, "Camera", sizeof(inp->name)); + return (0); +} + +static int vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} +static int vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + if (i > 0) + return -EINVAL; + return 0; +} + +/* --- controls ---------------------------------------------- */ +static int vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(s2255_qctrl); i++) + if (qc->id && qc->id == s2255_qctrl[i].id) { + memcpy(qc, &(s2255_qctrl[i]), sizeof(*qc)); + return (0); + } + + dprintk(4, "query_ctrl -EINVAL %d\n", qc->id); + return -EINVAL; +} + +static int vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(s2255_qctrl); i++) + if (ctrl->id == s2255_qctrl[i].id) { + ctrl->value = qctl_regs[i]; + return (0); + } + dprintk(4, "g_ctrl -EINVAL\n"); + + return -EINVAL; +} + +static int vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + int i; + struct s2255_fh *fh = priv; + struct s2255_dev *dev = fh->dev; + struct s2255_mode *mode; + mode = &fh->mode; + dprintk(4, "vidioc_s_ctrl\n"); + for (i = 0; i < ARRAY_SIZE(s2255_qctrl); i++) { + if (ctrl->id == s2255_qctrl[i].id) { + if (ctrl->value < s2255_qctrl[i].minimum || + ctrl->value > s2255_qctrl[i].maximum) + return (-ERANGE); + + qctl_regs[i] = ctrl->value; + /* update the mode to the corresponding value */ + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + mode->bright = ctrl->value; + break; + case V4L2_CID_CONTRAST: + mode->contrast = ctrl->value; + break; + case V4L2_CID_HUE: + mode->hue = ctrl->value; + break; + case V4L2_CID_SATURATION: + mode->saturation = ctrl->value; + break; + } + mode->restart = 0; + /* set mode here. Note: stream does not need restarted. + some V4L programs restart stream unnecessarily + after a s_crtl. + */ + s2255_set_mode(dev, fh->channel, mode); + return 0; + } + } + return -EINVAL; +} + +static int s2255_open(struct inode *inode, struct file *file) +{ + int minor = iminor(inode); + struct s2255_dev *h, *dev = NULL; + struct s2255_fh *fh; + struct list_head *list; + enum v4l2_buf_type type = 0; + int i = 0; + int cur_channel = -1; + dprintk(1, "s2255: open called (minor=%d)\n", minor); + + list_for_each(list, &s2255_devlist) { + h = list_entry(list, struct s2255_dev, s2255_devlist); + for (i = 0; i < MAX_CHANNELS; i++) { + if (h->vdev[i]->minor == minor) { + cur_channel = i; + dev = h; + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + } + } + } + + if ((NULL == dev) || (cur_channel == -1)) { + dprintk(1, "s2255: openv4l no dev\n"); + return -ENODEV; + } + + mutex_lock(&dev->open_lock); + + dev->users[cur_channel]++; + if (dev->users[cur_channel] > S2255_MAX_USERS) { + dev->users[cur_channel]--; + mutex_unlock(&dev->open_lock); + printk(KERN_INFO "s2255drv: too many open handles!\n"); + return -EBUSY; + } + + if (atomic_read(&dev->fw_data->fw_state) == S2255_FW_FAILED) { + err("2255 firmware load failed. retrying.\n"); + s2255_fwload_start(dev); + wait_event_timeout(dev->fw_data->wait_fw, + (atomic_read(&dev->fw_data->fw_state) + != S2255_FW_NOTLOADED), + msecs_to_jiffies(S2255_LOAD_TIMEOUT)); + if (atomic_read(&dev->fw_data->fw_state) + != S2255_FW_SUCCESS) { + printk(KERN_INFO "2255 FW load failed after 2 tries\n"); + mutex_unlock(&dev->open_lock); + return -EFAULT; + } + } else if (atomic_read(&dev->fw_data->fw_state) == S2255_FW_NOTLOADED) { + /* give S2255_LOAD_TIMEOUT time for firmware to load in case + driver loaded and then device immediately opened */ + printk(KERN_INFO "%s waiting for firmware load\n", __func__); + wait_event_timeout(dev->fw_data->wait_fw, + (atomic_read(&dev->fw_data->fw_state) + != S2255_FW_NOTLOADED), + msecs_to_jiffies(S2255_LOAD_TIMEOUT)); + if (atomic_read(&dev->fw_data->fw_state) + != S2255_FW_SUCCESS) { + printk(KERN_INFO "2255 firmware not loaded" + "try again\n"); + mutex_unlock(&dev->open_lock); + return -EBUSY; + } + } + + /* allocate + initialize per filehandle data */ + fh = kzalloc(sizeof(*fh), GFP_KERNEL); + if (NULL == fh) { + mutex_unlock(&dev->open_lock); + return -ENOMEM; + } + + file->private_data = fh; + fh->dev = dev; + fh->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + fh->mode = dev->mode[cur_channel]; + fh->fmt = dev->cur_fmt[cur_channel]; + /* default 4CIF NTSC */ + fh->width = LINE_SZ_4CIFS_NTSC; + fh->height = NUM_LINES_4CIFS_NTSC * 2; + fh->channel = cur_channel; + + /* Put all controls at a sane state */ + for (i = 0; i < ARRAY_SIZE(s2255_qctrl); i++) + qctl_regs[i] = s2255_qctrl[i].default_value; + + dprintk(1, "s2255drv: open minor=%d type=%s users=%d\n", + minor, v4l2_type_names[type], dev->users[cur_channel]); + dprintk(2, "s2255drv: open: fh=0x%08lx, dev=0x%08lx, vidq=0x%08lx\n", + (unsigned long)fh, (unsigned long)dev, + (unsigned long)&dev->vidq[cur_channel]); + dprintk(4, "s2255drv: open: list_empty active=%d\n", + list_empty(&dev->vidq[cur_channel].active)); + + videobuf_queue_vmalloc_init(&fh->vb_vidq, &s2255_video_qops, + NULL, &dev->slock, + fh->type, + V4L2_FIELD_INTERLACED, + sizeof(struct s2255_buffer), fh); + + kref_get(&dev->kref); + mutex_unlock(&dev->open_lock); + return 0; +} + + +static unsigned int s2255_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct s2255_fh *fh = file->private_data; + int rc; + dprintk(100, "%s\n", __func__); + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != fh->type) + return POLLERR; + + rc = videobuf_poll_stream(file, &fh->vb_vidq, wait); + return rc; +} + +static void s2255_destroy(struct kref *kref) +{ + struct s2255_dev *dev = to_s2255_dev(kref); + if (!dev) { + printk(KERN_ERR "s2255drv: kref problem\n"); + return; + } + /* prevent s2255_disconnect from racing s2255_open */ + mutex_lock(&dev->open_lock); + s2255_exit_v4l(dev); + /* device unregistered so no longer possible to open. open_mutex + can be unlocked */ + mutex_unlock(&dev->open_lock); + + /* board shutdown stops the read pipe if it is running */ + s2255_board_shutdown(dev); + + /* make sure firmware still not trying to load */ + if (dev->fw_data->fw_urb) { + dprintk(2, "kill fw_urb\n"); + usb_kill_urb(dev->fw_data->fw_urb); + usb_free_urb(dev->fw_data->fw_urb); + dev->fw_data->fw_urb = NULL; + } + + /* make sure we aren't waiting for the DSP */ + if (atomic_read(&dev->fw_data->fw_state) == S2255_FW_LOADED_DSPWAIT) { + /* if we are, wait for the wakeup for fw_success or timeout */ + wait_event_timeout(dev->fw_data->wait_fw, + (atomic_read(&dev->fw_data->fw_state) + == S2255_FW_SUCCESS), + msecs_to_jiffies(S2255_LOAD_TIMEOUT)); + } + + if (dev->fw_data) { + kfree(dev->fw_data->pfw_data); + kfree(dev->fw_data); + } + + if (dev->fw_data->fw) { + release_firmware(dev->fw_data->fw); + dev->fw_data->fw = NULL; + } + + usb_put_dev(dev->udev); + dprintk(1, "%s", __func__); + kfree(dev); +} + +static int s2255_close(struct inode *inode, struct file *file) +{ + struct s2255_fh *fh = file->private_data; + struct s2255_dev *dev = fh->dev; + int minor = iminor(inode); + if (!dev) + return -ENODEV; + + mutex_lock(&dev->open_lock); + + if (dev->b_acquire[fh->channel]) + s2255_stop_acquire(dev, fh->channel); + res_free(dev, fh); + videobuf_mmap_free(&fh->vb_vidq); + kfree(fh); + dev->users[fh->channel]--; + mutex_unlock(&dev->open_lock); + + kref_put(&dev->kref, s2255_destroy); + dprintk(1, "s2255: close called (minor=%d, users=%d)\n", + minor, dev->users[fh->channel]); + return 0; +} + +static int s2255_mmap_v4l(struct file *file, struct vm_area_struct *vma) +{ + struct s2255_fh *fh = file->private_data; + int ret; + + if (!fh) + return -ENODEV; + dprintk(4, "mmap called, vma=0x%08lx\n", (unsigned long)vma); + + ret = videobuf_mmap_mapper(&fh->vb_vidq, vma); + + dprintk(4, "vma start=0x%08lx, size=%ld, ret=%d\n", + (unsigned long)vma->vm_start, + (unsigned long)vma->vm_end - (unsigned long)vma->vm_start, ret); + + return ret; +} + +static const struct file_operations s2255_fops_v4l = { + .owner = THIS_MODULE, + .open = s2255_open, + .release = s2255_close, + .poll = s2255_poll, + .ioctl = video_ioctl2, /* V4L2 ioctl handler */ + .compat_ioctl = v4l_compat_ioctl32, + .mmap = s2255_mmap_v4l, + .llseek = no_llseek, +}; + +static struct video_device template = { + .name = "s2255v", + .type = VID_TYPE_CAPTURE, + .fops = &s2255_fops_v4l, + .minor = -1, + .release = video_device_release, + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_cap = vidioc_enum_fmt_cap, + .vidioc_g_fmt_cap = vidioc_g_fmt_cap, + .vidioc_try_fmt_cap = vidioc_try_fmt_cap, + .vidioc_s_fmt_cap = vidioc_s_fmt_cap, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_s_std = vidioc_s_std, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_queryctrl = vidioc_queryctrl, + .vidioc_g_ctrl = vidioc_g_ctrl, + .vidioc_s_ctrl = vidioc_s_ctrl, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, +#ifdef CONFIG_VIDEO_V4L1_COMPAT + .vidiocgmbuf = vidioc_cgmbuf, +#endif + .tvnorms = S2255_NORMS, + .current_norm = V4L2_STD_NTSC_M, +}; + +static int s2255_probe_v4l(struct s2255_dev *dev) +{ + int ret; + int i; + int cur_nr = video_nr; + + /* initialize all video 4 linux */ + list_add_tail(&dev->s2255_devlist, &s2255_devlist); + /* register 4 video devices */ + for (i = 0; i < MAX_CHANNELS; i++) { + INIT_LIST_HEAD(&dev->vidq[i].active); + dev->vidq[i].dev = dev; + dev->vidq[i].channel = i; + dev->vidq[i].kthread = NULL; + /* register 4 video devices */ + dev->vdev[i] = video_device_alloc(); + memcpy(dev->vdev[i], &template, sizeof(struct video_device)); + dev->vdev[i]->dev = &dev->interface->dev; + if (video_nr == -1) + ret = video_register_device(dev->vdev[i], + VFL_TYPE_GRABBER, + video_nr); + else + ret = video_register_device(dev->vdev[i], + VFL_TYPE_GRABBER, + cur_nr + i); + dev->vdev[i]->priv = dev; + + if (ret != 0) { + dev_err(&dev->udev->dev, + "failed to register video device!\n"); + return ret; + } + } + printk(KERN_INFO "Sensoray 2255 V4L driver\n"); + return ret; +} + +static void s2255_exit_v4l(struct s2255_dev *dev) +{ + struct list_head *list; + int i; + /* unregister the video devices */ + while (!list_empty(&s2255_devlist)) { + list = s2255_devlist.next; + list_del(list); + } + for (i = 0; i < MAX_CHANNELS; i++) { + if (-1 != dev->vdev[i]->minor) + video_unregister_device(dev->vdev[i]); + else + video_device_release(dev->vdev[i]); + } +} + +/* this function moves the usb stream read pipe data + * into the system buffers. + * returns 0 on success, EAGAIN if more data to process( call this + * function again). + * + * Received frame structure: + * bytes 0-3: marker : 0x2255DA4AL (FRAME_MARKER) + * bytes 4-7: channel: 0-3 + * bytes 8-11: payload size: size of the frame + * bytes 12-payloadsize+12: frame data + */ +static int save_frame(struct s2255_dev *dev, struct s2255_pipeinfo *pipe_info) +{ + static int dbgsync; /* = 0; */ + char *pdest; + u32 offset = 0; + int bsync = 0; + int btrunc = 0; + char *psrc; + unsigned long copy_size; + unsigned long size; + s32 idx = -1; + struct s2255_framei *frm; + unsigned char *pdata; + unsigned long cur_size; + int bsearch = 0; + struct s2255_bufferi *buf; + dprintk(100, "buffer to user\n"); + + idx = dev->cur_frame[dev->cc]; + buf = &dev->buffer[dev->cc]; + frm = &buf->frame[idx]; + + if (frm->ulState == 0) { + frm->ulState = 1; + frm->cur_size = 0; + bsearch = 1; + } else if (frm->ulState == 2) { + /* system frame was not freed */ + dprintk(2, "sys frame not free. overrun ringbuf\n"); + bsearch = 1; + frm->ulState = 1; + frm->cur_size = 0; + } + + if (bsearch) { + if (*(s32 *) pipe_info->transfer_buffer != FRAME_MARKER) { + u32 jj; + if (dbgsync == 0) { + dprintk(3, "not synched, discarding all packets" + "until marker\n"); + + dbgsync++; + } + pdata = (unsigned char *)pipe_info->transfer_buffer; + for (jj = 0; jj < (pipe_info->cur_transfer_size - 12); + jj++) { + if (*(s32 *) pdata == FRAME_MARKER) { + int cc; + dprintk(3, + "found frame marker at offset:" + " %d [%x %x]\n", jj, pdata[0], + pdata[1]); + offset = jj; + bsync = 1; + cc = *(u32 *) (pdata + sizeof(u32)); + if (cc >= MAX_CHANNELS) { + printk(KERN_ERR + "bad channel\n"); + return -EINVAL; + } + /* reverse it */ + dev->cc = G_chnmap[cc]; + break; + } + pdata++; + } + if (bsync == 0) + return -EINVAL; + } else { + u32 *pword; + u32 payload; + int cc; + dbgsync = 0; + bsync = 1; + pword = (u32 *) pipe_info->transfer_buffer; + cc = pword[1]; + + if (cc >= MAX_CHANNELS) { + printk("invalid channel found. " + "throwing out data!\n"); + return -EINVAL; + } + dev->cc = G_chnmap[cc]; + payload = pword[2]; + if (payload != dev->req_image_size[dev->cc]) { + dprintk(1, "[%d][%d]unexpected payload: %d" + "required: %lu \n", cc, dev->cc, + payload, dev->req_image_size[dev->cc]); + dev->bad_payload[dev->cc]++; + /* discard the bad frame */ + return -EINVAL; + } + + } + } + /* search done. now find out if should be acquiring + on this channel */ + if (!dev->b_acquire[dev->cc]) { + frm->ulState = 0; + return -EINVAL; + } + + idx = dev->cur_frame[dev->cc]; + frm = &dev->buffer[dev->cc].frame[idx]; + + if (frm->ulState == 0) { + frm->ulState = 1; + frm->cur_size = 0; + } else if (frm->ulState == 2) { + /* system frame ring buffer overrun */ + dprintk(2, "sys frame overrun. overwriting frame %d %d\n", + dev->cc, idx); + frm->ulState = 1; + frm->cur_size = 0; + } + + if (bsync) { + /* skip the marker 512 bytes (and offset if out of sync) */ + psrc = (u8 *)pipe_info->transfer_buffer + offset + PREFIX_SIZE; + } else { + psrc = (u8 *)pipe_info->transfer_buffer; + } + + if (frm->lpvbits == NULL) { + dprintk(1, "s2255 frame buffer == NULL.%p %p %d %d", + frm, dev, dev->cc, idx); + return -ENOMEM; + } + + pdest = frm->lpvbits + frm->cur_size; + + if (bsync) { + copy_size = + (pipe_info->cur_transfer_size - offset) - PREFIX_SIZE; + if (copy_size > pipe_info->cur_transfer_size) { + printk("invalid copy size, overflow!\n"); + return -ENOMEM; + } + } else { + copy_size = pipe_info->cur_transfer_size; + } + + cur_size = frm->cur_size; + size = dev->req_image_size[dev->cc]; + + if ((copy_size + cur_size) > size) { + copy_size = size - cur_size; + btrunc = 1; + } + + memcpy(pdest, psrc, copy_size); + cur_size += copy_size; + frm->cur_size += copy_size; + dprintk(50, "cur_size size %lu size %lu \n", cur_size, size); + + if (cur_size >= (size - PREFIX_SIZE)) { + u32 cc = dev->cc; + frm->ulState = 2; + dprintk(2, "****************[%d]Buffer[%d]full*************\n", + cc, idx); + dev->last_frame[cc] = dev->cur_frame[cc]; + dev->cur_frame[cc]++; + /* end of system frame ring buffer, start at zero */ + if ((dev->cur_frame[cc] == SYS_FRAMES) || + (dev->cur_frame[cc] == dev->buffer[cc].dwFrames)) + dev->cur_frame[cc] = 0; + + /* signal the semaphore for this channel */ + if (dev->b_acquire[cc]) + s2255_got_frame(dev, cc); + dev->frame_count[cc]++; + } + /* frame was truncated */ + if (btrunc) { + /* return more data to process */ + return EAGAIN; + } + /* done successfully */ + return 0; +} + +static void s2255_read_video_callback(struct s2255_dev *dev, + struct s2255_pipeinfo *pipe_info) +{ + int res; + dprintk(50, "callback read video \n"); + + if (dev->cc >= MAX_CHANNELS) { + dev->cc = 0; + dev_err(&dev->udev->dev, "invalid channel\n"); + return; + } + /* otherwise copy to the system buffers */ + res = save_frame(dev, pipe_info); + if (res == EAGAIN) + save_frame(dev, pipe_info); + + dprintk(50, "callback read video done\n"); + return; +} + +static long s2255_vendor_req(struct s2255_dev *dev, unsigned char Request, + u16 Index, u16 Value, void *TransferBuffer, + s32 TransferBufferLength, int bOut) +{ + int r; + if (!bOut) { + r = usb_control_msg(dev->udev, usb_rcvctrlpipe(dev->udev, 0), + Request, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | + USB_DIR_IN, + Value, Index, TransferBuffer, + TransferBufferLength, HZ * 5); + } else { + r = usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), + Request, USB_TYPE_VENDOR | USB_RECIP_DEVICE, + Value, Index, TransferBuffer, + TransferBufferLength, HZ * 5); + } + return r; +} + +/* + * retrieve FX2 firmware version. future use. + * @param dev pointer to device extension + * @return -1 for fail, else returns firmware version as an int(16 bits) + */ +static int s2255_get_fx2fw(struct s2255_dev *dev) +{ + int fw; + int ret; + unsigned char transBuffer[64]; + ret = s2255_vendor_req(dev, S2255_VR_FW, 0, 0, transBuffer, 2, + S2255_VR_IN); + if (ret < 0) + dprintk(2, "get fw error: %x\n", ret); + fw = transBuffer[0] + (transBuffer[1] << 8); + dprintk(2, "Get FW %x %x\n", transBuffer[0], transBuffer[1]); + return fw; +} + +/* + * Create the system ring buffer to copy frames into from the + * usb read pipe. + */ +static int s2255_create_sys_buffers(struct s2255_dev *dev, unsigned long chn) +{ + unsigned long i; + unsigned long reqsize; + dprintk(1, "create sys buffers\n"); + if (chn >= MAX_CHANNELS) + return -1; + + dev->buffer[chn].dwFrames = SYS_FRAMES; + + /* always allocate maximum size(PAL) for system buffers */ + reqsize = SYS_FRAMES_MAXSIZE; + + if (reqsize > SYS_FRAMES_MAXSIZE) + reqsize = SYS_FRAMES_MAXSIZE; + + for (i = 0; i < SYS_FRAMES; i++) { + /* allocate the frames */ + dev->buffer[chn].frame[i].lpvbits = vmalloc(reqsize); + + dprintk(1, "valloc %p chan %lu, idx %lu, pdata %p\n", + &dev->buffer[chn].frame[i], chn, i, + dev->buffer[chn].frame[i].lpvbits); + dev->buffer[chn].frame[i].size = reqsize; + if (dev->buffer[chn].frame[i].lpvbits == NULL) { + printk(KERN_INFO "out of memory. using less frames\n"); + dev->buffer[chn].dwFrames = i; + break; + } + } + + /* make sure internal states are set */ + for (i = 0; i < SYS_FRAMES; i++) { + dev->buffer[chn].frame[i].ulState = 0; + dev->buffer[chn].frame[i].cur_size = 0; + } + + dev->cur_frame[chn] = 0; + dev->last_frame[chn] = -1; + return 0; +} + +static int s2255_release_sys_buffers(struct s2255_dev *dev, + unsigned long channel) +{ + unsigned long i; + dprintk(1, "release sys buffers\n"); + for (i = 0; i < SYS_FRAMES; i++) { + if (dev->buffer[channel].frame[i].lpvbits) { + dprintk(1, "vfree %p\n", + dev->buffer[channel].frame[i].lpvbits); + vfree(dev->buffer[channel].frame[i].lpvbits); + } + dev->buffer[channel].frame[i].lpvbits = NULL; + } + return 0; +} + +static int s2255_board_init(struct s2255_dev *dev) +{ + int j; + struct s2255_mode mode_def = DEF_MODEI_NTSC_CONT; + int fw_ver; + dprintk(4, "board init: %p", dev); + + for (j = 0; j < MAX_PIPE_BUFFERS; j++) { + struct s2255_pipeinfo *pipe = &dev->pipes[j]; + + memset(pipe, 0, sizeof(*pipe)); + pipe->dev = dev; + pipe->cur_transfer_size = DEFAULT_PIPE_USBBLOCK; + pipe->max_transfer_size = MAX_PIPE_USBBLOCK; + + if (pipe->cur_transfer_size > pipe->max_transfer_size) + pipe->cur_transfer_size = pipe->max_transfer_size; + pipe->transfer_buffer = kzalloc(pipe->max_transfer_size, + GFP_KERNEL); + if (pipe->transfer_buffer == NULL) { + dprintk(1, "out of memory!\n"); + return -ENOMEM; + } + + } + + /* query the firmware */ + fw_ver = s2255_get_fx2fw(dev); + + printk(KERN_INFO "2255 usb firmware version %d \n", fw_ver); + if (fw_ver < CUR_USB_FWVER) + err("usb firmware not up to date %d\n", fw_ver); + + for (j = 0; j < MAX_CHANNELS; j++) { + dev->b_acquire[j] = 0; + dev->mode[j] = mode_def; + dev->cur_fmt[j] = &formats[0]; + dev->mode[j].restart = 1; + dev->req_image_size[j] = get_transfer_size(&mode_def); + dev->frame_count[j] = 0; + /* create the system buffers */ + s2255_create_sys_buffers(dev, j); + } + /* start read pipe */ + s2255_start_readpipe(dev); + + dprintk(1, "S2255: board initialized\n"); + return 0; +} + +static int s2255_board_shutdown(struct s2255_dev *dev) +{ + u32 i; + + dprintk(1, "S2255: board shutdown: %p", dev); + + for (i = 0; i < MAX_CHANNELS; i++) { + if (dev->b_acquire[i]) + s2255_stop_acquire(dev, i); + } + + s2255_stop_readpipe(dev); + + for (i = 0; i < MAX_CHANNELS; i++) + s2255_release_sys_buffers(dev, i); + + /* release transfer buffers */ + for (i = 0; i < MAX_PIPE_BUFFERS; i++) { + struct s2255_pipeinfo *pipe = &dev->pipes[i]; + kfree(pipe->transfer_buffer); + } + return 0; +} + +static void read_pipe_completion(struct urb *purb) +{ + struct s2255_pipeinfo *pipe_info; + struct s2255_dev *dev; + int status; + int pipe; + + pipe_info = purb->context; + dprintk(100, "read pipe completion %p, status %d\n", purb, + purb->status); + if (pipe_info == NULL) { + err("no context !"); + return; + } + + dev = pipe_info->dev; + if (dev == NULL) { + err("no context !"); + return; + } + status = purb->status; + if (status != 0) { + dprintk(2, "read_pipe_completion: err\n"); + return; + } + + if (pipe_info->state == 0) { + dprintk(2, "exiting USB pipe"); + return; + } + + s2255_read_video_callback(dev, pipe_info); + + pipe_info->err_count = 0; + pipe = usb_rcvbulkpipe(dev->udev, dev->read_endpoint); + /* reuse urb */ + usb_fill_bulk_urb(pipe_info->stream_urb, dev->udev, + pipe, + pipe_info->transfer_buffer, + pipe_info->cur_transfer_size, + read_pipe_completion, pipe_info); + + if (pipe_info->state != 0) { + if (usb_submit_urb(pipe_info->stream_urb, GFP_KERNEL)) { + dev_err(&dev->udev->dev, "error submitting urb\n"); + usb_free_urb(pipe_info->stream_urb); + } + } else { + dprintk(2, "read pipe complete state 0\n"); + } + return; +} + +static int s2255_start_readpipe(struct s2255_dev *dev) +{ + int pipe; + int retval; + int i; + struct s2255_pipeinfo *pipe_info = dev->pipes; + pipe = usb_rcvbulkpipe(dev->udev, dev->read_endpoint); + dprintk(2, "start pipe IN %d\n", dev->read_endpoint); + + for (i = 0; i < MAX_PIPE_BUFFERS; i++) { + pipe_info->state = 1; + pipe_info->buf_index = (u32) i; + pipe_info->priority_set = 0; + pipe_info->stream_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!pipe_info->stream_urb) { + dev_err(&dev->udev->dev, + "ReadStream: Unable to alloc URB"); + return -ENOMEM; + } + /* transfer buffer allocated in board_init */ + usb_fill_bulk_urb(pipe_info->stream_urb, dev->udev, + pipe, + pipe_info->transfer_buffer, + pipe_info->cur_transfer_size, + read_pipe_completion, pipe_info); + + pipe_info->urb_size = sizeof(pipe_info->stream_urb); + dprintk(4, "submitting URB %p\n", pipe_info->stream_urb); + retval = usb_submit_urb(pipe_info->stream_urb, GFP_KERNEL); + if (retval) { + printk(KERN_ERR "s2255: start read pipe failed\n"); + return retval; + } + } + + return 0; +} + +/* starts acquisition process */ +static int s2255_start_acquire(struct s2255_dev *dev, unsigned long chn) +{ + unsigned char *buffer; + int res; + unsigned long chn_rev; + int j; + if (chn >= MAX_CHANNELS) { + dprintk(2, "start acquire failed, bad channel %lu\n", chn); + return -1; + } + + chn_rev = G_chnmap[chn]; + dprintk(1, "S2255: start acquire %lu \n", chn); + + buffer = kzalloc(512, GFP_KERNEL); + if (buffer == NULL) { + dev_err(&dev->udev->dev, "out of mem\n"); + return -ENOMEM; + } + + dev->last_frame[chn] = -1; + dev->bad_payload[chn] = 0; + dev->cur_frame[chn] = 0; + for (j = 0; j < SYS_FRAMES; j++) { + dev->buffer[chn].frame[j].ulState = 0; + dev->buffer[chn].frame[j].cur_size = 0; + } + + /* send the start command */ + *(u32 *) buffer = IN_DATA_TOKEN; + *((u32 *) buffer + 1) = (u32) chn_rev; + *((u32 *) buffer + 2) = (u32) CMD_START; + res = s2255_write_config(dev->udev, (unsigned char *)buffer, 512); + if (res != 0) + dev_err(&dev->udev->dev, "CMD_START error\n"); + + dprintk(2, "start acquire exit[%lu] %d \n", chn, res); + kfree(buffer); + return 0; +} + +static int s2255_stop_acquire(struct s2255_dev *dev, unsigned long chn) +{ + unsigned char *buffer; + int res; + unsigned long chn_rev; + + if (chn >= MAX_CHANNELS) { + dprintk(2, "stop acquire failed, bad channel %lu\n", chn); + return -1; + } + chn_rev = G_chnmap[chn]; + + buffer = kzalloc(512, GFP_KERNEL); + if (buffer == NULL) { + dev_err(&dev->udev->dev, "out of mem\n"); + return -ENOMEM; + } + + /* send the stop command */ + dprintk(4, "stop acquire %lu\n", chn); + *(u32 *) buffer = IN_DATA_TOKEN; + *((u32 *) buffer + 1) = (u32) chn_rev; + *((u32 *) buffer + 2) = CMD_STOP; + res = s2255_write_config(dev->udev, (unsigned char *)buffer, 512); + + if (res != 0) + dev_err(&dev->udev->dev, "CMD_STOP error\n"); + + dprintk(4, "stop acquire: releasing states \n"); + + kfree(buffer); + dev->b_acquire[chn] = 0; + + return 0; +} + +static void s2255_stop_readpipe(struct s2255_dev *dev) +{ + int j; + + if (dev == NULL) { + err("s2255: invalid device"); + return; + } + dprintk(4, "stop read pipe\n"); + for (j = 0; j < MAX_PIPE_BUFFERS; j++) { + struct s2255_pipeinfo *pipe_info = &dev->pipes[j]; + if (pipe_info) { + if (pipe_info->state == 0) + continue; + pipe_info->state = 0; + pipe_info->prev_state = 1; + + } + } + + for (j = 0; j < MAX_PIPE_BUFFERS; j++) { + struct s2255_pipeinfo *pipe_info = &dev->pipes[j]; + if (pipe_info->stream_urb) { + /* cancel urb */ + usb_kill_urb(pipe_info->stream_urb); + usb_free_urb(pipe_info->stream_urb); + pipe_info->stream_urb = NULL; + } + } + dprintk(2, "s2255 stop read pipe: %d\n", j); + return; +} + +static void s2255_fwload_start(struct s2255_dev *dev) +{ + dev->fw_data->fw_size = dev->fw_data->fw->size; + atomic_set(&dev->fw_data->fw_state, S2255_FW_NOTLOADED); + memcpy(dev->fw_data->pfw_data, + dev->fw_data->fw->data, CHUNK_SIZE); + dev->fw_data->fw_loaded = CHUNK_SIZE; + usb_fill_bulk_urb(dev->fw_data->fw_urb, dev->udev, + usb_sndbulkpipe(dev->udev, 2), + dev->fw_data->pfw_data, + CHUNK_SIZE, s2255_fwchunk_complete, + dev->fw_data); + mod_timer(&dev->timer, jiffies + HZ); +} + +/* standard usb probe function */ +static int s2255_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct s2255_dev *dev = NULL; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int i; + int retval = -ENOMEM; + + dprintk(2, "s2255: probe\n"); + + /* allocate memory for our device state and initialize it to zero */ + dev = kzalloc(sizeof(struct s2255_dev), GFP_KERNEL); + if (dev == NULL) { + err("s2255: out of memory"); + goto error; + } + + dev->fw_data = kzalloc(sizeof(struct s2255_fw), GFP_KERNEL); + if (!dev->fw_data) + goto error; + + mutex_init(&dev->lock); + mutex_init(&dev->open_lock); + + /* grab usb_device and save it */ + dev->udev = usb_get_dev(interface_to_usbdev(interface)); + if (dev->udev == NULL) { + dev_err(&interface->dev, "null usb device\n"); + retval = -ENODEV; + goto error; + } + kref_init(&dev->kref); + dprintk(1, "dev: %p, kref: %p udev %p interface %p\n", dev, &dev->kref, + dev->udev, interface); + dev->interface = interface; + /* set up the endpoint information */ + iface_desc = interface->cur_altsetting; + dprintk(1, "num endpoints %d\n", iface_desc->desc.bNumEndpoints); + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + if (!dev->read_endpoint && usb_endpoint_is_bulk_in(endpoint)) { + /* we found the bulk in endpoint */ + dev->read_endpoint = endpoint->bEndpointAddress; + } + } + + if (!dev->read_endpoint) { + dev_err(&interface->dev, "Could not find bulk-in endpoint"); + goto error; + } + + /* set intfdata */ + usb_set_intfdata(interface, dev); + + dprintk(100, "after intfdata %p\n", dev); + + init_timer(&dev->timer); + dev->timer.function = s2255_timer; + dev->timer.data = (unsigned long)dev->fw_data; + + init_waitqueue_head(&dev->fw_data->wait_fw); + + + dev->fw_data->fw_urb = usb_alloc_urb(0, GFP_KERNEL); + + if (!dev->fw_data->fw_urb) { + dev_err(&interface->dev, "out of memory!\n"); + goto error; + } + dev->fw_data->pfw_data = kzalloc(CHUNK_SIZE, GFP_KERNEL); + if (!dev->fw_data->pfw_data) { + dev_err(&interface->dev, "out of memory!\n"); + goto error; + } + /* load the first chunk */ + if (request_firmware(&dev->fw_data->fw, + FIRMWARE_FILE_NAME, &dev->udev->dev)) { + printk(KERN_ERR "sensoray 2255 failed to get firmware\n"); + goto error; + } + + /* loads v4l specific */ + s2255_probe_v4l(dev); + /* load 2255 board specific */ + s2255_board_init(dev); + + dprintk(4, "before probe done %p\n", dev); + spin_lock_init(&dev->slock); + + s2255_fwload_start(dev); + dev_info(&interface->dev, "Sensoray 2255 detected\n"); + return 0; +error: + return retval; +} + +/* disconnect routine. when board is removed physically or with rmmod */ +static void s2255_disconnect(struct usb_interface *interface) +{ + struct s2255_dev *dev = NULL; + dprintk(1, "s2255: disconnect interface %p\n", interface); + dev = usb_get_intfdata(interface); + if (dev) { + kref_put(&dev->kref, s2255_destroy); + dprintk(1, "s2255drv: disconnect\n"); + dev_info(&interface->dev, "s2255usb now disconnected\n"); + } + usb_set_intfdata(interface, NULL); +} + +static struct usb_driver s2255_driver = { + .name = "s2255", + .probe = s2255_probe, + .disconnect = s2255_disconnect, + .id_table = s2255_table, +}; + +static int __init usb_s2255_init(void) +{ + int result; + + /* register this driver with the USB subsystem */ + result = usb_register(&s2255_driver); + + if (result) + err("usb_register failed. Error number %d", result); + + dprintk(2, "s2255_init: done\n"); + return result; +} + +static void __exit usb_s2255_exit(void) +{ + usb_deregister(&s2255_driver); +} + +module_init(usb_s2255_init); +module_exit(usb_s2255_exit); + +MODULE_DESCRIPTION("Sensoray 2255 Video for Linux driver"); +MODULE_AUTHOR("Dean Anderson (Sensoray Company Inc.)"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 9010e58da0f2..cb780578b307 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -235,6 +235,7 @@ obj-$(CONFIG_USB_CATC) += usb/ obj-$(CONFIG_USB_KAWETH) += usb/ obj-$(CONFIG_USB_PEGASUS) += usb/ obj-$(CONFIG_USB_RTL8150) += usb/ +obj-$(CONFIG_USB_HSO) += usb/ obj-$(CONFIG_USB_USBNET) += usb/ obj-$(CONFIG_USB_ZD1201) += usb/ diff --git a/drivers/net/usb/Kconfig b/drivers/net/usb/Kconfig index 0604f3faf043..68e198bd538b 100644 --- a/drivers/net/usb/Kconfig +++ b/drivers/net/usb/Kconfig @@ -154,6 +154,16 @@ config USB_NET_AX8817X This driver creates an interface named "ethX", where X depends on what other networking devices you have in use. +config USB_HSO + tristate "Option USB High Speed Mobile Devices" + depends on USB && RFKILL + default n + help + Choose this option if you have an Option HSDPA/HSUPA card. + These cards support downlink speeds of 7.2Mbps or greater. + + To compile this driver as a module, choose M here: the + module will be called hso. config USB_NET_CDCETHER tristate "CDC Ethernet support (smart devices such as cable modems)" diff --git a/drivers/net/usb/Makefile b/drivers/net/usb/Makefile index 595a539f8384..24800c157f98 100644 --- a/drivers/net/usb/Makefile +++ b/drivers/net/usb/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_USB_CATC) += catc.o obj-$(CONFIG_USB_KAWETH) += kaweth.o obj-$(CONFIG_USB_PEGASUS) += pegasus.o obj-$(CONFIG_USB_RTL8150) += rtl8150.o +obj-$(CONFIG_USB_HSO) += hso.o obj-$(CONFIG_USB_NET_AX8817X) += asix.o obj-$(CONFIG_USB_NET_CDCETHER) += cdc_ether.o obj-$(CONFIG_USB_NET_DM9601) += dm9601.o diff --git a/drivers/net/usb/hso.c b/drivers/net/usb/hso.c new file mode 100644 index 000000000000..031d07b105af --- /dev/null +++ b/drivers/net/usb/hso.c @@ -0,0 +1,2836 @@ +/****************************************************************************** + * + * Driver for Option High Speed Mobile Devices. + * + * Copyright (C) 2008 Option International + * Copyright (C) 2007 Andrew Bird (Sphere Systems Ltd) + * <ajb@spheresystems.co.uk> + * Copyright (C) 2008 Greg Kroah-Hartman <gregkh@suse.de> + * Copyright (C) 2008 Novell, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA + * + * + *****************************************************************************/ + +/****************************************************************************** + * + * Description of the device: + * + * Interface 0: Contains the IP network interface on the bulk end points. + * The multiplexed serial ports are using the interrupt and + * control endpoints. + * Interrupt contains a bitmap telling which multiplexed + * serialport needs servicing. + * + * Interface 1: Diagnostics port, uses bulk only, do not submit urbs until the + * port is opened, as this have a huge impact on the network port + * throughput. + * + * Interface 2: Standard modem interface - circuit switched interface, should + * not be used. + * + *****************************************************************************/ + +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/netdevice.h> +#include <linux/module.h> +#include <linux/ethtool.h> +#include <linux/usb.h> +#include <linux/timer.h> +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> +#include <linux/kmod.h> +#include <linux/rfkill.h> +#include <linux/ip.h> +#include <linux/uaccess.h> +#include <linux/usb/cdc.h> +#include <net/arp.h> +#include <asm/byteorder.h> + + +#define DRIVER_VERSION "1.2" +#define MOD_AUTHOR "Option Wireless" +#define MOD_DESCRIPTION "USB High Speed Option driver" +#define MOD_LICENSE "GPL" + +#define HSO_MAX_NET_DEVICES 10 +#define HSO__MAX_MTU 2048 +#define DEFAULT_MTU 1500 +#define DEFAULT_MRU 1500 + +#define CTRL_URB_RX_SIZE 1024 +#define CTRL_URB_TX_SIZE 64 + +#define BULK_URB_RX_SIZE 4096 +#define BULK_URB_TX_SIZE 8192 + +#define MUX_BULK_RX_BUF_SIZE HSO__MAX_MTU +#define MUX_BULK_TX_BUF_SIZE HSO__MAX_MTU +#define MUX_BULK_RX_BUF_COUNT 4 +#define USB_TYPE_OPTION_VENDOR 0x20 + +/* These definitions are used with the struct hso_net flags element */ +/* - use *_bit operations on it. (bit indices not values.) */ +#define HSO_NET_RUNNING 0 + +#define HSO_NET_TX_TIMEOUT (HZ*10) + +/* Serial port defines and structs. */ +#define HSO_SERIAL_FLAG_RX_SENT 0 + +#define HSO_SERIAL_MAGIC 0x48534f31 + +/* Number of ttys to handle */ +#define HSO_SERIAL_TTY_MINORS 256 + +#define MAX_RX_URBS 2 + +#define get_serial_by_tty(x) \ + (x ? (struct hso_serial *)x->driver_data : NULL) + +/*****************************************************************************/ +/* Debugging functions */ +/*****************************************************************************/ +#define D__(lvl_, fmt, arg...) \ + do { \ + printk(lvl_ "[%d:%s]: " fmt "\n", \ + __LINE__, __func__, ## arg); \ + } while (0) + +#define D_(lvl, args...) \ + do { \ + if (lvl & debug) \ + D__(KERN_INFO, args); \ + } while (0) + +#define D1(args...) D_(0x01, ##args) +#define D2(args...) D_(0x02, ##args) +#define D3(args...) D_(0x04, ##args) +#define D4(args...) D_(0x08, ##args) +#define D5(args...) D_(0x10, ##args) + +/*****************************************************************************/ +/* Enumerators */ +/*****************************************************************************/ +enum pkt_parse_state { + WAIT_IP, + WAIT_DATA, + WAIT_SYNC +}; + +/*****************************************************************************/ +/* Structs */ +/*****************************************************************************/ + +struct hso_shared_int { + struct usb_endpoint_descriptor *intr_endp; + void *shared_intr_buf; + struct urb *shared_intr_urb; + struct usb_device *usb; + int use_count; + int ref_count; + struct mutex shared_int_lock; +}; + +struct hso_net { + struct hso_device *parent; + struct net_device *net; + struct rfkill *rfkill; + + struct usb_endpoint_descriptor *in_endp; + struct usb_endpoint_descriptor *out_endp; + + struct urb *mux_bulk_rx_urb_pool[MUX_BULK_RX_BUF_COUNT]; + struct urb *mux_bulk_tx_urb; + void *mux_bulk_rx_buf_pool[MUX_BULK_RX_BUF_COUNT]; + void *mux_bulk_tx_buf; + + struct sk_buff *skb_rx_buf; + struct sk_buff *skb_tx_buf; + + enum pkt_parse_state rx_parse_state; + spinlock_t net_lock; + + unsigned short rx_buf_size; + unsigned short rx_buf_missing; + struct iphdr rx_ip_hdr; + + unsigned long flags; +}; + +struct hso_serial { + struct hso_device *parent; + int magic; + u8 minor; + + struct hso_shared_int *shared_int; + + /* rx/tx urb could be either a bulk urb or a control urb depending + on which serial port it is used on. */ + struct urb *rx_urb[MAX_RX_URBS]; + u8 num_rx_urbs; + u8 *rx_data[MAX_RX_URBS]; + u16 rx_data_length; /* should contain allocated length */ + + struct urb *tx_urb; + u8 *tx_data; + u8 *tx_buffer; + u16 tx_data_length; /* should contain allocated length */ + u16 tx_data_count; + u16 tx_buffer_count; + struct usb_ctrlrequest ctrl_req_tx; + struct usb_ctrlrequest ctrl_req_rx; + + struct usb_endpoint_descriptor *in_endp; + struct usb_endpoint_descriptor *out_endp; + + unsigned long flags; + u8 rts_state; + u8 dtr_state; + unsigned tx_urb_used:1; + + /* from usb_serial_port */ + struct tty_struct *tty; + int open_count; + spinlock_t serial_lock; + + int (*write_data) (struct hso_serial *serial); +}; + +struct hso_device { + union { + struct hso_serial *dev_serial; + struct hso_net *dev_net; + } port_data; + + u32 port_spec; + + u8 is_active; + u8 usb_gone; + struct work_struct async_get_intf; + struct work_struct async_put_intf; + + struct usb_device *usb; + struct usb_interface *interface; + + struct device *dev; + struct kref ref; + struct mutex mutex; +}; + +/* Type of interface */ +#define HSO_INTF_MASK 0xFF00 +#define HSO_INTF_MUX 0x0100 +#define HSO_INTF_BULK 0x0200 + +/* Type of port */ +#define HSO_PORT_MASK 0xFF +#define HSO_PORT_NO_PORT 0x0 +#define HSO_PORT_CONTROL 0x1 +#define HSO_PORT_APP 0x2 +#define HSO_PORT_GPS 0x3 +#define HSO_PORT_PCSC 0x4 +#define HSO_PORT_APP2 0x5 +#define HSO_PORT_GPS_CONTROL 0x6 +#define HSO_PORT_MSD 0x7 +#define HSO_PORT_VOICE 0x8 +#define HSO_PORT_DIAG2 0x9 +#define HSO_PORT_DIAG 0x10 +#define HSO_PORT_MODEM 0x11 +#define HSO_PORT_NETWORK 0x12 + +/* Additional device info */ +#define HSO_INFO_MASK 0xFF000000 +#define HSO_INFO_CRC_BUG 0x01000000 + +/*****************************************************************************/ +/* Prototypes */ +/*****************************************************************************/ +/* Serial driver functions */ +static int hso_serial_tiocmset(struct tty_struct *tty, struct file *file, + unsigned int set, unsigned int clear); +static void ctrl_callback(struct urb *urb); +static void put_rxbuf_data(struct urb *urb, struct hso_serial *serial); +static void hso_kick_transmit(struct hso_serial *serial); +/* Helper functions */ +static int hso_mux_submit_intr_urb(struct hso_shared_int *mux_int, + struct usb_device *usb, gfp_t gfp); +static void log_usb_status(int status, const char *function); +static struct usb_endpoint_descriptor *hso_get_ep(struct usb_interface *intf, + int type, int dir); +static int hso_get_mux_ports(struct usb_interface *intf, unsigned char *ports); +static void hso_free_interface(struct usb_interface *intf); +static int hso_start_serial_device(struct hso_device *hso_dev, gfp_t flags); +static int hso_stop_serial_device(struct hso_device *hso_dev); +static int hso_start_net_device(struct hso_device *hso_dev); +static void hso_free_shared_int(struct hso_shared_int *shared_int); +static int hso_stop_net_device(struct hso_device *hso_dev); +static void hso_serial_ref_free(struct kref *ref); +static void async_get_intf(struct work_struct *data); +static void async_put_intf(struct work_struct *data); +static int hso_put_activity(struct hso_device *hso_dev); +static int hso_get_activity(struct hso_device *hso_dev); + +/*****************************************************************************/ +/* Helping functions */ +/*****************************************************************************/ + +/* #define DEBUG */ + +#define dev2net(x) (x->port_data.dev_net) +#define dev2ser(x) (x->port_data.dev_serial) + +/* Debugging functions */ +#ifdef DEBUG +static void dbg_dump(int line_count, const char *func_name, unsigned char *buf, + unsigned int len) +{ + u8 i = 0; + + printk(KERN_DEBUG "[%d:%s]: len %d", line_count, func_name, len); + + for (i = 0; i < len; i++) { + if (!(i % 16)) + printk("\n 0x%03x: ", i); + printk("%02x ", (unsigned char)buf[i]); + } + printk("\n"); +} + +#define DUMP(buf_, len_) \ + dbg_dump(__LINE__, __func__, buf_, len_) + +#define DUMP1(buf_, len_) \ + do { \ + if (0x01 & debug) \ + DUMP(buf_, len_); \ + } while (0) +#else +#define DUMP(buf_, len_) +#define DUMP1(buf_, len_) +#endif + +/* module parameters */ +static int debug; +static int tty_major; +static int disable_net; + +/* driver info */ +static const char driver_name[] = "hso"; +static const char tty_filename[] = "ttyHS"; +static const char *version = __FILE__ ": " DRIVER_VERSION " " MOD_AUTHOR; +/* the usb driver itself (registered in hso_init) */ +static struct usb_driver hso_driver; +/* serial structures */ +static struct tty_driver *tty_drv; +static struct hso_device *serial_table[HSO_SERIAL_TTY_MINORS]; +static struct hso_device *network_table[HSO_MAX_NET_DEVICES]; +static spinlock_t serial_table_lock; +static struct ktermios *hso_serial_termios[HSO_SERIAL_TTY_MINORS]; +static struct ktermios *hso_serial_termios_locked[HSO_SERIAL_TTY_MINORS]; + +static const s32 default_port_spec[] = { + HSO_INTF_MUX | HSO_PORT_NETWORK, + HSO_INTF_BULK | HSO_PORT_DIAG, + HSO_INTF_BULK | HSO_PORT_MODEM, + 0 +}; + +static const s32 icon321_port_spec[] = { + HSO_INTF_MUX | HSO_PORT_NETWORK, + HSO_INTF_BULK | HSO_PORT_DIAG2, + HSO_INTF_BULK | HSO_PORT_MODEM, + HSO_INTF_BULK | HSO_PORT_DIAG, + 0 +}; + +#define default_port_device(vendor, product) \ + USB_DEVICE(vendor, product), \ + .driver_info = (kernel_ulong_t)default_port_spec + +#define icon321_port_device(vendor, product) \ + USB_DEVICE(vendor, product), \ + .driver_info = (kernel_ulong_t)icon321_port_spec + +/* list of devices we support */ +static const struct usb_device_id hso_ids[] = { + {default_port_device(0x0af0, 0x6711)}, + {default_port_device(0x0af0, 0x6731)}, + {default_port_device(0x0af0, 0x6751)}, + {default_port_device(0x0af0, 0x6771)}, + {default_port_device(0x0af0, 0x6791)}, + {default_port_device(0x0af0, 0x6811)}, + {default_port_device(0x0af0, 0x6911)}, + {default_port_device(0x0af0, 0x6951)}, + {default_port_device(0x0af0, 0x6971)}, + {default_port_device(0x0af0, 0x7011)}, + {default_port_device(0x0af0, 0x7031)}, + {default_port_device(0x0af0, 0x7051)}, + {default_port_device(0x0af0, 0x7071)}, + {default_port_device(0x0af0, 0x7111)}, + {default_port_device(0x0af0, 0x7211)}, + {default_port_device(0x0af0, 0x7251)}, + {default_port_device(0x0af0, 0x7271)}, + {default_port_device(0x0af0, 0x7311)}, + {default_port_device(0x0af0, 0xc031)}, /* Icon-Edge */ + {icon321_port_device(0x0af0, 0xd013)}, /* Module HSxPA */ + {icon321_port_device(0x0af0, 0xd031)}, /* Icon-321 */ + {default_port_device(0x0af0, 0xd033)}, /* Icon-322 */ + {USB_DEVICE(0x0af0, 0x7301)}, /* GE40x */ + {USB_DEVICE(0x0af0, 0x7361)}, /* GE40x */ + {USB_DEVICE(0x0af0, 0x7401)}, /* GI 0401 */ + {USB_DEVICE(0x0af0, 0x7501)}, /* GTM 382 */ + {USB_DEVICE(0x0af0, 0x7601)}, /* GE40x */ + {} +}; +MODULE_DEVICE_TABLE(usb, hso_ids); + +/* Sysfs attribute */ +static ssize_t hso_sysfs_show_porttype(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hso_device *hso_dev = dev->driver_data; + char *port_name; + + if (!hso_dev) + return 0; + + switch (hso_dev->port_spec & HSO_PORT_MASK) { + case HSO_PORT_CONTROL: + port_name = "Control"; + break; + case HSO_PORT_APP: + port_name = "Application"; + break; + case HSO_PORT_APP2: + port_name = "Application2"; + break; + case HSO_PORT_GPS: + port_name = "GPS"; + break; + case HSO_PORT_GPS_CONTROL: + port_name = "GPS Control"; + break; + case HSO_PORT_PCSC: + port_name = "PCSC"; + break; + case HSO_PORT_DIAG: + port_name = "Diagnostic"; + break; + case HSO_PORT_DIAG2: + port_name = "Diagnostic2"; + break; + case HSO_PORT_MODEM: + port_name = "Modem"; + break; + case HSO_PORT_NETWORK: + port_name = "Network"; + break; + default: + port_name = "Unknown"; + break; + } + + return sprintf(buf, "%s\n", port_name); +} +static DEVICE_ATTR(hsotype, S_IRUGO, hso_sysfs_show_porttype, NULL); + +/* converts mux value to a port spec value */ +static u32 hso_mux_to_port(int mux) +{ + u32 result; + + switch (mux) { + case 0x1: + result = HSO_PORT_CONTROL; + break; + case 0x2: + result = HSO_PORT_APP; + break; + case 0x4: + result = HSO_PORT_PCSC; + break; + case 0x8: + result = HSO_PORT_GPS; + break; + case 0x10: + result = HSO_PORT_APP2; + break; + default: + result = HSO_PORT_NO_PORT; + } + return result; +} + +/* converts port spec value to a mux value */ +static u32 hso_port_to_mux(int port) +{ + u32 result; + + switch (port & HSO_PORT_MASK) { + case HSO_PORT_CONTROL: + result = 0x0; + break; + case HSO_PORT_APP: + result = 0x1; + break; + case HSO_PORT_PCSC: + result = 0x2; + break; + case HSO_PORT_GPS: + result = 0x3; + break; + case HSO_PORT_APP2: + result = 0x4; + break; + default: + result = 0x0; + } + return result; +} + +static struct hso_serial *get_serial_by_shared_int_and_type( + struct hso_shared_int *shared_int, + int mux) +{ + int i, port; + + port = hso_mux_to_port(mux); + + for (i = 0; i < HSO_SERIAL_TTY_MINORS; i++) { + if (serial_table[i] + && (dev2ser(serial_table[i])->shared_int == shared_int) + && ((serial_table[i]->port_spec & HSO_PORT_MASK) == port)) { + return dev2ser(serial_table[i]); + } + } + + return NULL; +} + +static struct hso_serial *get_serial_by_index(unsigned index) +{ + struct hso_serial *serial; + unsigned long flags; + + if (!serial_table[index]) + return NULL; + spin_lock_irqsave(&serial_table_lock, flags); + serial = dev2ser(serial_table[index]); + spin_unlock_irqrestore(&serial_table_lock, flags); + + return serial; +} + +static int get_free_serial_index(void) +{ + int index; + unsigned long flags; + + spin_lock_irqsave(&serial_table_lock, flags); + for (index = 0; index < HSO_SERIAL_TTY_MINORS; index++) { + if (serial_table[index] == NULL) { + spin_unlock_irqrestore(&serial_table_lock, flags); + return index; + } + } + spin_unlock_irqrestore(&serial_table_lock, flags); + + printk(KERN_ERR "%s: no free serial devices in table\n", __func__); + return -1; +} + +static void set_serial_by_index(unsigned index, struct hso_serial *serial) +{ + unsigned long flags; + spin_lock_irqsave(&serial_table_lock, flags); + if (serial) + serial_table[index] = serial->parent; + else + serial_table[index] = NULL; + spin_unlock_irqrestore(&serial_table_lock, flags); +} + +/* log a meaningfull explanation of an USB status */ +static void log_usb_status(int status, const char *function) +{ + char *explanation; + + switch (status) { + case -ENODEV: + explanation = "no device"; + break; + case -ENOENT: + explanation = "endpoint not enabled"; + break; + case -EPIPE: + explanation = "endpoint stalled"; + break; + case -ENOSPC: + explanation = "not enough bandwidth"; + break; + case -ESHUTDOWN: + explanation = "device disabled"; + break; + case -EHOSTUNREACH: + explanation = "device suspended"; + break; + case -EINVAL: + case -EAGAIN: + case -EFBIG: + case -EMSGSIZE: + explanation = "internal error"; + break; + default: + explanation = "unknown status"; + break; + } + D1("%s: received USB status - %s (%d)", function, explanation, status); +} + +/* Network interface functions */ + +/* called when net interface is brought up by ifconfig */ +static int hso_net_open(struct net_device *net) +{ + struct hso_net *odev = netdev_priv(net); + unsigned long flags = 0; + + if (!odev) { + dev_err(&net->dev, "No net device !\n"); + return -ENODEV; + } + + odev->skb_tx_buf = NULL; + + /* setup environment */ + spin_lock_irqsave(&odev->net_lock, flags); + odev->rx_parse_state = WAIT_IP; + odev->rx_buf_size = 0; + odev->rx_buf_missing = sizeof(struct iphdr); + spin_unlock_irqrestore(&odev->net_lock, flags); + + hso_start_net_device(odev->parent); + + /* We are up and running. */ + set_bit(HSO_NET_RUNNING, &odev->flags); + + /* Tell the kernel we are ready to start receiving from it */ + netif_start_queue(net); + + return 0; +} + +/* called when interface is brought down by ifconfig */ +static int hso_net_close(struct net_device *net) +{ + struct hso_net *odev = netdev_priv(net); + + /* we don't need the queue anymore */ + netif_stop_queue(net); + /* no longer running */ + clear_bit(HSO_NET_RUNNING, &odev->flags); + + hso_stop_net_device(odev->parent); + + /* done */ + return 0; +} + +/* USB tells is xmit done, we should start the netqueue again */ +static void write_bulk_callback(struct urb *urb) +{ + struct hso_net *odev = urb->context; + int status = urb->status; + + /* Sanity check */ + if (!odev || !test_bit(HSO_NET_RUNNING, &odev->flags)) { + dev_err(&urb->dev->dev, "%s: device not running\n", __func__); + return; + } + + /* Do we still have a valid kernel network device? */ + if (!netif_device_present(odev->net)) { + dev_err(&urb->dev->dev, "%s: net device not present\n", + __func__); + return; + } + + /* log status, but don't act on it, we don't need to resubmit anything + * anyhow */ + if (status) + log_usb_status(status, __func__); + + hso_put_activity(odev->parent); + + /* Tell the network interface we are ready for another frame */ + netif_wake_queue(odev->net); +} + +/* called by kernel when we need to transmit a packet */ +static int hso_net_start_xmit(struct sk_buff *skb, struct net_device *net) +{ + struct hso_net *odev = netdev_priv(net); + int result; + + /* Tell the kernel, "No more frames 'til we are done with this one." */ + netif_stop_queue(net); + if (hso_get_activity(odev->parent) == -EAGAIN) { + odev->skb_tx_buf = skb; + return 0; + } + + /* log if asked */ + DUMP1(skb->data, skb->len); + /* Copy it from kernel memory to OUR memory */ + memcpy(odev->mux_bulk_tx_buf, skb->data, skb->len); + D1("len: %d/%d", skb->len, MUX_BULK_TX_BUF_SIZE); + + /* Fill in the URB for shipping it out. */ + usb_fill_bulk_urb(odev->mux_bulk_tx_urb, + odev->parent->usb, + usb_sndbulkpipe(odev->parent->usb, + odev->out_endp-> + bEndpointAddress & 0x7F), + odev->mux_bulk_tx_buf, skb->len, write_bulk_callback, + odev); + + /* Deal with the Zero Length packet problem, I hope */ + odev->mux_bulk_tx_urb->transfer_flags |= URB_ZERO_PACKET; + + /* Send the URB on its merry way. */ + result = usb_submit_urb(odev->mux_bulk_tx_urb, GFP_ATOMIC); + if (result) { + dev_warn(&odev->parent->interface->dev, + "failed mux_bulk_tx_urb %d", result); + net->stats.tx_errors++; + netif_start_queue(net); + } else { + net->stats.tx_packets++; + net->stats.tx_bytes += skb->len; + /* And tell the kernel when the last transmit started. */ + net->trans_start = jiffies; + } + dev_kfree_skb(skb); + /* we're done */ + return result; +} + +static void hso_get_drvinfo(struct net_device *net, struct ethtool_drvinfo *info) +{ + struct hso_net *odev = netdev_priv(net); + + strncpy(info->driver, driver_name, ETHTOOL_BUSINFO_LEN); + strncpy(info->version, DRIVER_VERSION, ETHTOOL_BUSINFO_LEN); + usb_make_path(odev->parent->usb, info->bus_info, sizeof info->bus_info); +} + +static struct ethtool_ops ops = { + .get_drvinfo = hso_get_drvinfo, + .get_link = ethtool_op_get_link +}; + +/* called when a packet did not ack after watchdogtimeout */ +static void hso_net_tx_timeout(struct net_device *net) +{ + struct hso_net *odev = netdev_priv(net); + + if (!odev) + return; + + /* Tell syslog we are hosed. */ + dev_warn(&net->dev, "Tx timed out.\n"); + + /* Tear the waiting frame off the list */ + if (odev->mux_bulk_tx_urb + && (odev->mux_bulk_tx_urb->status == -EINPROGRESS)) + usb_unlink_urb(odev->mux_bulk_tx_urb); + + /* Update statistics */ + net->stats.tx_errors++; +} + +/* make a real packet from the received USB buffer */ +static void packetizeRx(struct hso_net *odev, unsigned char *ip_pkt, + unsigned int count, unsigned char is_eop) +{ + unsigned short temp_bytes; + unsigned short buffer_offset = 0; + unsigned short frame_len; + unsigned char *tmp_rx_buf; + + /* log if needed */ + D1("Rx %d bytes", count); + DUMP(ip_pkt, min(128, (int)count)); + + while (count) { + switch (odev->rx_parse_state) { + case WAIT_IP: + /* waiting for IP header. */ + /* wanted bytes - size of ip header */ + temp_bytes = + (count < + odev->rx_buf_missing) ? count : odev-> + rx_buf_missing; + + memcpy(((unsigned char *)(&odev->rx_ip_hdr)) + + odev->rx_buf_size, ip_pkt + buffer_offset, + temp_bytes); + + odev->rx_buf_size += temp_bytes; + buffer_offset += temp_bytes; + odev->rx_buf_missing -= temp_bytes; + count -= temp_bytes; + + if (!odev->rx_buf_missing) { + /* header is complete allocate an sk_buffer and + * continue to WAIT_DATA */ + frame_len = ntohs(odev->rx_ip_hdr.tot_len); + + if ((frame_len > DEFAULT_MRU) || + (frame_len < sizeof(struct iphdr))) { + dev_err(&odev->net->dev, + "Invalid frame (%d) length\n", + frame_len); + odev->rx_parse_state = WAIT_SYNC; + continue; + } + /* Allocate an sk_buff */ + odev->skb_rx_buf = dev_alloc_skb(frame_len); + if (!odev->skb_rx_buf) { + /* We got no receive buffer. */ + D1("could not allocate memory"); + odev->rx_parse_state = WAIT_SYNC; + return; + } + /* Here's where it came from */ + odev->skb_rx_buf->dev = odev->net; + + /* Copy what we got so far. make room for iphdr + * after tail. */ + tmp_rx_buf = + skb_put(odev->skb_rx_buf, + sizeof(struct iphdr)); + memcpy(tmp_rx_buf, (char *)&(odev->rx_ip_hdr), + sizeof(struct iphdr)); + + /* ETH_HLEN */ + odev->rx_buf_size = sizeof(struct iphdr); + + /* Filip actually use .tot_len */ + odev->rx_buf_missing = + frame_len - sizeof(struct iphdr); + odev->rx_parse_state = WAIT_DATA; + } + break; + + case WAIT_DATA: + temp_bytes = (count < odev->rx_buf_missing) + ? count : odev->rx_buf_missing; + + /* Copy the rest of the bytes that are left in the + * buffer into the waiting sk_buf. */ + /* Make room for temp_bytes after tail. */ + tmp_rx_buf = skb_put(odev->skb_rx_buf, temp_bytes); + memcpy(tmp_rx_buf, ip_pkt + buffer_offset, temp_bytes); + + odev->rx_buf_missing -= temp_bytes; + count -= temp_bytes; + buffer_offset += temp_bytes; + odev->rx_buf_size += temp_bytes; + if (!odev->rx_buf_missing) { + /* Packet is complete. Inject into stack. */ + /* We have IP packet here */ + odev->skb_rx_buf->protocol = + __constant_htons(ETH_P_IP); + /* don't check it */ + odev->skb_rx_buf->ip_summed = + CHECKSUM_UNNECESSARY; + + skb_reset_mac_header(odev->skb_rx_buf); + + /* Ship it off to the kernel */ + netif_rx(odev->skb_rx_buf); + /* No longer our buffer. */ + odev->skb_rx_buf = NULL; + + /* update out statistics */ + odev->net->stats.rx_packets++; + + odev->net->stats.rx_bytes += odev->rx_buf_size; + + odev->rx_buf_size = 0; + odev->rx_buf_missing = sizeof(struct iphdr); + odev->rx_parse_state = WAIT_IP; + } + break; + + case WAIT_SYNC: + D1(" W_S"); + count = 0; + break; + default: + D1(" "); + count--; + break; + } + } + + /* Recovery mechanism for WAIT_SYNC state. */ + if (is_eop) { + if (odev->rx_parse_state == WAIT_SYNC) { + odev->rx_parse_state = WAIT_IP; + odev->rx_buf_size = 0; + odev->rx_buf_missing = sizeof(struct iphdr); + } + } +} + +/* Moving data from usb to kernel (in interrupt state) */ +static void read_bulk_callback(struct urb *urb) +{ + struct hso_net *odev = urb->context; + struct net_device *net; + int result; + int status = urb->status; + + /* is al ok? (Filip: Who's Al ?) */ + if (status) { + log_usb_status(status, __func__); + return; + } + + /* Sanity check */ + if (!odev || !test_bit(HSO_NET_RUNNING, &odev->flags)) { + D1("BULK IN callback but driver is not active!"); + return; + } + usb_mark_last_busy(urb->dev); + + net = odev->net; + + if (!netif_device_present(net)) { + /* Somebody killed our network interface... */ + return; + } + + if (odev->parent->port_spec & HSO_INFO_CRC_BUG) { + u32 rest; + u8 crc_check[4] = { 0xDE, 0xAD, 0xBE, 0xEF }; + rest = urb->actual_length % odev->in_endp->wMaxPacketSize; + if (((rest == 5) || (rest == 6)) + && !memcmp(((u8 *) urb->transfer_buffer) + + urb->actual_length - 4, crc_check, 4)) { + urb->actual_length -= 4; + } + } + + /* do we even have a packet? */ + if (urb->actual_length) { + /* Handle the IP stream, add header and push it onto network + * stack if the packet is complete. */ + spin_lock(&odev->net_lock); + packetizeRx(odev, urb->transfer_buffer, urb->actual_length, + (urb->transfer_buffer_length > + urb->actual_length) ? 1 : 0); + spin_unlock(&odev->net_lock); + } + + /* We are done with this URB, resubmit it. Prep the USB to wait for + * another frame. Reuse same as received. */ + usb_fill_bulk_urb(urb, + odev->parent->usb, + usb_rcvbulkpipe(odev->parent->usb, + odev->in_endp-> + bEndpointAddress & 0x7F), + urb->transfer_buffer, MUX_BULK_RX_BUF_SIZE, + read_bulk_callback, odev); + + /* Give this to the USB subsystem so it can tell us when more data + * arrives. */ + result = usb_submit_urb(urb, GFP_ATOMIC); + if (result) + dev_warn(&odev->parent->interface->dev, + "%s failed submit mux_bulk_rx_urb %d", __func__, + result); +} + +/* Serial driver functions */ + +static void _hso_serial_set_termios(struct tty_struct *tty, + struct ktermios *old) +{ + struct hso_serial *serial = get_serial_by_tty(tty); + struct ktermios *termios; + + if ((!tty) || (!tty->termios) || (!serial)) { + printk(KERN_ERR "%s: no tty structures", __func__); + return; + } + + D4("port %d", serial->minor); + + /* + * The default requirements for this device are: + */ + termios = tty->termios; + termios->c_iflag &= + ~(IGNBRK /* disable ignore break */ + | BRKINT /* disable break causes interrupt */ + | PARMRK /* disable mark parity errors */ + | ISTRIP /* disable clear high bit of input characters */ + | INLCR /* disable translate NL to CR */ + | IGNCR /* disable ignore CR */ + | ICRNL /* disable translate CR to NL */ + | IXON); /* disable enable XON/XOFF flow control */ + + /* disable postprocess output characters */ + termios->c_oflag &= ~OPOST; + + termios->c_lflag &= + ~(ECHO /* disable echo input characters */ + | ECHONL /* disable echo new line */ + | ICANON /* disable erase, kill, werase, and rprnt + special characters */ + | ISIG /* disable interrupt, quit, and suspend special + characters */ + | IEXTEN); /* disable non-POSIX special characters */ + + termios->c_cflag &= + ~(CSIZE /* no size */ + | PARENB /* disable parity bit */ + | CBAUD /* clear current baud rate */ + | CBAUDEX); /* clear current buad rate */ + + termios->c_cflag |= CS8; /* character size 8 bits */ + + /* baud rate 115200 */ + tty_encode_baud_rate(serial->tty, 115200, 115200); + + /* + * Force low_latency on; otherwise the pushes are scheduled; + * this is bad as it opens up the possibility of dropping bytes + * on the floor. We don't want to drop bytes on the floor. :) + */ + serial->tty->low_latency = 1; + return; +} + +/* open the requested serial port */ +static int hso_serial_open(struct tty_struct *tty, struct file *filp) +{ + struct hso_serial *serial = get_serial_by_index(tty->index); + int result; + + /* sanity check */ + if (serial == NULL || serial->magic != HSO_SERIAL_MAGIC) { + tty->driver_data = NULL; + D1("Failed to open port"); + return -ENODEV; + } + + mutex_lock(&serial->parent->mutex); + result = usb_autopm_get_interface(serial->parent->interface); + if (result < 0) + goto err_out; + + D1("Opening %d", serial->minor); + kref_get(&serial->parent->ref); + + /* setup */ + tty->driver_data = serial; + serial->tty = tty; + + /* check for port allready opened, if not set the termios */ + serial->open_count++; + if (serial->open_count == 1) { + tty->low_latency = 1; + serial->flags = 0; + /* Force default termio settings */ + _hso_serial_set_termios(tty, NULL); + result = hso_start_serial_device(serial->parent, GFP_KERNEL); + if (result) { + hso_stop_serial_device(serial->parent); + serial->open_count--; + kref_put(&serial->parent->ref, hso_serial_ref_free); + } + } else { + D1("Port was already open"); + } + + usb_autopm_put_interface(serial->parent->interface); + + /* done */ + if (result) + hso_serial_tiocmset(tty, NULL, TIOCM_RTS | TIOCM_DTR, 0); +err_out: + mutex_unlock(&serial->parent->mutex); + return result; +} + +/* close the requested serial port */ +static void hso_serial_close(struct tty_struct *tty, struct file *filp) +{ + struct hso_serial *serial = tty->driver_data; + u8 usb_gone; + + D1("Closing serial port"); + + mutex_lock(&serial->parent->mutex); + usb_gone = serial->parent->usb_gone; + + if (!usb_gone) + usb_autopm_get_interface(serial->parent->interface); + + /* reset the rts and dtr */ + /* do the actual close */ + serial->open_count--; + if (serial->open_count <= 0) { + kref_put(&serial->parent->ref, hso_serial_ref_free); + serial->open_count = 0; + if (serial->tty) { + serial->tty->driver_data = NULL; + serial->tty = NULL; + } + if (!usb_gone) + hso_stop_serial_device(serial->parent); + } + if (!usb_gone) + usb_autopm_put_interface(serial->parent->interface); + mutex_unlock(&serial->parent->mutex); +} + +/* close the requested serial port */ +static int hso_serial_write(struct tty_struct *tty, const unsigned char *buf, + int count) +{ + struct hso_serial *serial = get_serial_by_tty(tty); + int space, tx_bytes; + unsigned long flags; + + /* sanity check */ + if (serial == NULL) { + printk(KERN_ERR "%s: serial is NULL\n", __func__); + return -ENODEV; + } + + spin_lock_irqsave(&serial->serial_lock, flags); + + space = serial->tx_data_length - serial->tx_buffer_count; + tx_bytes = (count < space) ? count : space; + + if (!tx_bytes) + goto out; + + memcpy(serial->tx_buffer + serial->tx_buffer_count, buf, tx_bytes); + serial->tx_buffer_count += tx_bytes; + +out: + spin_unlock_irqrestore(&serial->serial_lock, flags); + + hso_kick_transmit(serial); + /* done */ + return tx_bytes; +} + +/* how much room is there for writing */ +static int hso_serial_write_room(struct tty_struct *tty) +{ + struct hso_serial *serial = get_serial_by_tty(tty); + int room; + unsigned long flags; + + spin_lock_irqsave(&serial->serial_lock, flags); + room = serial->tx_data_length - serial->tx_buffer_count; + spin_unlock_irqrestore(&serial->serial_lock, flags); + + /* return free room */ + return room; +} + +/* setup the term */ +static void hso_serial_set_termios(struct tty_struct *tty, struct ktermios *old) +{ + struct hso_serial *serial = get_serial_by_tty(tty); + unsigned long flags; + + if (old) + D5("Termios called with: cflags new[%d] - old[%d]", + tty->termios->c_cflag, old->c_cflag); + + /* the actual setup */ + spin_lock_irqsave(&serial->serial_lock, flags); + if (serial->open_count) + _hso_serial_set_termios(tty, old); + else + tty->termios = old; + spin_unlock_irqrestore(&serial->serial_lock, flags); + + /* done */ + return; +} + +/* how many characters in the buffer */ +static int hso_serial_chars_in_buffer(struct tty_struct *tty) +{ + struct hso_serial *serial = get_serial_by_tty(tty); + int chars; + unsigned long flags; + + /* sanity check */ + if (serial == NULL) + return 0; + + spin_lock_irqsave(&serial->serial_lock, flags); + chars = serial->tx_buffer_count; + spin_unlock_irqrestore(&serial->serial_lock, flags); + + return chars; +} + +static int hso_serial_tiocmget(struct tty_struct *tty, struct file *file) +{ + unsigned int value; + struct hso_serial *serial = get_serial_by_tty(tty); + unsigned long flags; + + /* sanity check */ + if (!serial) { + D1("no tty structures"); + return -EINVAL; + } + + spin_lock_irqsave(&serial->serial_lock, flags); + value = ((serial->rts_state) ? TIOCM_RTS : 0) | + ((serial->dtr_state) ? TIOCM_DTR : 0); + spin_unlock_irqrestore(&serial->serial_lock, flags); + + return value; +} + +static int hso_serial_tiocmset(struct tty_struct *tty, struct file *file, + unsigned int set, unsigned int clear) +{ + int val = 0; + unsigned long flags; + int if_num; + struct hso_serial *serial = get_serial_by_tty(tty); + + /* sanity check */ + if (!serial) { + D1("no tty structures"); + return -EINVAL; + } + if_num = serial->parent->interface->altsetting->desc.bInterfaceNumber; + + spin_lock_irqsave(&serial->serial_lock, flags); + if (set & TIOCM_RTS) + serial->rts_state = 1; + if (set & TIOCM_DTR) + serial->dtr_state = 1; + + if (clear & TIOCM_RTS) + serial->rts_state = 0; + if (clear & TIOCM_DTR) + serial->dtr_state = 0; + + if (serial->dtr_state) + val |= 0x01; + if (serial->rts_state) + val |= 0x02; + + spin_unlock_irqrestore(&serial->serial_lock, flags); + + return usb_control_msg(serial->parent->usb, + usb_rcvctrlpipe(serial->parent->usb, 0), 0x22, + 0x21, val, if_num, NULL, 0, + USB_CTRL_SET_TIMEOUT); +} + +/* starts a transmit */ +static void hso_kick_transmit(struct hso_serial *serial) +{ + u8 *temp; + unsigned long flags; + int res; + + spin_lock_irqsave(&serial->serial_lock, flags); + if (!serial->tx_buffer_count) + goto out; + + if (serial->tx_urb_used) + goto out; + + /* Wakeup USB interface if necessary */ + if (hso_get_activity(serial->parent) == -EAGAIN) + goto out; + + /* Switch pointers around to avoid memcpy */ + temp = serial->tx_buffer; + serial->tx_buffer = serial->tx_data; + serial->tx_data = temp; + serial->tx_data_count = serial->tx_buffer_count; + serial->tx_buffer_count = 0; + + /* If temp is set, it means we switched buffers */ + if (temp && serial->write_data) { + res = serial->write_data(serial); + if (res >= 0) + serial->tx_urb_used = 1; + } +out: + spin_unlock_irqrestore(&serial->serial_lock, flags); +} + +/* make a request (for reading and writing data to muxed serial port) */ +static int mux_device_request(struct hso_serial *serial, u8 type, u16 port, + struct urb *ctrl_urb, + struct usb_ctrlrequest *ctrl_req, + u8 *ctrl_urb_data, u32 size) +{ + int result; + int pipe; + + /* Sanity check */ + if (!serial || !ctrl_urb || !ctrl_req) { + printk(KERN_ERR "%s: Wrong arguments\n", __func__); + return -EINVAL; + } + + /* initialize */ + ctrl_req->wValue = 0; + ctrl_req->wIndex = hso_port_to_mux(port); + ctrl_req->wLength = size; + + if (type == USB_CDC_GET_ENCAPSULATED_RESPONSE) { + /* Reading command */ + ctrl_req->bRequestType = USB_DIR_IN | + USB_TYPE_OPTION_VENDOR | + USB_RECIP_INTERFACE; + ctrl_req->bRequest = USB_CDC_GET_ENCAPSULATED_RESPONSE; + pipe = usb_rcvctrlpipe(serial->parent->usb, 0); + } else { + /* Writing command */ + ctrl_req->bRequestType = USB_DIR_OUT | + USB_TYPE_OPTION_VENDOR | + USB_RECIP_INTERFACE; + ctrl_req->bRequest = USB_CDC_SEND_ENCAPSULATED_COMMAND; + pipe = usb_sndctrlpipe(serial->parent->usb, 0); + } + /* syslog */ + D2("%s command (%02x) len: %d, port: %d", + type == USB_CDC_GET_ENCAPSULATED_RESPONSE ? "Read" : "Write", + ctrl_req->bRequestType, ctrl_req->wLength, port); + + /* Load ctrl urb */ + ctrl_urb->transfer_flags = 0; + usb_fill_control_urb(ctrl_urb, + serial->parent->usb, + pipe, + (u8 *) ctrl_req, + ctrl_urb_data, size, ctrl_callback, serial); + /* Send it on merry way */ + result = usb_submit_urb(ctrl_urb, GFP_ATOMIC); + if (result) { + dev_err(&ctrl_urb->dev->dev, + "%s failed submit ctrl_urb %d type %d", __func__, + result, type); + return result; + } + + /* done */ + return size; +} + +/* called by intr_callback when read occurs */ +static int hso_mux_serial_read(struct hso_serial *serial) +{ + if (!serial) + return -EINVAL; + + /* clean data */ + memset(serial->rx_data[0], 0, CTRL_URB_RX_SIZE); + /* make the request */ + + if (serial->num_rx_urbs != 1) { + dev_err(&serial->parent->interface->dev, + "ERROR: mux'd reads with multiple buffers " + "not possible\n"); + return 0; + } + return mux_device_request(serial, + USB_CDC_GET_ENCAPSULATED_RESPONSE, + serial->parent->port_spec & HSO_PORT_MASK, + serial->rx_urb[0], + &serial->ctrl_req_rx, + serial->rx_data[0], serial->rx_data_length); +} + +/* used for muxed serial port callback (muxed serial read) */ +static void intr_callback(struct urb *urb) +{ + struct hso_shared_int *shared_int = urb->context; + struct hso_serial *serial; + unsigned char *port_req; + int status = urb->status; + int i; + + usb_mark_last_busy(urb->dev); + + /* sanity check */ + if (!shared_int) + return; + + /* status check */ + if (status) { + log_usb_status(status, __func__); + return; + } + D4("\n--- Got intr callback 0x%02X ---", status); + + /* what request? */ + port_req = urb->transfer_buffer; + D4(" port_req = 0x%.2X\n", *port_req); + /* loop over all muxed ports to find the one sending this */ + for (i = 0; i < 8; i++) { + /* max 8 channels on MUX */ + if (*port_req & (1 << i)) { + serial = get_serial_by_shared_int_and_type(shared_int, + (1 << i)); + if (serial != NULL) { + D1("Pending read interrupt on port %d\n", i); + if (!test_and_set_bit(HSO_SERIAL_FLAG_RX_SENT, + &serial->flags)) { + /* Setup and send a ctrl req read on + * port i */ + hso_mux_serial_read(serial); + } else { + D1("Already pending a read on " + "port %d\n", i); + } + } + } + } + /* Resubmit interrupt urb */ + hso_mux_submit_intr_urb(shared_int, urb->dev, GFP_ATOMIC); +} + +/* called for writing to muxed serial port */ +static int hso_mux_serial_write_data(struct hso_serial *serial) +{ + if (NULL == serial) + return -EINVAL; + + return mux_device_request(serial, + USB_CDC_SEND_ENCAPSULATED_COMMAND, + serial->parent->port_spec & HSO_PORT_MASK, + serial->tx_urb, + &serial->ctrl_req_tx, + serial->tx_data, serial->tx_data_count); +} + +/* write callback for Diag and CS port */ +static void hso_std_serial_write_bulk_callback(struct urb *urb) +{ + struct hso_serial *serial = urb->context; + int status = urb->status; + + /* sanity check */ + if (!serial) { + D1("serial == NULL"); + return; + } + + spin_lock(&serial->serial_lock); + serial->tx_urb_used = 0; + spin_unlock(&serial->serial_lock); + if (status) { + log_usb_status(status, __func__); + return; + } + hso_put_activity(serial->parent); + tty_wakeup(serial->tty); + hso_kick_transmit(serial); + + D1(" "); + return; +} + +/* called for writing diag or CS serial port */ +static int hso_std_serial_write_data(struct hso_serial *serial) +{ + int count = serial->tx_data_count; + int result; + + usb_fill_bulk_urb(serial->tx_urb, + serial->parent->usb, + usb_sndbulkpipe(serial->parent->usb, + serial->out_endp-> + bEndpointAddress & 0x7F), + serial->tx_data, serial->tx_data_count, + hso_std_serial_write_bulk_callback, serial); + + result = usb_submit_urb(serial->tx_urb, GFP_ATOMIC); + if (result) { + dev_warn(&serial->parent->usb->dev, + "Failed to submit urb - res %d\n", result); + return result; + } + + return count; +} + +/* callback after read or write on muxed serial port */ +static void ctrl_callback(struct urb *urb) +{ + struct hso_serial *serial = urb->context; + struct usb_ctrlrequest *req; + int status = urb->status; + + /* sanity check */ + if (!serial) + return; + + spin_lock(&serial->serial_lock); + serial->tx_urb_used = 0; + spin_unlock(&serial->serial_lock); + if (status) { + log_usb_status(status, __func__); + return; + } + + /* what request? */ + req = (struct usb_ctrlrequest *)(urb->setup_packet); + D4("\n--- Got muxed ctrl callback 0x%02X ---", status); + D4("Actual length of urb = %d\n", urb->actual_length); + DUMP1(urb->transfer_buffer, urb->actual_length); + + if (req->bRequestType == + (USB_DIR_IN | USB_TYPE_OPTION_VENDOR | USB_RECIP_INTERFACE)) { + /* response to a read command */ + if (serial->open_count > 0) { + /* handle RX data the normal way */ + put_rxbuf_data(urb, serial); + } + + /* Re issue a read as long as we receive data. */ + if (urb->actual_length != 0) + hso_mux_serial_read(serial); + else + clear_bit(HSO_SERIAL_FLAG_RX_SENT, &serial->flags); + } else { + hso_put_activity(serial->parent); + tty_wakeup(serial->tty); + /* response to a write command */ + hso_kick_transmit(serial); + } +} + +/* handle RX data for serial port */ +static void put_rxbuf_data(struct urb *urb, struct hso_serial *serial) +{ + struct tty_struct *tty = serial->tty; + + /* Sanity check */ + if (urb == NULL || serial == NULL) { + D1("serial = NULL"); + return; + } + + /* Push data to tty */ + if (tty && urb->actual_length) { + D1("data to push to tty"); + tty_insert_flip_string(tty, urb->transfer_buffer, + urb->actual_length); + tty_flip_buffer_push(tty); + } +} + +/* read callback for Diag and CS port */ +static void hso_std_serial_read_bulk_callback(struct urb *urb) +{ + struct hso_serial *serial = urb->context; + int result; + int status = urb->status; + + /* sanity check */ + if (!serial) { + D1("serial == NULL"); + return; + } else if (status) { + log_usb_status(status, __func__); + return; + } + + D4("\n--- Got serial_read_bulk callback %02x ---", status); + D1("Actual length = %d\n", urb->actual_length); + DUMP1(urb->transfer_buffer, urb->actual_length); + + /* Anyone listening? */ + if (serial->open_count == 0) + return; + + if (status == 0) { + if (serial->parent->port_spec & HSO_INFO_CRC_BUG) { + u32 rest; + u8 crc_check[4] = { 0xDE, 0xAD, 0xBE, 0xEF }; + rest = + urb->actual_length % + serial->in_endp->wMaxPacketSize; + if (((rest == 5) || (rest == 6)) + && !memcmp(((u8 *) urb->transfer_buffer) + + urb->actual_length - 4, crc_check, 4)) { + urb->actual_length -= 4; + } + } + /* Valid data, handle RX data */ + put_rxbuf_data(urb, serial); + } else if (status == -ENOENT || status == -ECONNRESET) { + /* Unlinked - check for throttled port. */ + D2("Port %d, successfully unlinked urb", serial->minor); + } else { + D2("Port %d, status = %d for read urb", serial->minor, status); + return; + } + + usb_mark_last_busy(urb->dev); + + /* We are done with this URB, resubmit it. Prep the USB to wait for + * another frame */ + usb_fill_bulk_urb(urb, serial->parent->usb, + usb_rcvbulkpipe(serial->parent->usb, + serial->in_endp-> + bEndpointAddress & 0x7F), + urb->transfer_buffer, serial->rx_data_length, + hso_std_serial_read_bulk_callback, serial); + /* Give this to the USB subsystem so it can tell us when more data + * arrives. */ + result = usb_submit_urb(urb, GFP_ATOMIC); + if (result) { + dev_err(&urb->dev->dev, "%s failed submit serial rx_urb %d", + __func__, result); + } +} + +/* Base driver functions */ + +static void hso_log_port(struct hso_device *hso_dev) +{ + char *port_type; + char port_dev[20]; + + switch (hso_dev->port_spec & HSO_PORT_MASK) { + case HSO_PORT_CONTROL: + port_type = "Control"; + break; + case HSO_PORT_APP: + port_type = "Application"; + break; + case HSO_PORT_GPS: + port_type = "GPS"; + break; + case HSO_PORT_GPS_CONTROL: + port_type = "GPS control"; + break; + case HSO_PORT_APP2: + port_type = "Application2"; + break; + case HSO_PORT_PCSC: + port_type = "PCSC"; + break; + case HSO_PORT_DIAG: + port_type = "Diagnostic"; + break; + case HSO_PORT_DIAG2: + port_type = "Diagnostic2"; + break; + case HSO_PORT_MODEM: + port_type = "Modem"; + break; + case HSO_PORT_NETWORK: + port_type = "Network"; + break; + default: + port_type = "Unknown"; + break; + } + if ((hso_dev->port_spec & HSO_PORT_MASK) == HSO_PORT_NETWORK) { + sprintf(port_dev, "%s", dev2net(hso_dev)->net->name); + } else + sprintf(port_dev, "/dev/%s%d", tty_filename, + dev2ser(hso_dev)->minor); + + dev_dbg(&hso_dev->interface->dev, "HSO: Found %s port %s\n", + port_type, port_dev); +} + +static int hso_start_net_device(struct hso_device *hso_dev) +{ + int i, result = 0; + struct hso_net *hso_net = dev2net(hso_dev); + + if (!hso_net) + return -ENODEV; + + /* send URBs for all read buffers */ + for (i = 0; i < MUX_BULK_RX_BUF_COUNT; i++) { + + /* Prep a receive URB */ + usb_fill_bulk_urb(hso_net->mux_bulk_rx_urb_pool[i], + hso_dev->usb, + usb_rcvbulkpipe(hso_dev->usb, + hso_net->in_endp-> + bEndpointAddress & 0x7F), + hso_net->mux_bulk_rx_buf_pool[i], + MUX_BULK_RX_BUF_SIZE, read_bulk_callback, + hso_net); + + /* Put it out there so the device can send us stuff */ + result = usb_submit_urb(hso_net->mux_bulk_rx_urb_pool[i], + GFP_NOIO); + if (result) + dev_warn(&hso_dev->usb->dev, + "%s failed mux_bulk_rx_urb[%d] %d\n", __func__, + i, result); + } + + return result; +} + +static int hso_stop_net_device(struct hso_device *hso_dev) +{ + int i; + struct hso_net *hso_net = dev2net(hso_dev); + + if (!hso_net) + return -ENODEV; + + for (i = 0; i < MUX_BULK_RX_BUF_COUNT; i++) { + if (hso_net->mux_bulk_rx_urb_pool[i]) + usb_kill_urb(hso_net->mux_bulk_rx_urb_pool[i]); + + } + if (hso_net->mux_bulk_tx_urb) + usb_kill_urb(hso_net->mux_bulk_tx_urb); + + return 0; +} + +static int hso_start_serial_device(struct hso_device *hso_dev, gfp_t flags) +{ + int i, result = 0; + struct hso_serial *serial = dev2ser(hso_dev); + + if (!serial) + return -ENODEV; + + /* If it is not the MUX port fill in and submit a bulk urb (already + * allocated in hso_serial_start) */ + if (!(serial->parent->port_spec & HSO_INTF_MUX)) { + for (i = 0; i < serial->num_rx_urbs; i++) { + usb_fill_bulk_urb(serial->rx_urb[i], + serial->parent->usb, + usb_rcvbulkpipe(serial->parent->usb, + serial->in_endp-> + bEndpointAddress & + 0x7F), + serial->rx_data[i], + serial->rx_data_length, + hso_std_serial_read_bulk_callback, + serial); + result = usb_submit_urb(serial->rx_urb[i], flags); + if (result) { + dev_warn(&serial->parent->usb->dev, + "Failed to submit urb - res %d\n", + result); + break; + } + } + } else { + mutex_lock(&serial->shared_int->shared_int_lock); + if (!serial->shared_int->use_count) { + result = + hso_mux_submit_intr_urb(serial->shared_int, + hso_dev->usb, flags); + } + serial->shared_int->use_count++; + mutex_unlock(&serial->shared_int->shared_int_lock); + } + + return result; +} + +static int hso_stop_serial_device(struct hso_device *hso_dev) +{ + int i; + struct hso_serial *serial = dev2ser(hso_dev); + + if (!serial) + return -ENODEV; + + for (i = 0; i < serial->num_rx_urbs; i++) { + if (serial->rx_urb[i]) + usb_kill_urb(serial->rx_urb[i]); + } + + if (serial->tx_urb) + usb_kill_urb(serial->tx_urb); + + if (serial->shared_int) { + mutex_lock(&serial->shared_int->shared_int_lock); + if (serial->shared_int->use_count && + (--serial->shared_int->use_count == 0)) { + struct urb *urb; + + urb = serial->shared_int->shared_intr_urb; + if (urb) + usb_kill_urb(urb); + } + mutex_unlock(&serial->shared_int->shared_int_lock); + } + + return 0; +} + +static void hso_serial_common_free(struct hso_serial *serial) +{ + int i; + + if (serial->parent->dev) + device_remove_file(serial->parent->dev, &dev_attr_hsotype); + + tty_unregister_device(tty_drv, serial->minor); + + for (i = 0; i < serial->num_rx_urbs; i++) { + /* unlink and free RX URB */ + usb_free_urb(serial->rx_urb[i]); + /* free the RX buffer */ + kfree(serial->rx_data[i]); + } + + /* unlink and free TX URB */ + usb_free_urb(serial->tx_urb); + kfree(serial->tx_data); +} + +static int hso_serial_common_create(struct hso_serial *serial, int num_urbs, + int rx_size, int tx_size) +{ + struct device *dev; + int minor; + int i; + + minor = get_free_serial_index(); + if (minor < 0) + goto exit; + + /* register our minor number */ + serial->parent->dev = tty_register_device(tty_drv, minor, + &serial->parent->interface->dev); + dev = serial->parent->dev; + dev->driver_data = serial->parent; + i = device_create_file(dev, &dev_attr_hsotype); + + /* fill in specific data for later use */ + serial->minor = minor; + serial->magic = HSO_SERIAL_MAGIC; + spin_lock_init(&serial->serial_lock); + serial->num_rx_urbs = num_urbs; + + /* RX, allocate urb and initialize */ + + /* prepare our RX buffer */ + serial->rx_data_length = rx_size; + for (i = 0; i < serial->num_rx_urbs; i++) { + serial->rx_urb[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!serial->rx_urb[i]) { + dev_err(dev, "Could not allocate urb?\n"); + goto exit; + } + serial->rx_urb[i]->transfer_buffer = NULL; + serial->rx_urb[i]->transfer_buffer_length = 0; + serial->rx_data[i] = kzalloc(serial->rx_data_length, + GFP_KERNEL); + if (!serial->rx_data[i]) { + dev_err(dev, "%s - Out of memory\n", __func__); + goto exit; + } + } + + /* TX, allocate urb and initialize */ + serial->tx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!serial->tx_urb) { + dev_err(dev, "Could not allocate urb?\n"); + goto exit; + } + serial->tx_urb->transfer_buffer = NULL; + serial->tx_urb->transfer_buffer_length = 0; + /* prepare our TX buffer */ + serial->tx_data_count = 0; + serial->tx_buffer_count = 0; + serial->tx_data_length = tx_size; + serial->tx_data = kzalloc(serial->tx_data_length, GFP_KERNEL); + if (!serial->tx_data) { + dev_err(dev, "%s - Out of memory", __func__); + goto exit; + } + serial->tx_buffer = kzalloc(serial->tx_data_length, GFP_KERNEL); + if (!serial->tx_buffer) { + dev_err(dev, "%s - Out of memory", __func__); + goto exit; + } + + return 0; +exit: + hso_serial_common_free(serial); + return -1; +} + +/* Frees a general hso device */ +static void hso_free_device(struct hso_device *hso_dev) +{ + kfree(hso_dev); +} + +/* Creates a general hso device */ +static struct hso_device *hso_create_device(struct usb_interface *intf, + int port_spec) +{ + struct hso_device *hso_dev; + + hso_dev = kzalloc(sizeof(*hso_dev), GFP_ATOMIC); + if (!hso_dev) + return NULL; + + hso_dev->port_spec = port_spec; + hso_dev->usb = interface_to_usbdev(intf); + hso_dev->interface = intf; + kref_init(&hso_dev->ref); + mutex_init(&hso_dev->mutex); + + INIT_WORK(&hso_dev->async_get_intf, async_get_intf); + INIT_WORK(&hso_dev->async_put_intf, async_put_intf); + + return hso_dev; +} + +/* Removes a network device in the network device table */ +static int remove_net_device(struct hso_device *hso_dev) +{ + int i; + + for (i = 0; i < HSO_MAX_NET_DEVICES; i++) { + if (network_table[i] == hso_dev) { + network_table[i] = NULL; + break; + } + } + if (i == HSO_MAX_NET_DEVICES) + return -1; + return 0; +} + +/* Frees our network device */ +static void hso_free_net_device(struct hso_device *hso_dev) +{ + int i; + struct hso_net *hso_net = dev2net(hso_dev); + + if (!hso_net) + return; + + /* start freeing */ + for (i = 0; i < MUX_BULK_RX_BUF_COUNT; i++) { + usb_free_urb(hso_net->mux_bulk_rx_urb_pool[i]); + kfree(hso_net->mux_bulk_rx_buf_pool[i]); + } + usb_free_urb(hso_net->mux_bulk_tx_urb); + kfree(hso_net->mux_bulk_tx_buf); + + remove_net_device(hso_net->parent); + + if (hso_net->net) { + unregister_netdev(hso_net->net); + free_netdev(hso_net->net); + } + + hso_free_device(hso_dev); +} + +/* initialize the network interface */ +static void hso_net_init(struct net_device *net) +{ + struct hso_net *hso_net = netdev_priv(net); + + D1("sizeof hso_net is %d", (int)sizeof(*hso_net)); + + /* fill in the other fields */ + net->open = hso_net_open; + net->stop = hso_net_close; + net->hard_start_xmit = hso_net_start_xmit; + net->tx_timeout = hso_net_tx_timeout; + net->watchdog_timeo = HSO_NET_TX_TIMEOUT; + net->flags = IFF_POINTOPOINT | IFF_NOARP | IFF_MULTICAST; + net->type = ARPHRD_NONE; + net->mtu = DEFAULT_MTU - 14; + net->tx_queue_len = 10; + SET_ETHTOOL_OPS(net, &ops); + + /* and initialize the semaphore */ + spin_lock_init(&hso_net->net_lock); +} + +/* Adds a network device in the network device table */ +static int add_net_device(struct hso_device *hso_dev) +{ + int i; + + for (i = 0; i < HSO_MAX_NET_DEVICES; i++) { + if (network_table[i] == NULL) { + network_table[i] = hso_dev; + break; + } + } + if (i == HSO_MAX_NET_DEVICES) + return -1; + return 0; +} + +static int hso_radio_toggle(void *data, enum rfkill_state state) +{ + struct hso_device *hso_dev = data; + int enabled = (state == RFKILL_STATE_ON); + int rv; + + mutex_lock(&hso_dev->mutex); + if (hso_dev->usb_gone) + rv = 0; + else + rv = usb_control_msg(hso_dev->usb, usb_rcvctrlpipe(hso_dev->usb, 0), + enabled ? 0x82 : 0x81, 0x40, 0, 0, NULL, 0, + USB_CTRL_SET_TIMEOUT); + mutex_unlock(&hso_dev->mutex); + return rv; +} + +/* Creates and sets up everything for rfkill */ +static void hso_create_rfkill(struct hso_device *hso_dev, + struct usb_interface *interface) +{ + struct hso_net *hso_net = dev2net(hso_dev); + struct device *dev = hso_dev->dev; + char *rfkn; + + hso_net->rfkill = rfkill_allocate(&interface_to_usbdev(interface)->dev, + RFKILL_TYPE_WLAN); + if (!hso_net->rfkill) { + dev_err(dev, "%s - Out of memory", __func__); + return; + } + rfkn = kzalloc(20, GFP_KERNEL); + if (!rfkn) { + rfkill_free(hso_net->rfkill); + dev_err(dev, "%s - Out of memory", __func__); + return; + } + snprintf(rfkn, 20, "hso-%d", + interface->altsetting->desc.bInterfaceNumber); + hso_net->rfkill->name = rfkn; + hso_net->rfkill->state = RFKILL_STATE_ON; + hso_net->rfkill->data = hso_dev; + hso_net->rfkill->toggle_radio = hso_radio_toggle; + if (rfkill_register(hso_net->rfkill) < 0) { + kfree(rfkn); + hso_net->rfkill->name = NULL; + rfkill_free(hso_net->rfkill); + dev_err(dev, "%s - Failed to register rfkill", __func__); + return; + } +} + +/* Creates our network device */ +static struct hso_device *hso_create_net_device(struct usb_interface *interface) +{ + int result, i; + struct net_device *net; + struct hso_net *hso_net; + struct hso_device *hso_dev; + + hso_dev = hso_create_device(interface, HSO_INTF_MUX | HSO_PORT_NETWORK); + if (!hso_dev) + return NULL; + + /* allocate our network device, then we can put in our private data */ + /* call hso_net_init to do the basic initialization */ + net = alloc_netdev(sizeof(struct hso_net), "hso%d", hso_net_init); + if (!net) { + dev_err(&interface->dev, "Unable to create ethernet device\n"); + goto exit; + } + + hso_net = netdev_priv(net); + + hso_dev->port_data.dev_net = hso_net; + hso_net->net = net; + hso_net->parent = hso_dev; + + hso_net->in_endp = hso_get_ep(interface, USB_ENDPOINT_XFER_BULK, + USB_DIR_IN); + if (!hso_net->in_endp) { + dev_err(&interface->dev, "Can't find BULK IN endpoint\n"); + goto exit; + } + hso_net->out_endp = hso_get_ep(interface, USB_ENDPOINT_XFER_BULK, + USB_DIR_OUT); + if (!hso_net->out_endp) { + dev_err(&interface->dev, "Can't find BULK OUT endpoint\n"); + goto exit; + } + SET_NETDEV_DEV(net, &interface->dev); + + /* registering our net device */ + result = register_netdev(net); + if (result) { + dev_err(&interface->dev, "Failed to register device\n"); + goto exit; + } + + /* start allocating */ + for (i = 0; i < MUX_BULK_RX_BUF_COUNT; i++) { + hso_net->mux_bulk_rx_urb_pool[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!hso_net->mux_bulk_rx_urb_pool[i]) { + dev_err(&interface->dev, "Could not allocate rx urb\n"); + goto exit; + } + hso_net->mux_bulk_rx_buf_pool[i] = kzalloc(MUX_BULK_RX_BUF_SIZE, + GFP_KERNEL); + if (!hso_net->mux_bulk_rx_buf_pool[i]) { + dev_err(&interface->dev, "Could not allocate rx buf\n"); + goto exit; + } + } + hso_net->mux_bulk_tx_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!hso_net->mux_bulk_tx_urb) { + dev_err(&interface->dev, "Could not allocate tx urb\n"); + goto exit; + } + hso_net->mux_bulk_tx_buf = kzalloc(MUX_BULK_TX_BUF_SIZE, GFP_KERNEL); + if (!hso_net->mux_bulk_tx_buf) { + dev_err(&interface->dev, "Could not allocate tx buf\n"); + goto exit; + } + + add_net_device(hso_dev); + + hso_log_port(hso_dev); + + hso_create_rfkill(hso_dev, interface); + + return hso_dev; +exit: + hso_free_net_device(hso_dev); + return NULL; +} + +/* Frees an AT channel ( goes for both mux and non-mux ) */ +static void hso_free_serial_device(struct hso_device *hso_dev) +{ + struct hso_serial *serial = dev2ser(hso_dev); + + if (!serial) + return; + set_serial_by_index(serial->minor, NULL); + + hso_serial_common_free(serial); + + if (serial->shared_int) { + mutex_lock(&serial->shared_int->shared_int_lock); + if (--serial->shared_int->ref_count == 0) + hso_free_shared_int(serial->shared_int); + else + mutex_unlock(&serial->shared_int->shared_int_lock); + } + kfree(serial); + hso_free_device(hso_dev); +} + +/* Creates a bulk AT channel */ +static struct hso_device *hso_create_bulk_serial_device( + struct usb_interface *interface, int port) +{ + struct hso_device *hso_dev; + struct hso_serial *serial; + int num_urbs; + + hso_dev = hso_create_device(interface, port); + if (!hso_dev) + return NULL; + + serial = kzalloc(sizeof(*serial), GFP_KERNEL); + if (!serial) + goto exit; + + serial->parent = hso_dev; + hso_dev->port_data.dev_serial = serial; + + if (port & HSO_PORT_MODEM) + num_urbs = 2; + else + num_urbs = 1; + + if (hso_serial_common_create(serial, num_urbs, BULK_URB_RX_SIZE, + BULK_URB_TX_SIZE)) + goto exit; + + serial->in_endp = hso_get_ep(interface, USB_ENDPOINT_XFER_BULK, + USB_DIR_IN); + if (!serial->in_endp) { + dev_err(&interface->dev, "Failed to find BULK IN ep\n"); + goto exit; + } + + if (! + (serial->out_endp = + hso_get_ep(interface, USB_ENDPOINT_XFER_BULK, USB_DIR_OUT))) { + dev_err(&interface->dev, "Failed to find BULK IN ep\n"); + goto exit; + } + + serial->write_data = hso_std_serial_write_data; + + /* and record this serial */ + set_serial_by_index(serial->minor, serial); + + /* setup the proc dirs and files if needed */ + hso_log_port(hso_dev); + + /* done, return it */ + return hso_dev; +exit: + if (hso_dev && serial) + hso_serial_common_free(serial); + kfree(serial); + hso_free_device(hso_dev); + return NULL; +} + +/* Creates a multiplexed AT channel */ +static +struct hso_device *hso_create_mux_serial_device(struct usb_interface *interface, + int port, + struct hso_shared_int *mux) +{ + struct hso_device *hso_dev; + struct hso_serial *serial; + int port_spec; + + port_spec = HSO_INTF_MUX; + port_spec &= ~HSO_PORT_MASK; + + port_spec |= hso_mux_to_port(port); + if ((port_spec & HSO_PORT_MASK) == HSO_PORT_NO_PORT) + return NULL; + + hso_dev = hso_create_device(interface, port_spec); + if (!hso_dev) + return NULL; + + serial = kzalloc(sizeof(*serial), GFP_KERNEL); + if (!serial) + goto exit; + + hso_dev->port_data.dev_serial = serial; + serial->parent = hso_dev; + + if (hso_serial_common_create + (serial, 1, CTRL_URB_RX_SIZE, CTRL_URB_TX_SIZE)) + goto exit; + + serial->tx_data_length--; + serial->write_data = hso_mux_serial_write_data; + + serial->shared_int = mux; + mutex_lock(&serial->shared_int->shared_int_lock); + serial->shared_int->ref_count++; + mutex_unlock(&serial->shared_int->shared_int_lock); + + /* and record this serial */ + set_serial_by_index(serial->minor, serial); + + /* setup the proc dirs and files if needed */ + hso_log_port(hso_dev); + + /* done, return it */ + return hso_dev; + +exit: + if (serial) { + tty_unregister_device(tty_drv, serial->minor); + kfree(serial); + } + if (hso_dev) + hso_free_device(hso_dev); + return NULL; + +} + +static void hso_free_shared_int(struct hso_shared_int *mux) +{ + usb_free_urb(mux->shared_intr_urb); + kfree(mux->shared_intr_buf); + mutex_unlock(&mux->shared_int_lock); + kfree(mux); +} + +static +struct hso_shared_int *hso_create_shared_int(struct usb_interface *interface) +{ + struct hso_shared_int *mux = kzalloc(sizeof(*mux), GFP_KERNEL); + + if (!mux) + return NULL; + + mux->intr_endp = hso_get_ep(interface, USB_ENDPOINT_XFER_INT, + USB_DIR_IN); + if (!mux->intr_endp) { + dev_err(&interface->dev, "Can't find INT IN endpoint\n"); + goto exit; + } + + mux->shared_intr_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!mux->shared_intr_urb) { + dev_err(&interface->dev, "Could not allocate intr urb?"); + goto exit; + } + mux->shared_intr_buf = kzalloc(mux->intr_endp->wMaxPacketSize, + GFP_KERNEL); + if (!mux->shared_intr_buf) { + dev_err(&interface->dev, "Could not allocate intr buf?"); + goto exit; + } + + mutex_init(&mux->shared_int_lock); + + return mux; + +exit: + kfree(mux->shared_intr_buf); + usb_free_urb(mux->shared_intr_urb); + kfree(mux); + return NULL; +} + +/* Gets the port spec for a certain interface */ +static int hso_get_config_data(struct usb_interface *interface) +{ + struct usb_device *usbdev = interface_to_usbdev(interface); + u8 config_data[17]; + u32 if_num = interface->altsetting->desc.bInterfaceNumber; + s32 result; + + if (usb_control_msg(usbdev, usb_rcvctrlpipe(usbdev, 0), + 0x86, 0xC0, 0, 0, config_data, 17, + USB_CTRL_SET_TIMEOUT) != 0x11) { + return -EIO; + } + + switch (config_data[if_num]) { + case 0x0: + result = 0; + break; + case 0x1: + result = HSO_PORT_DIAG; + break; + case 0x2: + result = HSO_PORT_GPS; + break; + case 0x3: + result = HSO_PORT_GPS_CONTROL; + break; + case 0x4: + result = HSO_PORT_APP; + break; + case 0x5: + result = HSO_PORT_APP2; + break; + case 0x6: + result = HSO_PORT_CONTROL; + break; + case 0x7: + result = HSO_PORT_NETWORK; + break; + case 0x8: + result = HSO_PORT_MODEM; + break; + case 0x9: + result = HSO_PORT_MSD; + break; + case 0xa: + result = HSO_PORT_PCSC; + break; + case 0xb: + result = HSO_PORT_VOICE; + break; + default: + result = 0; + } + + if (result) + result |= HSO_INTF_BULK; + + if (config_data[16] & 0x1) + result |= HSO_INFO_CRC_BUG; + + return result; +} + +/* called once for each interface upon device insertion */ +static int hso_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + int mux, i, if_num, port_spec; + unsigned char port_mask; + struct hso_device *hso_dev = NULL; + struct hso_shared_int *shared_int; + struct hso_device *tmp_dev = NULL; + + if_num = interface->altsetting->desc.bInterfaceNumber; + + /* Get the interface/port specification from either driver_info or from + * the device itself */ + if (id->driver_info) + port_spec = ((u32 *)(id->driver_info))[if_num]; + else + port_spec = hso_get_config_data(interface); + + if (interface->cur_altsetting->desc.bInterfaceClass != 0xFF) { + dev_err(&interface->dev, "Not our interface\n"); + return -ENODEV; + } + /* Check if we need to switch to alt interfaces prior to port + * configuration */ + if (interface->num_altsetting > 1) + usb_set_interface(interface_to_usbdev(interface), if_num, 1); + interface->needs_remote_wakeup = 1; + + /* Allocate new hso device(s) */ + switch (port_spec & HSO_INTF_MASK) { + case HSO_INTF_MUX: + if ((port_spec & HSO_PORT_MASK) == HSO_PORT_NETWORK) { + /* Create the network device */ + if (!disable_net) { + hso_dev = hso_create_net_device(interface); + if (!hso_dev) + goto exit; + tmp_dev = hso_dev; + } + } + + if (hso_get_mux_ports(interface, &port_mask)) + /* TODO: de-allocate everything */ + goto exit; + + shared_int = hso_create_shared_int(interface); + if (!shared_int) + goto exit; + + for (i = 1, mux = 0; i < 0x100; i = i << 1, mux++) { + if (port_mask & i) { + hso_dev = hso_create_mux_serial_device( + interface, i, shared_int); + if (!hso_dev) + goto exit; + } + } + + if (tmp_dev) + hso_dev = tmp_dev; + break; + + case HSO_INTF_BULK: + /* It's a regular bulk interface */ + if (((port_spec & HSO_PORT_MASK) == HSO_PORT_NETWORK) + && !disable_net) + hso_dev = hso_create_net_device(interface); + else + hso_dev = + hso_create_bulk_serial_device(interface, port_spec); + if (!hso_dev) + goto exit; + break; + default: + goto exit; + } + + usb_driver_claim_interface(&hso_driver, interface, hso_dev); + + /* save our data pointer in this device */ + usb_set_intfdata(interface, hso_dev); + + /* done */ + return 0; +exit: + hso_free_interface(interface); + return -ENODEV; +} + +/* device removed, cleaning up */ +static void hso_disconnect(struct usb_interface *interface) +{ + hso_free_interface(interface); + + /* remove reference of our private data */ + usb_set_intfdata(interface, NULL); + + usb_driver_release_interface(&hso_driver, interface); +} + +static void async_get_intf(struct work_struct *data) +{ + struct hso_device *hso_dev = + container_of(data, struct hso_device, async_get_intf); + usb_autopm_get_interface(hso_dev->interface); +} + +static void async_put_intf(struct work_struct *data) +{ + struct hso_device *hso_dev = + container_of(data, struct hso_device, async_put_intf); + usb_autopm_put_interface(hso_dev->interface); +} + +static int hso_get_activity(struct hso_device *hso_dev) +{ + if (hso_dev->usb->state == USB_STATE_SUSPENDED) { + if (!hso_dev->is_active) { + hso_dev->is_active = 1; + schedule_work(&hso_dev->async_get_intf); + } + } + + if (hso_dev->usb->state != USB_STATE_CONFIGURED) + return -EAGAIN; + + usb_mark_last_busy(hso_dev->usb); + + return 0; +} + +static int hso_put_activity(struct hso_device *hso_dev) +{ + if (hso_dev->usb->state != USB_STATE_SUSPENDED) { + if (hso_dev->is_active) { + hso_dev->is_active = 0; + schedule_work(&hso_dev->async_put_intf); + return -EAGAIN; + } + } + hso_dev->is_active = 0; + return 0; +} + +/* called by kernel when we need to suspend device */ +static int hso_suspend(struct usb_interface *iface, pm_message_t message) +{ + int i, result; + + /* Stop all serial ports */ + for (i = 0; i < HSO_SERIAL_TTY_MINORS; i++) { + if (serial_table[i] && (serial_table[i]->interface == iface)) { + result = hso_stop_serial_device(serial_table[i]); + if (result) + goto out; + } + } + + /* Stop all network ports */ + for (i = 0; i < HSO_MAX_NET_DEVICES; i++) { + if (network_table[i] && + (network_table[i]->interface == iface)) { + result = hso_stop_net_device(network_table[i]); + if (result) + goto out; + } + } + +out: + return 0; +} + +/* called by kernel when we need to resume device */ +static int hso_resume(struct usb_interface *iface) +{ + int i, result = 0; + struct hso_net *hso_net; + + /* Start all serial ports */ + for (i = 0; i < HSO_SERIAL_TTY_MINORS; i++) { + if (serial_table[i] && (serial_table[i]->interface == iface)) { + if (dev2ser(serial_table[i])->open_count) { + result = + hso_start_serial_device(serial_table[i], GFP_NOIO); + hso_kick_transmit(dev2ser(serial_table[i])); + if (result) + goto out; + } + } + } + + /* Start all network ports */ + for (i = 0; i < HSO_MAX_NET_DEVICES; i++) { + if (network_table[i] && + (network_table[i]->interface == iface)) { + hso_net = dev2net(network_table[i]); + /* First transmit any lingering data, then restart the + * device. */ + if (hso_net->skb_tx_buf) { + dev_dbg(&iface->dev, + "Transmitting lingering data\n"); + hso_net_start_xmit(hso_net->skb_tx_buf, + hso_net->net); + } + result = hso_start_net_device(network_table[i]); + if (result) + goto out; + } + } + +out: + return result; +} + +static void hso_serial_ref_free(struct kref *ref) +{ + struct hso_device *hso_dev = container_of(ref, struct hso_device, ref); + + hso_free_serial_device(hso_dev); +} + +static void hso_free_interface(struct usb_interface *interface) +{ + struct hso_serial *hso_dev; + int i; + + for (i = 0; i < HSO_SERIAL_TTY_MINORS; i++) { + if (serial_table[i] + && (serial_table[i]->interface == interface)) { + hso_dev = dev2ser(serial_table[i]); + if (hso_dev->tty) + tty_hangup(hso_dev->tty); + mutex_lock(&hso_dev->parent->mutex); + hso_dev->parent->usb_gone = 1; + mutex_unlock(&hso_dev->parent->mutex); + kref_put(&serial_table[i]->ref, hso_serial_ref_free); + } + } + + for (i = 0; i < HSO_MAX_NET_DEVICES; i++) { + if (network_table[i] + && (network_table[i]->interface == interface)) { + struct rfkill *rfk = dev2net(network_table[i])->rfkill; + /* hso_stop_net_device doesn't stop the net queue since + * traffic needs to start it again when suspended */ + netif_stop_queue(dev2net(network_table[i])->net); + hso_stop_net_device(network_table[i]); + cancel_work_sync(&network_table[i]->async_put_intf); + cancel_work_sync(&network_table[i]->async_get_intf); + if(rfk) + rfkill_unregister(rfk); + hso_free_net_device(network_table[i]); + } + } +} + +/* Helper functions */ + +/* Get the endpoint ! */ +static struct usb_endpoint_descriptor *hso_get_ep(struct usb_interface *intf, + int type, int dir) +{ + int i; + struct usb_host_interface *iface = intf->cur_altsetting; + struct usb_endpoint_descriptor *endp; + + for (i = 0; i < iface->desc.bNumEndpoints; i++) { + endp = &iface->endpoint[i].desc; + if (((endp->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == dir) && + ((endp->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == type)) + return endp; + } + + return NULL; +} + +/* Get the byte that describes which ports are enabled */ +static int hso_get_mux_ports(struct usb_interface *intf, unsigned char *ports) +{ + int i; + struct usb_host_interface *iface = intf->cur_altsetting; + + if (iface->extralen == 3) { + *ports = iface->extra[2]; + return 0; + } + + for (i = 0; i < iface->desc.bNumEndpoints; i++) { + if (iface->endpoint[i].extralen == 3) { + *ports = iface->endpoint[i].extra[2]; + return 0; + } + } + + return -1; +} + +/* interrupt urb needs to be submitted, used for serial read of muxed port */ +static int hso_mux_submit_intr_urb(struct hso_shared_int *shared_int, + struct usb_device *usb, gfp_t gfp) +{ + int result; + + usb_fill_int_urb(shared_int->shared_intr_urb, usb, + usb_rcvintpipe(usb, + shared_int->intr_endp->bEndpointAddress & 0x7F), + shared_int->shared_intr_buf, + shared_int->intr_endp->wMaxPacketSize, + intr_callback, shared_int, + shared_int->intr_endp->bInterval); + + result = usb_submit_urb(shared_int->shared_intr_urb, gfp); + if (result) + dev_warn(&usb->dev, "%s failed mux_intr_urb %d", __func__, + result); + + return result; +} + +/* operations setup of the serial interface */ +static struct tty_operations hso_serial_ops = { + .open = hso_serial_open, + .close = hso_serial_close, + .write = hso_serial_write, + .write_room = hso_serial_write_room, + .set_termios = hso_serial_set_termios, + .chars_in_buffer = hso_serial_chars_in_buffer, + .tiocmget = hso_serial_tiocmget, + .tiocmset = hso_serial_tiocmset, +}; + +static struct usb_driver hso_driver = { + .name = driver_name, + .probe = hso_probe, + .disconnect = hso_disconnect, + .id_table = hso_ids, + .suspend = hso_suspend, + .resume = hso_resume, + .supports_autosuspend = 1, +}; + +static int __init hso_init(void) +{ + int i; + int result; + + /* put it in the log */ + printk(KERN_INFO "hso: %s\n", version); + + /* Initialise the serial table semaphore and table */ + spin_lock_init(&serial_table_lock); + for (i = 0; i < HSO_SERIAL_TTY_MINORS; i++) + serial_table[i] = NULL; + + /* allocate our driver using the proper amount of supported minors */ + tty_drv = alloc_tty_driver(HSO_SERIAL_TTY_MINORS); + if (!tty_drv) + return -ENOMEM; + + /* fill in all needed values */ + tty_drv->magic = TTY_DRIVER_MAGIC; + tty_drv->owner = THIS_MODULE; + tty_drv->driver_name = driver_name; + tty_drv->name = tty_filename; + + /* if major number is provided as parameter, use that one */ + if (tty_major) + tty_drv->major = tty_major; + + tty_drv->minor_start = 0; + tty_drv->num = HSO_SERIAL_TTY_MINORS; + tty_drv->type = TTY_DRIVER_TYPE_SERIAL; + tty_drv->subtype = SERIAL_TYPE_NORMAL; + tty_drv->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; + tty_drv->init_termios = tty_std_termios; + tty_drv->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL; + tty_drv->termios = hso_serial_termios; + tty_drv->termios_locked = hso_serial_termios_locked; + tty_set_operations(tty_drv, &hso_serial_ops); + + /* register the tty driver */ + result = tty_register_driver(tty_drv); + if (result) { + printk(KERN_ERR "%s - tty_register_driver failed(%d)\n", + __func__, result); + return result; + } + + /* register this module as an usb driver */ + result = usb_register(&hso_driver); + if (result) { + printk(KERN_ERR "Could not register hso driver? error: %d\n", + result); + /* cleanup serial interface */ + tty_unregister_driver(tty_drv); + return result; + } + + /* done */ + return 0; +} + +static void __exit hso_exit(void) +{ + printk(KERN_INFO "hso: unloaded\n"); + + tty_unregister_driver(tty_drv); + /* deregister the usb driver */ + usb_deregister(&hso_driver); +} + +/* Module definitions */ +module_init(hso_init); +module_exit(hso_exit); + +MODULE_AUTHOR(MOD_AUTHOR); +MODULE_DESCRIPTION(MOD_DESCRIPTION); +MODULE_LICENSE(MOD_LICENSE); +MODULE_INFO(Version, DRIVER_VERSION); + +/* change the debug level (eg: insmod hso.ko debug=0x04) */ +MODULE_PARM_DESC(debug, "Level of debug [0x01 | 0x02 | 0x04 | 0x08 | 0x10]"); +module_param(debug, int, S_IRUGO | S_IWUSR); + +/* set the major tty number (eg: insmod hso.ko tty_major=245) */ +MODULE_PARM_DESC(tty_major, "Set the major tty number"); +module_param(tty_major, int, S_IRUGO | S_IWUSR); + +/* disable network interface (eg: insmod hso.ko disable_net=1) */ +MODULE_PARM_DESC(disable_net, "Disable the network interface"); +module_param(disable_net, int, S_IRUGO | S_IWUSR); |