|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
This site uses Google Analytics to track visits.
Privacy Statement |
MAX3420 - Maxim USB Peripheral ControllerThe MAX3420 is a USB peripheral controller chip with an SPI bus. This page hopefully contains enough information to help you easily make use of the device in your projects.
• MAX3420 IntroductionThe MAX3420 provides a very simple approach to adding a USB interface to a circuit. It uses a SPI bus to connect to your system. It does require a reasonable amount of configuration and control, so you'll need to connect it to some form of microprocessor/microcontroller. The photos below show a PCB containing a MAX3420 device. The board is designed to connect to a Digilent XUP-V2Pro FPGA development board.
• Circuit & PCBBelow are the Schematic and PCB layouts for the board. Use the link below to download the Eagle CAD files.
The board is driven by a 3.3V supply. The connector pin arrangement allows it to be plugged directly into header J5 of a Diglent XUP-V2Pro FPGA development board, as shown in the right-hand photo above.
• Example SystemBelow is an example setup of using the MAX3420 to connect a PC and a microcontroller embedded on an FPGA. There are 3 main custom parts to this system.
Information on the MAX3420 has already been given above. Details on the linux device driver and the EDK Peripheral are given below.
• Linux Host DriverYou will need a kernel driver in order to send and receive data from the MAX3420 chip. Below is a basic usb driver that will do this. Hopefully all you'll need to do is download the gzippped tar, uncompress, and type make.
> tar -zxf usbDIO.tar.gz > cd usbDIO > make > insmod usbDIO.ko Note: I'm not in any way an expert at Linux kernel development. Although the driver seems to work fine, I can't say how efficient or reliable it is. • File: usbDIO.c
#include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/usb.h> #include <asm/uaccess.h> #include <linux/slab.h> #include <linux/mutex.h> #define USBDIO_VENDOR_ID 0x6666 #define USBDIO_PRODUCT_ID 0x1ABC // Minor number for misc USB devices #define USBDIO_MINOR 132 // Maximum number of writes at one time #define MAX_WRITE_TRANSFER 256 #define MAX_WRITE_IN_FLIGHT 16 static int usbDIO_open(struct inode *inode, struct file *file); static int usbDIO_release(struct inode *inode, struct file *file); static ssize_t usbDIO_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos); static ssize_t usbDIO_write(struct file *file, const char __user *user_buffer, size_t count, loff_t *ppos); static void usbDIO_write_callback(struct urb *urb); static int usbDIO_probe(struct usb_interface *interface, const struct usb_device_id *id); static void usbDIO_disconnect(struct usb_interface *interface); static void usbDIO_delete(struct kref *kref); // **************************************************************************************************** // Stuctures // **************************************************************************************************** // Table of devices that work with this driver static struct usb_device_id usbDIO_id_table [] = { { USB_DEVICE(USBDIO_VENDOR_ID, USBDIO_PRODUCT_ID) }, { }, // Terminating Entry }; MODULE_DEVICE_TABLE (usb, usbDIO_id_table); // Strucutre for device specific data struct usbDIO_deviceData { struct usb_device *device; // struct usb_interface *interface; // The interface for this device unsigned char *bulk_in_buffer; // The buffer to receive data size_t bulk_in_size; // The size of the receive buffer __u8 bulk_in_endpointAddr; // The address of the bulk in endpoint __u8 bulk_out_endpointAddr; // The address of the bulk out endpoint struct kref kref; // Reference Counter struct semaphore limitSem; // Limiting the number of writes in progress struct mutex ioMutex; // Synchronize I/O with disconnect }; static struct usb_driver usbDIO_driver = { .name = "usbDIO", .id_table = usbDIO_id_table, .probe = usbDIO_probe, .disconnect = usbDIO_disconnect, }; static struct file_operations usbDIO_fops = { .owner = THIS_MODULE, .open = usbDIO_open, .release = usbDIO_release, .read = usbDIO_read, .write = usbDIO_write, // .ioctl = ioctl_rio, }; // USB class driver info in order to get a minor number from the usb core, // and to have the device registered with the driver core static struct usb_class_driver usbDIO_class = { .name = "usbDIO%d", .fops = &usbDIO_fops, .minor_base = USBDIO_MINOR, }; // **************************************************************************************************** // File Operations // **************************************************************************************************** static int usbDIO_open(struct inode *inode, struct file *file) { struct usbDIO_deviceData *deviceData; struct usb_interface *interface; int subminor; int retval = 0; subminor = iminor(inode); interface = usb_find_interface(&usbDIO_driver, subminor); if (!interface) { err ("usbDIO: %s - error, can't find device for minor %d", __FUNCTION__, subminor); return -ENODEV; } // Get the device data structure from the interface deviceData = usb_get_intfdata(interface); if (!deviceData) return -ENODEV; // Prevent the device from being autosuspended retval = usb_autopm_get_interface(interface); if (retval) return retval; // Increment our usage count for the device kref_get(&deviceData->kref); // Save the device data structure in the file's private structure file->private_data = deviceData; return 0; } static int usbDIO_release(struct inode *inode, struct file *file) { struct usbDIO_deviceData *deviceData; deviceData = (struct usbDIO_deviceData *)file->private_data; if (deviceData == NULL) return -ENODEV; // Allow the device to be autosuspended mutex_lock(&deviceData->ioMutex); if (deviceData->interface) usb_autopm_put_interface(deviceData->interface); mutex_unlock(&deviceData->ioMutex); // Decrement the count on our device kref_put(&deviceData->kref, usbDIO_delete); return 0; } static ssize_t usbDIO_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) { struct usbDIO_deviceData *deviceData; int retval = 0; int bytesRead; deviceData = (struct usbDIO_deviceData *)file->private_data; mutex_lock(&deviceData->ioMutex); if (!deviceData->interface) { // disconnect() was called retval = -ENODEV; goto exit; } // Do a blocking bulk read to get data from the device retval = usb_bulk_msg(deviceData->device, usb_rcvbulkpipe(deviceData->device, deviceData->bulk_in_endpointAddr), deviceData->bulk_in_buffer, min(deviceData->bulk_in_size, count), &bytesRead, HZ*10); // If the read was successful (retval != 0), copy the data to userspace if (!retval) { if (copy_to_user(buffer, deviceData->bulk_in_buffer, count)) retval = -EFAULT; else retval = bytesRead; } exit: mutex_unlock(&deviceData->ioMutex); return retval; } static ssize_t usbDIO_write(struct file *file, const char __user *user_buffer, size_t count, loff_t *ppos) { struct usbDIO_deviceData *deviceData; int retval = 0; struct urb *urb = NULL; char *buf = NULL; size_t writeSize = min(count, (size_t) MAX_WRITE_TRANSFER); deviceData = (struct usbDIO_deviceData *)file->private_data; // Verify that there is actually some data to write if (count == 0) goto exit; // Limit the number of URBs in flight to stop a user from using up all RAM if (down_interruptible(&deviceData->limitSem)) { retval = -ERESTARTSYS; goto exit; } mutex_lock(&deviceData->ioMutex); if (!deviceData->interface) { // Disconnect() was called - Setting interface to NULL retval = -ENODEV; goto error; } // Create an URB urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) { retval = -ENOMEM; goto error; } // Create a buffer buf = usb_buffer_alloc(deviceData->device, writeSize, GFP_KERNEL, &urb->transfer_dma); if (!buf) { retval = -ENOMEM; goto error; } // Copy the data to the urb if (copy_from_user(buf, user_buffer, writeSize)) { retval = -EFAULT; goto error; } // Initialize the urb usb_fill_bulk_urb(urb, deviceData->device, usb_sndbulkpipe(deviceData->device, deviceData->bulk_out_endpointAddr), buf, writeSize, usbDIO_write_callback, deviceData); urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; // Send the data out the bulk port retval = usb_submit_urb(urb, GFP_KERNEL); if (retval) { err("usbDIO: %s - failed submitting write urb, error %d", __FUNCTION__, retval); goto error; } // Release the URB reference, the USB core will eventually free it entirely usb_free_urb(urb); mutex_unlock(&deviceData->ioMutex); return writeSize; error: if (urb) { usb_buffer_free(deviceData->device, writeSize, buf, urb->transfer_dma); usb_free_urb(urb); } mutex_unlock(&deviceData->ioMutex); up(&deviceData->limitSem); exit: return retval; } static void usbDIO_write_callback(struct urb *urb) { struct usbDIO_deviceData *deviceData = (struct usbDIO_deviceData *) urb->context; // sync/async unlink faults aren't errors if (urb->status && !(urb->status == -ENOENT || urb->status == -ECONNRESET || urb->status == -ESHUTDOWN)) { err("%s - nonzero write bulk status received: %d", __FUNCTION__, urb->status); } // free up our allocated buffer usb_buffer_free(urb->dev, urb->transfer_buffer_length, urb->transfer_buffer, urb->transfer_dma); up(&deviceData->limitSem); } // **************************************************************************************************** // USB Operations // **************************************************************************************************** // Called when a device is installed that the USB core thinks this driver should handle; // Local structures used by the USB driver should initialize here. // Information that is needed about the device should be saved static int usbDIO_probe(struct usb_interface *interface, const struct usb_device_id *id) { struct usbDIO_deviceData *deviceData = NULL; int i; struct usb_host_interface *interface_desc; struct usb_endpoint_descriptor *endpoint; size_t buffer_size; int retval; // Allocate memory for a device state structure deviceData = kzalloc(sizeof(*deviceData), GFP_KERNEL); // Check memory allocation worked if (deviceData == NULL) { err("usbDIO_probe: No memory to allocate device structure"); retval = -ENOMEM; goto error; } // Initialise the device structure's reference counter kref_init(&deviceData->kref); sema_init(&deviceData->limitSem, MAX_WRITE_IN_FLIGHT); mutex_init(&deviceData->ioMutex); // Store the USB Device and interface pointers deviceData->device = usb_get_dev(interface_to_usbdev(interface)); deviceData->interface = interface; // XXX Get altsetting? interface_desc = interface->cur_altsetting; for (i = 0; i < interface_desc->desc.bNumEndpoints; ++i) { endpoint = &interface_desc->endpoint[i].desc; // Check for Bulk IN endpoint if (!deviceData->bulk_in_endpointAddr && usb_endpoint_is_bulk_in(endpoint)) { deviceData->bulk_in_endpointAddr = endpoint->bEndpointAddress; // Allocate memory for IN buffer buffer_size = le16_to_cpu(endpoint->wMaxPacketSize); deviceData->bulk_in_size = buffer_size; deviceData->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL); if (!deviceData->bulk_in_buffer) { err("usbDIO_probe: No memory to allocate bulk_in_buffer"); retval = -ENOMEM; goto error; } } // Check for Bulk OUT endpoint if (!deviceData->bulk_out_endpointAddr && usb_endpoint_is_bulk_out(endpoint)) { deviceData->bulk_out_endpointAddr = endpoint->bEndpointAddress; } } // Check that both endpoints have been found if (!deviceData->bulk_in_endpointAddr || !deviceData->bulk_out_endpointAddr) { err("Could not find both bulk-in and bulk-out endpoints"); retval = -1; // XXX Need better error number goto error; } // Save our device pointer in this interface device usb_set_intfdata(interface, deviceData); // Register the device, asking for minor number // Creates the devfs file for the usb device, if devfs is enabled, and creates a usb class device in the sysfs tree. retval = usb_register_dev(interface, &usbDIO_class); if (retval) { // Something prevented the driver being registered err("usbDIO_probe: Not able to register the device."); usb_set_intfdata(interface, NULL); goto error; } // Get Device release number (Binary code decimal) i = le16_to_cpu(deviceData->device->descriptor.bcdDevice); // Display Endpoint numbers info("usbDIO: Bulk IN EP:0x%X(%lu), Bulk OUT EP:0x%X", deviceData->bulk_in_endpointAddr, deviceData->bulk_in_size, deviceData->bulk_out_endpointAddr); info("usbDIO: Version %1d%1d.%1d%1d found at address %d", (i>>12)&0xF, (i>>8)&0xF, (i>>4)&0xF, i&0xF, deviceData->device->devnum); // Let the user know what node this device is now attached to info("usbDIO: device now attached, minor number:%d", interface->minor); return 0; error: // Decrement the ref counter (Calls usbDIO_delete if counter reaches 0) if (deviceData) kref_put(&deviceData->kref, usbDIO_delete); return retval; } // Called when the driver should no longer control the device for some reason and can do clean-up. static void usbDIO_disconnect(struct usb_interface *interface) { struct usbDIO_deviceData *deviceData; int minor = interface->minor; // Prevent usbDIO_open() from racing usbDIO_disconnect() lock_kernel(); deviceData = usb_get_intfdata(interface); usb_set_intfdata(interface, NULL); // Give back the minor usb_deregister_dev(interface, &usbDIO_class); // Prevent more I/O from starting mutex_lock(&deviceData->ioMutex); deviceData->interface = NULL; mutex_unlock(&deviceData->ioMutex); unlock_kernel(); // Decrement the usage count kref_put(&deviceData->kref, usbDIO_delete); info("usbDIO: #%d now disconnected", minor); } static void usbDIO_delete(struct kref *kref) { // container_of(ptr, type, member) // ptr The pointer to the member. // type The type of the container struct this is embedded in. // member The name of the member within the struct. // Get pointer to struture (usb_usbDIO) from member (kref) struct usbDIO_deviceData *deviceData = container_of(kref, struct usbDIO_deviceData, kref); // Release the use of the usb device structure usb_put_dev(deviceData->device); // Free the allocated memory kfree(deviceData->bulk_in_buffer); kfree(deviceData); } // **************************************************************************************************** // Module init and exit // **************************************************************************************************** static int __init usbDIO_init(void) { int result = usb_register(&usbDIO_driver); // Register this driver with the USB subsystem if (result) err("usbDIO: usb_register failed. Error number %d", result); return result; } static void __exit usbDIO_exit(void) { // Deregister this driver with the USB subsystem usb_deregister(&usbDIO_driver); } // **************************************************************************************************** MODULE_LICENSE("GPL"); MODULE_AUTHOR("A.Greensted <ajg112@ohm.york.ac.uk>"); MODULE_DESCRIPTION("USB based Data IO driver"); MODULE_VERSION("0.1"); module_init(usbDIO_init); module_exit(usbDIO_exit); • File: Makefile
# usbDIO Makefile ifneq ($(KERNELRELEASE),) obj-m := usbDIO.o else KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules endif Loading this module will create the file /dev/usbDIO0. Writing to and reading from this file will send and receive date from the MAX3420 device.
• EDK Peripheral & Driver CodeThe EDK peripheral linked to below contains a SPI module that will talk to the MAX3420. Untar the file and place the max3420_v1_00_a directory into your EDK project's pcores directory.
The table below shows the pad LOC constraints for the XUP-V2Pro J5 Header. You'll need to put these into your system's UCF file.
The module only uses two register locations. The first is used to read the MAX3420 status bits and set the SPI data rate. The second is used to transfer data between the MAX3420 device and the embedded processor. These functions are summeriesed in the table below.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||