| Johan Hovold | 9059389 | 2010-08-19 00:13:48 +0200 | [diff] [blame] | 1 | /* | 
|  | 2 | * Atmel SAM Boot Assistant (SAM-BA) driver | 
|  | 3 | * | 
|  | 4 | * Copyright (C) 2010 Johan Hovold <jhovold@gmail.com> | 
|  | 5 | * | 
|  | 6 | *	This program is free software; you can redistribute it and/or | 
|  | 7 | *	modify it under the terms of the GNU General Public License version | 
|  | 8 | *	2 as published by the Free Software Foundation. | 
|  | 9 | */ | 
|  | 10 |  | 
|  | 11 | #include <linux/kernel.h> | 
|  | 12 | #include <linux/tty.h> | 
|  | 13 | #include <linux/module.h> | 
|  | 14 | #include <linux/moduleparam.h> | 
|  | 15 | #include <linux/usb.h> | 
|  | 16 | #include <linux/usb/serial.h> | 
|  | 17 |  | 
|  | 18 |  | 
|  | 19 | #define DRIVER_VERSION	"v1.0" | 
|  | 20 | #define DRIVER_AUTHOR	"Johan Hovold <jhovold@gmail.com>" | 
|  | 21 | #define DRIVER_DESC	"Atmel SAM Boot Assistant (SAM-BA) driver" | 
|  | 22 |  | 
|  | 23 | #define SAMBA_VENDOR_ID		0x3eb | 
|  | 24 | #define SAMBA_PRODUCT_ID	0x6124 | 
|  | 25 |  | 
|  | 26 |  | 
|  | 27 | static int debug; | 
|  | 28 |  | 
|  | 29 | static const struct usb_device_id id_table[] = { | 
|  | 30 | /* | 
|  | 31 | * NOTE: Only match the CDC Data interface. | 
|  | 32 | */ | 
|  | 33 | { USB_DEVICE_AND_INTERFACE_INFO(SAMBA_VENDOR_ID, SAMBA_PRODUCT_ID, | 
|  | 34 | USB_CLASS_CDC_DATA, 0, 0) }, | 
|  | 35 | { } | 
|  | 36 | }; | 
|  | 37 | MODULE_DEVICE_TABLE(usb, id_table); | 
|  | 38 |  | 
|  | 39 | static struct usb_driver samba_driver = { | 
|  | 40 | .name		= "sam-ba", | 
|  | 41 | .probe		= usb_serial_probe, | 
|  | 42 | .disconnect	= usb_serial_disconnect, | 
|  | 43 | .id_table	= id_table, | 
|  | 44 | .no_dynamic_id	= 1, | 
|  | 45 | }; | 
|  | 46 |  | 
|  | 47 |  | 
|  | 48 | /* | 
|  | 49 | * NOTE: The SAM-BA firmware cannot handle merged write requests so we cannot | 
|  | 50 | *       use the generic write implementation (which uses the port write fifo). | 
|  | 51 | */ | 
|  | 52 | static int samba_write(struct tty_struct *tty, struct usb_serial_port *port, | 
|  | 53 | const unsigned char *buf, int count) | 
|  | 54 | { | 
|  | 55 | struct urb *urb; | 
|  | 56 | unsigned long flags; | 
|  | 57 | int result; | 
|  | 58 | int i; | 
|  | 59 |  | 
|  | 60 | if (!count) | 
|  | 61 | return 0; | 
|  | 62 |  | 
|  | 63 | count = min_t(int, count, port->bulk_out_size); | 
|  | 64 |  | 
|  | 65 | spin_lock_irqsave(&port->lock, flags); | 
|  | 66 | if (!port->write_urbs_free) { | 
|  | 67 | spin_unlock_irqrestore(&port->lock, flags); | 
|  | 68 | return 0; | 
|  | 69 | } | 
|  | 70 | i = find_first_bit(&port->write_urbs_free, | 
|  | 71 | ARRAY_SIZE(port->write_urbs)); | 
|  | 72 | __clear_bit(i, &port->write_urbs_free); | 
|  | 73 | port->tx_bytes += count; | 
|  | 74 | spin_unlock_irqrestore(&port->lock, flags); | 
|  | 75 |  | 
|  | 76 | urb = port->write_urbs[i]; | 
|  | 77 | memcpy(urb->transfer_buffer, buf, count); | 
|  | 78 | urb->transfer_buffer_length = count; | 
|  | 79 | usb_serial_debug_data(debug, &port->dev, __func__, count, | 
|  | 80 | urb->transfer_buffer); | 
|  | 81 | result = usb_submit_urb(urb, GFP_ATOMIC); | 
|  | 82 | if (result) { | 
|  | 83 | dev_err(&port->dev, "%s - error submitting urb: %d\n", | 
|  | 84 | __func__, result); | 
|  | 85 | spin_lock_irqsave(&port->lock, flags); | 
|  | 86 | __set_bit(i, &port->write_urbs_free); | 
|  | 87 | port->tx_bytes -= count; | 
|  | 88 | spin_unlock_irqrestore(&port->lock, flags); | 
|  | 89 |  | 
|  | 90 | return result; | 
|  | 91 | } | 
|  | 92 |  | 
|  | 93 | return count; | 
|  | 94 | } | 
|  | 95 |  | 
|  | 96 | static int samba_write_room(struct tty_struct *tty) | 
|  | 97 | { | 
|  | 98 | struct usb_serial_port *port = tty->driver_data; | 
|  | 99 | unsigned long flags; | 
|  | 100 | unsigned long free; | 
|  | 101 | int count; | 
|  | 102 | int room; | 
|  | 103 |  | 
|  | 104 | spin_lock_irqsave(&port->lock, flags); | 
|  | 105 | free = port->write_urbs_free; | 
|  | 106 | spin_unlock_irqrestore(&port->lock, flags); | 
|  | 107 |  | 
|  | 108 | count = hweight_long(free); | 
|  | 109 | room = count * port->bulk_out_size; | 
|  | 110 |  | 
|  | 111 | dbg("%s - returns %d", __func__, room); | 
|  | 112 |  | 
|  | 113 | return room; | 
|  | 114 | } | 
|  | 115 |  | 
|  | 116 | static int samba_chars_in_buffer(struct tty_struct *tty) | 
|  | 117 | { | 
|  | 118 | struct usb_serial_port *port = tty->driver_data; | 
|  | 119 | unsigned long flags; | 
|  | 120 | int chars; | 
|  | 121 |  | 
|  | 122 | spin_lock_irqsave(&port->lock, flags); | 
|  | 123 | chars = port->tx_bytes; | 
|  | 124 | spin_unlock_irqrestore(&port->lock, flags); | 
|  | 125 |  | 
|  | 126 | dbg("%s - returns %d", __func__, chars); | 
|  | 127 |  | 
|  | 128 | return chars; | 
|  | 129 | } | 
|  | 130 |  | 
|  | 131 | static void samba_write_bulk_callback(struct urb *urb) | 
|  | 132 | { | 
|  | 133 | struct usb_serial_port *port = urb->context; | 
|  | 134 | unsigned long flags; | 
|  | 135 | int i; | 
|  | 136 |  | 
|  | 137 | dbg("%s - port %d", __func__, port->number); | 
|  | 138 |  | 
|  | 139 | for (i = 0; i < ARRAY_SIZE(port->write_urbs); ++i) { | 
|  | 140 | if (port->write_urbs[i] == urb) | 
|  | 141 | break; | 
|  | 142 | } | 
|  | 143 | spin_lock_irqsave(&port->lock, flags); | 
|  | 144 | __set_bit(i, &port->write_urbs_free); | 
|  | 145 | port->tx_bytes -= urb->transfer_buffer_length; | 
|  | 146 | spin_unlock_irqrestore(&port->lock, flags); | 
|  | 147 |  | 
|  | 148 | if (urb->status) | 
|  | 149 | dbg("%s - non-zero urb status: %d", __func__, urb->status); | 
|  | 150 |  | 
|  | 151 | usb_serial_port_softint(port); | 
|  | 152 | } | 
|  | 153 |  | 
|  | 154 | static struct usb_serial_driver samba_device = { | 
|  | 155 | .driver = { | 
|  | 156 | .owner		= THIS_MODULE, | 
|  | 157 | .name		= "sam-ba", | 
|  | 158 | }, | 
|  | 159 | .usb_driver		= &samba_driver, | 
|  | 160 | .id_table		= id_table, | 
|  | 161 | .num_ports		= 1, | 
|  | 162 | .bulk_in_size		= 512, | 
|  | 163 | .bulk_out_size		= 2048, | 
|  | 164 | .write			= samba_write, | 
|  | 165 | .write_room		= samba_write_room, | 
|  | 166 | .chars_in_buffer	= samba_chars_in_buffer, | 
|  | 167 | .write_bulk_callback	= samba_write_bulk_callback, | 
|  | 168 | .throttle		= usb_serial_generic_throttle, | 
|  | 169 | .unthrottle		= usb_serial_generic_unthrottle, | 
|  | 170 | }; | 
|  | 171 |  | 
|  | 172 | static int __init samba_init(void) | 
|  | 173 | { | 
|  | 174 | int retval; | 
|  | 175 |  | 
|  | 176 | retval = usb_serial_register(&samba_device); | 
|  | 177 | if (retval) | 
|  | 178 | return retval; | 
|  | 179 |  | 
|  | 180 | retval = usb_register(&samba_driver); | 
|  | 181 | if (retval) { | 
|  | 182 | usb_serial_deregister(&samba_device); | 
|  | 183 | return retval; | 
|  | 184 | } | 
|  | 185 |  | 
|  | 186 | printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ": " | 
|  | 187 | DRIVER_DESC "\n"); | 
|  | 188 | return 0; | 
|  | 189 | } | 
|  | 190 |  | 
|  | 191 | static void __exit samba_exit(void) | 
|  | 192 | { | 
|  | 193 | usb_deregister(&samba_driver); | 
|  | 194 | usb_serial_deregister(&samba_device); | 
|  | 195 | } | 
|  | 196 |  | 
|  | 197 | module_init(samba_init); | 
|  | 198 | module_exit(samba_exit); | 
|  | 199 |  | 
|  | 200 | MODULE_AUTHOR(DRIVER_AUTHOR); | 
|  | 201 | MODULE_DESCRIPTION(DRIVER_DESC); | 
|  | 202 | MODULE_VERSION(DRIVER_VERSION); | 
|  | 203 | MODULE_LICENSE("GPL"); | 
|  | 204 |  | 
|  | 205 | module_param(debug, bool, S_IRUGO | S_IWUSR); | 
|  | 206 | MODULE_PARM_DESC(debug, "Enable verbose debugging messages"); |