#include "../include/usb.h" #include "asm-generic/errno-base.h" #include "linux/cleanup.h" #include "linux/device/devres.h" #include "linux/gfp_types.h" #include #include #include #define GPIO_PIN_COUNT 18 struct microcontroller_gpio { struct gpio_chip chip; /* kernel GPIO controller object */ struct usb_device *udev; /* handle used for all USB transfers */ struct usb_interface *intf; unsigned char ep_addr; int interval; }; #include #include static int send_cmd_get_response_control(struct usb_device *udev, u8 *dst, __u16 max_resp_size, enum vendor_request request_type, const u8 cmd[2]) { int retval; u16 wValue; if (cmd == NULL) { wValue = ((u16)cmd[0] << 8) | cmd[1]; /* Pack 2 command bytes into wValue */ } else { wValue = 0; } /* 2. Execute Control IN transfer */ retval = usb_control_msg( udev, usb_rcvctrlpipe(udev, 0), /* Control IN pipe */ request_type, /* bRequest */ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, /* bmRequestType */ wValue, /* 2 Command bytes sent in SETUP */ 0x0000, /* wIndex (unused) */ dst, /* Buffer for incoming data */ max_resp_size, /* Request up to 256 bytes */ 2000 /* Timeout (ms) */ ); if (retval < 0) { pr_err("Control transfer failed: %d\n", retval); } return retval; } static void usb_data_send_completed(struct urb *urb) { if (urb->status == 0) pr_info("Async transfer successful!\n"); else pr_err("Async transfer failed with status: %d\n", urb->status); kfree(urb->transfer_buffer); } static int async_send_usb_data_to_microcontroller(const unsigned char *msg, size_t msg_len, struct usb_device *udev, unsigned char ep_addr,int interval) { struct urb *urb; char *buf_dma; int retval; unsigned int pipe = usb_sndbulkpipe(udev, ep_addr); urb = usb_alloc_urb(0, GFP_KERNEL); if (!urb) return -ENOMEM; buf_dma = kzalloc(msg_len, GFP_KERNEL); if (!buf_dma) { usb_free_urb(urb); return -ENOMEM; } memcpy(buf_dma, msg, msg_len); usb_fill_int_urb(urb, udev, pipe, buf_dma, msg_len, usb_data_send_completed, NULL, interval); retval = usb_submit_urb(urb, GFP_KERNEL); if (retval) { pr_err("Failed to submit URB: %d\n", retval); kfree(buf_dma); } // usb_data_send_completed will free is automaticaly usb_free_urb(urb); return retval; } /* Read the level of line "off" from the device; return 1, 0 or -errno. */ static int microcontroller_get(struct gpio_chip *gc, unsigned int off) { if (off>= gc->ngpio) { return -EINVAL; } struct microcontroller_gpio *dev = gpiochip_get_data(gc); u8 retval; const u8 cmd[] = {OP_GPIO_GET,off}; send_cmd_get_response_control(dev->udev, &retval, sizeof(retval), CONTROL_PINS, cmd); return retval; } /* Drive output line "off" to "val" (0 or 1) on the device; return 0 or -errno. */ static int microcontroller_set(struct gpio_chip *gc, unsigned int off, int val) { struct microcontroller_gpio *dev = gpiochip_get_data(gc); const unsigned char msg[2] = {OP_GPIO_SET, val}; return async_send_usb_data_to_microcontroller(msg, sizeof(msg), dev->udev, dev->ep_addr,dev->interval); }; /* Tell the device to make line "off" an input; return 0 or -errno. */ static int microcontroller_dir_in(struct gpio_chip *gc, unsigned int off) { struct microcontroller_gpio *dev = gpiochip_get_data(gc); const unsigned char msg[2] = {OP_GPIO_SET_DIR, DIR_IN}; return async_send_usb_data_to_microcontroller(msg, sizeof(msg), dev->udev, dev->ep_addr,dev->interval); } /* Tell the device to make line "off" an output driving "val"; return 0 or * -errno. */ static int microcontroller_dir_out(struct gpio_chip *gc, unsigned int off, int val) { struct microcontroller_gpio *dev = gpiochip_get_data(gc); const unsigned char msg[3] = {OP_GPIO_SET_DIR, DIR_OUT, val}; return async_send_usb_data_to_microcontroller(msg, sizeof(msg), dev->udev, dev->ep_addr,dev->interval); } static int get_microcontroller_capabilities(struct microcontroller_gpio *dev) { struct microcontroller_capabilities capabs; if (send_cmd_get_response_control(dev->udev, (u8 *)&capabs, sizeof(capabs), GET_INFO, NULL) != sizeof(capabs)) { dev->chip.ngpio = capabs.num_gpio; return EINVAL; } return 0; } /* Called when the USB device matches: allocate state, fill gpio_chip, register * it. */ static int microcontroller_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct microcontroller_gpio *dev; dev = devm_kzalloc(&intf->dev, sizeof(*dev), GFP_KERNEL); if (!dev) { return -ENOMEM; } struct usb_host_interface *iface_desc = intf->cur_altsetting; struct usb_endpoint_descriptor *ep_desc; int i; for (i = 0; i < iface_desc->desc.bNumEndpoints; i++) { ep_desc = &iface_desc->endpoint[i].desc; if (usb_endpoint_is_int_out(ep_desc)) { pr_info("Found Bulk OUT endpoint address: 0x%02X\n", ep_desc->bEndpointAddress); dev->interval = ep_desc->bInterval; dev->ep_addr = ep_desc->bEndpointAddress; break; } } usb_set_intfdata(intf, dev); dev_info(&intf->dev, "USB device attached: vendor=0x%04x, product=0x%04x\n", le16_to_cpu(dev->udev->descriptor.idVendor), le16_to_cpu(dev->udev->descriptor.idProduct)); dev->udev = usb_get_dev(interface_to_usbdev(intf)); dev->intf = intf; dev->chip.label = "microcontroller-gpio"; dev->chip.parent = &intf->dev; dev->chip.owner = THIS_MODULE; dev->chip.base = -1; dev->chip.get = microcontroller_get; dev->chip.set = microcontroller_set; dev->chip.direction_input = microcontroller_dir_in; dev->chip.direction_output = microcontroller_dir_out; get_microcontroller_capabilities(dev); int ret = devm_gpiochip_add_data(&intf->dev, &dev->chip, dev); if (ret) { dev_err(&intf->dev, "Failed to add gpio_chip\n"); return ret; } return 0; } /* Called on unplug or driver unbind: release the USB device reference. */ static void microcontroller_disconnect(struct usb_interface *intf) { struct microcontroller_gpio *dev; dev = usb_get_intfdata(intf); usb_set_intfdata(intf, NULL); if (dev) { usb_put_dev(dev->udev); } } /* VID/PID pairs this driver binds to. */ static const struct usb_device_id microcontroller_table[] = { {USB_DEVICE(0xfad0, 0x9000)}, {}}; /* Exports the table so udev can autoload the module when the device appears. */ MODULE_DEVICE_TABLE(usb, microcontroller_table); /* Ties probe, disconnect and the ID table together for the USB core. */ static struct usb_driver microcontroller_driver = { .name = "microcontroller-GPIO", .probe = microcontroller_probe, .disconnect = microcontroller_disconnect, .id_table = microcontroller_table, }; /* Generates module init and exit that register and unregister * microcontroller_driver. */ module_usb_driver(microcontroller_driver); MODULE_LICENSE("GPL");