18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*****************************************************************************
38c2ecf20Sopenharmony_ci *                          USBLCD Kernel Driver                             *
48c2ecf20Sopenharmony_ci *                            Version 1.05                                   *
58c2ecf20Sopenharmony_ci *             (C) 2005 Georges Toth <g.toth@e-biz.lu>                       *
68c2ecf20Sopenharmony_ci *                                                                           *
78c2ecf20Sopenharmony_ci *     This file is licensed under the GPL. See COPYING in the package.      *
88c2ecf20Sopenharmony_ci * Based on usb-skeleton.c 2.0 by Greg Kroah-Hartman (greg@kroah.com)        *
98c2ecf20Sopenharmony_ci *                                                                           *
108c2ecf20Sopenharmony_ci *                                                                           *
118c2ecf20Sopenharmony_ci * 28.02.05 Complete rewrite of the original usblcd.c driver,                *
128c2ecf20Sopenharmony_ci *          based on usb_skeleton.c.                                         *
138c2ecf20Sopenharmony_ci *          This new driver allows more than one USB-LCD to be connected     *
148c2ecf20Sopenharmony_ci *          and controlled, at once                                          *
158c2ecf20Sopenharmony_ci *****************************************************************************/
168c2ecf20Sopenharmony_ci#include <linux/module.h>
178c2ecf20Sopenharmony_ci#include <linux/kernel.h>
188c2ecf20Sopenharmony_ci#include <linux/slab.h>
198c2ecf20Sopenharmony_ci#include <linux/errno.h>
208c2ecf20Sopenharmony_ci#include <linux/mutex.h>
218c2ecf20Sopenharmony_ci#include <linux/rwsem.h>
228c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
238c2ecf20Sopenharmony_ci#include <linux/usb.h>
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci#define DRIVER_VERSION "USBLCD Driver Version 1.05"
268c2ecf20Sopenharmony_ci
278c2ecf20Sopenharmony_ci#define USBLCD_MINOR		144
288c2ecf20Sopenharmony_ci
298c2ecf20Sopenharmony_ci#define IOCTL_GET_HARD_VERSION	1
308c2ecf20Sopenharmony_ci#define IOCTL_GET_DRV_VERSION	2
318c2ecf20Sopenharmony_ci
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_cistatic const struct usb_device_id id_table[] = {
348c2ecf20Sopenharmony_ci	{ .idVendor = 0x10D2, .match_flags = USB_DEVICE_ID_MATCH_VENDOR, },
358c2ecf20Sopenharmony_ci	{ },
368c2ecf20Sopenharmony_ci};
378c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(usb, id_table);
388c2ecf20Sopenharmony_ci
398c2ecf20Sopenharmony_cistruct usb_lcd {
408c2ecf20Sopenharmony_ci	struct usb_device	*udev;			/* init: probe_lcd */
418c2ecf20Sopenharmony_ci	struct usb_interface	*interface;		/* the interface for
428c2ecf20Sopenharmony_ci							   this device */
438c2ecf20Sopenharmony_ci	unsigned char		*bulk_in_buffer;	/* the buffer to receive
448c2ecf20Sopenharmony_ci							   data */
458c2ecf20Sopenharmony_ci	size_t			bulk_in_size;		/* the size of the
468c2ecf20Sopenharmony_ci							   receive buffer */
478c2ecf20Sopenharmony_ci	__u8			bulk_in_endpointAddr;	/* the address of the
488c2ecf20Sopenharmony_ci							   bulk in endpoint */
498c2ecf20Sopenharmony_ci	__u8			bulk_out_endpointAddr;	/* the address of the
508c2ecf20Sopenharmony_ci							   bulk out endpoint */
518c2ecf20Sopenharmony_ci	struct kref		kref;
528c2ecf20Sopenharmony_ci	struct semaphore	limit_sem;		/* to stop writes at
538c2ecf20Sopenharmony_ci							   full throttle from
548c2ecf20Sopenharmony_ci							   using up all RAM */
558c2ecf20Sopenharmony_ci	struct usb_anchor	submitted;		/* URBs to wait for
568c2ecf20Sopenharmony_ci							   before suspend */
578c2ecf20Sopenharmony_ci	struct rw_semaphore	io_rwsem;
588c2ecf20Sopenharmony_ci	unsigned long		disconnected:1;
598c2ecf20Sopenharmony_ci};
608c2ecf20Sopenharmony_ci#define to_lcd_dev(d) container_of(d, struct usb_lcd, kref)
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci#define USB_LCD_CONCURRENT_WRITES	5
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_cistatic struct usb_driver lcd_driver;
658c2ecf20Sopenharmony_ci
668c2ecf20Sopenharmony_ci
678c2ecf20Sopenharmony_cistatic void lcd_delete(struct kref *kref)
688c2ecf20Sopenharmony_ci{
698c2ecf20Sopenharmony_ci	struct usb_lcd *dev = to_lcd_dev(kref);
708c2ecf20Sopenharmony_ci
718c2ecf20Sopenharmony_ci	usb_put_dev(dev->udev);
728c2ecf20Sopenharmony_ci	kfree(dev->bulk_in_buffer);
738c2ecf20Sopenharmony_ci	kfree(dev);
748c2ecf20Sopenharmony_ci}
758c2ecf20Sopenharmony_ci
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_cistatic int lcd_open(struct inode *inode, struct file *file)
788c2ecf20Sopenharmony_ci{
798c2ecf20Sopenharmony_ci	struct usb_lcd *dev;
808c2ecf20Sopenharmony_ci	struct usb_interface *interface;
818c2ecf20Sopenharmony_ci	int subminor, r;
828c2ecf20Sopenharmony_ci
838c2ecf20Sopenharmony_ci	subminor = iminor(inode);
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	interface = usb_find_interface(&lcd_driver, subminor);
868c2ecf20Sopenharmony_ci	if (!interface) {
878c2ecf20Sopenharmony_ci		pr_err("USBLCD: %s - error, can't find device for minor %d\n",
888c2ecf20Sopenharmony_ci		       __func__, subminor);
898c2ecf20Sopenharmony_ci		return -ENODEV;
908c2ecf20Sopenharmony_ci	}
918c2ecf20Sopenharmony_ci
928c2ecf20Sopenharmony_ci	dev = usb_get_intfdata(interface);
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_ci	/* increment our usage count for the device */
958c2ecf20Sopenharmony_ci	kref_get(&dev->kref);
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	/* grab a power reference */
988c2ecf20Sopenharmony_ci	r = usb_autopm_get_interface(interface);
998c2ecf20Sopenharmony_ci	if (r < 0) {
1008c2ecf20Sopenharmony_ci		kref_put(&dev->kref, lcd_delete);
1018c2ecf20Sopenharmony_ci		return r;
1028c2ecf20Sopenharmony_ci	}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci	/* save our object in the file's private structure */
1058c2ecf20Sopenharmony_ci	file->private_data = dev;
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_ci	return 0;
1088c2ecf20Sopenharmony_ci}
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_cistatic int lcd_release(struct inode *inode, struct file *file)
1118c2ecf20Sopenharmony_ci{
1128c2ecf20Sopenharmony_ci	struct usb_lcd *dev;
1138c2ecf20Sopenharmony_ci
1148c2ecf20Sopenharmony_ci	dev = file->private_data;
1158c2ecf20Sopenharmony_ci	if (dev == NULL)
1168c2ecf20Sopenharmony_ci		return -ENODEV;
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	/* decrement the count on our device */
1198c2ecf20Sopenharmony_ci	usb_autopm_put_interface(dev->interface);
1208c2ecf20Sopenharmony_ci	kref_put(&dev->kref, lcd_delete);
1218c2ecf20Sopenharmony_ci	return 0;
1228c2ecf20Sopenharmony_ci}
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_cistatic ssize_t lcd_read(struct file *file, char __user * buffer,
1258c2ecf20Sopenharmony_ci			size_t count, loff_t *ppos)
1268c2ecf20Sopenharmony_ci{
1278c2ecf20Sopenharmony_ci	struct usb_lcd *dev;
1288c2ecf20Sopenharmony_ci	int retval = 0;
1298c2ecf20Sopenharmony_ci	int bytes_read;
1308c2ecf20Sopenharmony_ci
1318c2ecf20Sopenharmony_ci	dev = file->private_data;
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	down_read(&dev->io_rwsem);
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	if (dev->disconnected) {
1368c2ecf20Sopenharmony_ci		retval = -ENODEV;
1378c2ecf20Sopenharmony_ci		goto out_up_io;
1388c2ecf20Sopenharmony_ci	}
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	/* do a blocking bulk read to get data from the device */
1418c2ecf20Sopenharmony_ci	retval = usb_bulk_msg(dev->udev,
1428c2ecf20Sopenharmony_ci			      usb_rcvbulkpipe(dev->udev,
1438c2ecf20Sopenharmony_ci					      dev->bulk_in_endpointAddr),
1448c2ecf20Sopenharmony_ci			      dev->bulk_in_buffer,
1458c2ecf20Sopenharmony_ci			      min(dev->bulk_in_size, count),
1468c2ecf20Sopenharmony_ci			      &bytes_read, 10000);
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_ci	/* if the read was successful, copy the data to userspace */
1498c2ecf20Sopenharmony_ci	if (!retval) {
1508c2ecf20Sopenharmony_ci		if (copy_to_user(buffer, dev->bulk_in_buffer, bytes_read))
1518c2ecf20Sopenharmony_ci			retval = -EFAULT;
1528c2ecf20Sopenharmony_ci		else
1538c2ecf20Sopenharmony_ci			retval = bytes_read;
1548c2ecf20Sopenharmony_ci	}
1558c2ecf20Sopenharmony_ci
1568c2ecf20Sopenharmony_ciout_up_io:
1578c2ecf20Sopenharmony_ci	up_read(&dev->io_rwsem);
1588c2ecf20Sopenharmony_ci
1598c2ecf20Sopenharmony_ci	return retval;
1608c2ecf20Sopenharmony_ci}
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_cistatic long lcd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
1638c2ecf20Sopenharmony_ci{
1648c2ecf20Sopenharmony_ci	struct usb_lcd *dev;
1658c2ecf20Sopenharmony_ci	u16 bcdDevice;
1668c2ecf20Sopenharmony_ci	char buf[30];
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	dev = file->private_data;
1698c2ecf20Sopenharmony_ci	if (dev == NULL)
1708c2ecf20Sopenharmony_ci		return -ENODEV;
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	switch (cmd) {
1738c2ecf20Sopenharmony_ci	case IOCTL_GET_HARD_VERSION:
1748c2ecf20Sopenharmony_ci		bcdDevice = le16_to_cpu((dev->udev)->descriptor.bcdDevice);
1758c2ecf20Sopenharmony_ci		sprintf(buf, "%1d%1d.%1d%1d",
1768c2ecf20Sopenharmony_ci			(bcdDevice & 0xF000)>>12,
1778c2ecf20Sopenharmony_ci			(bcdDevice & 0xF00)>>8,
1788c2ecf20Sopenharmony_ci			(bcdDevice & 0xF0)>>4,
1798c2ecf20Sopenharmony_ci			(bcdDevice & 0xF));
1808c2ecf20Sopenharmony_ci		if (copy_to_user((void __user *)arg, buf, strlen(buf)) != 0)
1818c2ecf20Sopenharmony_ci			return -EFAULT;
1828c2ecf20Sopenharmony_ci		break;
1838c2ecf20Sopenharmony_ci	case IOCTL_GET_DRV_VERSION:
1848c2ecf20Sopenharmony_ci		sprintf(buf, DRIVER_VERSION);
1858c2ecf20Sopenharmony_ci		if (copy_to_user((void __user *)arg, buf, strlen(buf)) != 0)
1868c2ecf20Sopenharmony_ci			return -EFAULT;
1878c2ecf20Sopenharmony_ci		break;
1888c2ecf20Sopenharmony_ci	default:
1898c2ecf20Sopenharmony_ci		return -ENOTTY;
1908c2ecf20Sopenharmony_ci	}
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	return 0;
1938c2ecf20Sopenharmony_ci}
1948c2ecf20Sopenharmony_ci
1958c2ecf20Sopenharmony_cistatic void lcd_write_bulk_callback(struct urb *urb)
1968c2ecf20Sopenharmony_ci{
1978c2ecf20Sopenharmony_ci	struct usb_lcd *dev;
1988c2ecf20Sopenharmony_ci	int status = urb->status;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	dev = urb->context;
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_ci	/* sync/async unlink faults aren't errors */
2038c2ecf20Sopenharmony_ci	if (status &&
2048c2ecf20Sopenharmony_ci	    !(status == -ENOENT ||
2058c2ecf20Sopenharmony_ci	      status == -ECONNRESET ||
2068c2ecf20Sopenharmony_ci	      status == -ESHUTDOWN)) {
2078c2ecf20Sopenharmony_ci		dev_dbg(&dev->interface->dev,
2088c2ecf20Sopenharmony_ci			"nonzero write bulk status received: %d\n", status);
2098c2ecf20Sopenharmony_ci	}
2108c2ecf20Sopenharmony_ci
2118c2ecf20Sopenharmony_ci	/* free up our allocated buffer */
2128c2ecf20Sopenharmony_ci	usb_free_coherent(urb->dev, urb->transfer_buffer_length,
2138c2ecf20Sopenharmony_ci			  urb->transfer_buffer, urb->transfer_dma);
2148c2ecf20Sopenharmony_ci	up(&dev->limit_sem);
2158c2ecf20Sopenharmony_ci}
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_cistatic ssize_t lcd_write(struct file *file, const char __user * user_buffer,
2188c2ecf20Sopenharmony_ci			 size_t count, loff_t *ppos)
2198c2ecf20Sopenharmony_ci{
2208c2ecf20Sopenharmony_ci	struct usb_lcd *dev;
2218c2ecf20Sopenharmony_ci	int retval = 0, r;
2228c2ecf20Sopenharmony_ci	struct urb *urb = NULL;
2238c2ecf20Sopenharmony_ci	char *buf = NULL;
2248c2ecf20Sopenharmony_ci
2258c2ecf20Sopenharmony_ci	dev = file->private_data;
2268c2ecf20Sopenharmony_ci
2278c2ecf20Sopenharmony_ci	/* verify that we actually have some data to write */
2288c2ecf20Sopenharmony_ci	if (count == 0)
2298c2ecf20Sopenharmony_ci		goto exit;
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	r = down_interruptible(&dev->limit_sem);
2328c2ecf20Sopenharmony_ci	if (r < 0)
2338c2ecf20Sopenharmony_ci		return -EINTR;
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	down_read(&dev->io_rwsem);
2368c2ecf20Sopenharmony_ci
2378c2ecf20Sopenharmony_ci	if (dev->disconnected) {
2388c2ecf20Sopenharmony_ci		retval = -ENODEV;
2398c2ecf20Sopenharmony_ci		goto err_up_io;
2408c2ecf20Sopenharmony_ci	}
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci	/* create a urb, and a buffer for it, and copy the data to the urb */
2438c2ecf20Sopenharmony_ci	urb = usb_alloc_urb(0, GFP_KERNEL);
2448c2ecf20Sopenharmony_ci	if (!urb) {
2458c2ecf20Sopenharmony_ci		retval = -ENOMEM;
2468c2ecf20Sopenharmony_ci		goto err_up_io;
2478c2ecf20Sopenharmony_ci	}
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	buf = usb_alloc_coherent(dev->udev, count, GFP_KERNEL,
2508c2ecf20Sopenharmony_ci				 &urb->transfer_dma);
2518c2ecf20Sopenharmony_ci	if (!buf) {
2528c2ecf20Sopenharmony_ci		retval = -ENOMEM;
2538c2ecf20Sopenharmony_ci		goto error;
2548c2ecf20Sopenharmony_ci	}
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci	if (copy_from_user(buf, user_buffer, count)) {
2578c2ecf20Sopenharmony_ci		retval = -EFAULT;
2588c2ecf20Sopenharmony_ci		goto error;
2598c2ecf20Sopenharmony_ci	}
2608c2ecf20Sopenharmony_ci
2618c2ecf20Sopenharmony_ci	/* initialize the urb properly */
2628c2ecf20Sopenharmony_ci	usb_fill_bulk_urb(urb, dev->udev,
2638c2ecf20Sopenharmony_ci			  usb_sndbulkpipe(dev->udev,
2648c2ecf20Sopenharmony_ci			  dev->bulk_out_endpointAddr),
2658c2ecf20Sopenharmony_ci			  buf, count, lcd_write_bulk_callback, dev);
2668c2ecf20Sopenharmony_ci	urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci	usb_anchor_urb(urb, &dev->submitted);
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci	/* send the data out the bulk port */
2718c2ecf20Sopenharmony_ci	retval = usb_submit_urb(urb, GFP_KERNEL);
2728c2ecf20Sopenharmony_ci	if (retval) {
2738c2ecf20Sopenharmony_ci		dev_err(&dev->udev->dev,
2748c2ecf20Sopenharmony_ci			"%s - failed submitting write urb, error %d\n",
2758c2ecf20Sopenharmony_ci			__func__, retval);
2768c2ecf20Sopenharmony_ci		goto error_unanchor;
2778c2ecf20Sopenharmony_ci	}
2788c2ecf20Sopenharmony_ci
2798c2ecf20Sopenharmony_ci	/* release our reference to this urb,
2808c2ecf20Sopenharmony_ci	   the USB core will eventually free it entirely */
2818c2ecf20Sopenharmony_ci	usb_free_urb(urb);
2828c2ecf20Sopenharmony_ci
2838c2ecf20Sopenharmony_ci	up_read(&dev->io_rwsem);
2848c2ecf20Sopenharmony_ciexit:
2858c2ecf20Sopenharmony_ci	return count;
2868c2ecf20Sopenharmony_cierror_unanchor:
2878c2ecf20Sopenharmony_ci	usb_unanchor_urb(urb);
2888c2ecf20Sopenharmony_cierror:
2898c2ecf20Sopenharmony_ci	usb_free_coherent(dev->udev, count, buf, urb->transfer_dma);
2908c2ecf20Sopenharmony_ci	usb_free_urb(urb);
2918c2ecf20Sopenharmony_cierr_up_io:
2928c2ecf20Sopenharmony_ci	up_read(&dev->io_rwsem);
2938c2ecf20Sopenharmony_ci	up(&dev->limit_sem);
2948c2ecf20Sopenharmony_ci	return retval;
2958c2ecf20Sopenharmony_ci}
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_cistatic const struct file_operations lcd_fops = {
2988c2ecf20Sopenharmony_ci	.owner =        THIS_MODULE,
2998c2ecf20Sopenharmony_ci	.read =         lcd_read,
3008c2ecf20Sopenharmony_ci	.write =        lcd_write,
3018c2ecf20Sopenharmony_ci	.open =         lcd_open,
3028c2ecf20Sopenharmony_ci	.unlocked_ioctl = lcd_ioctl,
3038c2ecf20Sopenharmony_ci	.release =      lcd_release,
3048c2ecf20Sopenharmony_ci	.llseek =	 noop_llseek,
3058c2ecf20Sopenharmony_ci};
3068c2ecf20Sopenharmony_ci
3078c2ecf20Sopenharmony_ci/*
3088c2ecf20Sopenharmony_ci * usb class driver info in order to get a minor number from the usb core,
3098c2ecf20Sopenharmony_ci * and to have the device registered with the driver core
3108c2ecf20Sopenharmony_ci */
3118c2ecf20Sopenharmony_cistatic struct usb_class_driver lcd_class = {
3128c2ecf20Sopenharmony_ci	.name =         "lcd%d",
3138c2ecf20Sopenharmony_ci	.fops =         &lcd_fops,
3148c2ecf20Sopenharmony_ci	.minor_base =   USBLCD_MINOR,
3158c2ecf20Sopenharmony_ci};
3168c2ecf20Sopenharmony_ci
3178c2ecf20Sopenharmony_cistatic int lcd_probe(struct usb_interface *interface,
3188c2ecf20Sopenharmony_ci		     const struct usb_device_id *id)
3198c2ecf20Sopenharmony_ci{
3208c2ecf20Sopenharmony_ci	struct usb_lcd *dev = NULL;
3218c2ecf20Sopenharmony_ci	struct usb_endpoint_descriptor *bulk_in, *bulk_out;
3228c2ecf20Sopenharmony_ci	int i;
3238c2ecf20Sopenharmony_ci	int retval;
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_ci	/* allocate memory for our device state and initialize it */
3268c2ecf20Sopenharmony_ci	dev = kzalloc(sizeof(*dev), GFP_KERNEL);
3278c2ecf20Sopenharmony_ci	if (!dev)
3288c2ecf20Sopenharmony_ci		return -ENOMEM;
3298c2ecf20Sopenharmony_ci
3308c2ecf20Sopenharmony_ci	kref_init(&dev->kref);
3318c2ecf20Sopenharmony_ci	sema_init(&dev->limit_sem, USB_LCD_CONCURRENT_WRITES);
3328c2ecf20Sopenharmony_ci	init_rwsem(&dev->io_rwsem);
3338c2ecf20Sopenharmony_ci	init_usb_anchor(&dev->submitted);
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_ci	dev->udev = usb_get_dev(interface_to_usbdev(interface));
3368c2ecf20Sopenharmony_ci	dev->interface = interface;
3378c2ecf20Sopenharmony_ci
3388c2ecf20Sopenharmony_ci	if (le16_to_cpu(dev->udev->descriptor.idProduct) != 0x0001) {
3398c2ecf20Sopenharmony_ci		dev_warn(&interface->dev, "USBLCD model not supported.\n");
3408c2ecf20Sopenharmony_ci		retval = -ENODEV;
3418c2ecf20Sopenharmony_ci		goto error;
3428c2ecf20Sopenharmony_ci	}
3438c2ecf20Sopenharmony_ci
3448c2ecf20Sopenharmony_ci	/* set up the endpoint information */
3458c2ecf20Sopenharmony_ci	/* use only the first bulk-in and bulk-out endpoints */
3468c2ecf20Sopenharmony_ci	retval = usb_find_common_endpoints(interface->cur_altsetting,
3478c2ecf20Sopenharmony_ci			&bulk_in, &bulk_out, NULL, NULL);
3488c2ecf20Sopenharmony_ci	if (retval) {
3498c2ecf20Sopenharmony_ci		dev_err(&interface->dev,
3508c2ecf20Sopenharmony_ci			"Could not find both bulk-in and bulk-out endpoints\n");
3518c2ecf20Sopenharmony_ci		goto error;
3528c2ecf20Sopenharmony_ci	}
3538c2ecf20Sopenharmony_ci
3548c2ecf20Sopenharmony_ci	dev->bulk_in_size = usb_endpoint_maxp(bulk_in);
3558c2ecf20Sopenharmony_ci	dev->bulk_in_endpointAddr = bulk_in->bEndpointAddress;
3568c2ecf20Sopenharmony_ci	dev->bulk_in_buffer = kmalloc(dev->bulk_in_size, GFP_KERNEL);
3578c2ecf20Sopenharmony_ci	if (!dev->bulk_in_buffer) {
3588c2ecf20Sopenharmony_ci		retval = -ENOMEM;
3598c2ecf20Sopenharmony_ci		goto error;
3608c2ecf20Sopenharmony_ci	}
3618c2ecf20Sopenharmony_ci
3628c2ecf20Sopenharmony_ci	dev->bulk_out_endpointAddr = bulk_out->bEndpointAddress;
3638c2ecf20Sopenharmony_ci
3648c2ecf20Sopenharmony_ci	/* save our data pointer in this interface device */
3658c2ecf20Sopenharmony_ci	usb_set_intfdata(interface, dev);
3668c2ecf20Sopenharmony_ci
3678c2ecf20Sopenharmony_ci	/* we can register the device now, as it is ready */
3688c2ecf20Sopenharmony_ci	retval = usb_register_dev(interface, &lcd_class);
3698c2ecf20Sopenharmony_ci	if (retval) {
3708c2ecf20Sopenharmony_ci		/* something prevented us from registering this driver */
3718c2ecf20Sopenharmony_ci		dev_err(&interface->dev,
3728c2ecf20Sopenharmony_ci			"Not able to get a minor for this device.\n");
3738c2ecf20Sopenharmony_ci		goto error;
3748c2ecf20Sopenharmony_ci	}
3758c2ecf20Sopenharmony_ci
3768c2ecf20Sopenharmony_ci	i = le16_to_cpu(dev->udev->descriptor.bcdDevice);
3778c2ecf20Sopenharmony_ci
3788c2ecf20Sopenharmony_ci	dev_info(&interface->dev, "USBLCD Version %1d%1d.%1d%1d found "
3798c2ecf20Sopenharmony_ci		 "at address %d\n", (i & 0xF000)>>12, (i & 0xF00)>>8,
3808c2ecf20Sopenharmony_ci		 (i & 0xF0)>>4, (i & 0xF), dev->udev->devnum);
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_ci	/* let the user know what node this device is now attached to */
3838c2ecf20Sopenharmony_ci	dev_info(&interface->dev, "USB LCD device now attached to USBLCD-%d\n",
3848c2ecf20Sopenharmony_ci		 interface->minor);
3858c2ecf20Sopenharmony_ci	return 0;
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_cierror:
3888c2ecf20Sopenharmony_ci	kref_put(&dev->kref, lcd_delete);
3898c2ecf20Sopenharmony_ci	return retval;
3908c2ecf20Sopenharmony_ci}
3918c2ecf20Sopenharmony_ci
3928c2ecf20Sopenharmony_cistatic void lcd_draw_down(struct usb_lcd *dev)
3938c2ecf20Sopenharmony_ci{
3948c2ecf20Sopenharmony_ci	int time;
3958c2ecf20Sopenharmony_ci
3968c2ecf20Sopenharmony_ci	time = usb_wait_anchor_empty_timeout(&dev->submitted, 1000);
3978c2ecf20Sopenharmony_ci	if (!time)
3988c2ecf20Sopenharmony_ci		usb_kill_anchored_urbs(&dev->submitted);
3998c2ecf20Sopenharmony_ci}
4008c2ecf20Sopenharmony_ci
4018c2ecf20Sopenharmony_cistatic int lcd_suspend(struct usb_interface *intf, pm_message_t message)
4028c2ecf20Sopenharmony_ci{
4038c2ecf20Sopenharmony_ci	struct usb_lcd *dev = usb_get_intfdata(intf);
4048c2ecf20Sopenharmony_ci
4058c2ecf20Sopenharmony_ci	if (!dev)
4068c2ecf20Sopenharmony_ci		return 0;
4078c2ecf20Sopenharmony_ci	lcd_draw_down(dev);
4088c2ecf20Sopenharmony_ci	return 0;
4098c2ecf20Sopenharmony_ci}
4108c2ecf20Sopenharmony_ci
4118c2ecf20Sopenharmony_cistatic int lcd_resume(struct usb_interface *intf)
4128c2ecf20Sopenharmony_ci{
4138c2ecf20Sopenharmony_ci	return 0;
4148c2ecf20Sopenharmony_ci}
4158c2ecf20Sopenharmony_ci
4168c2ecf20Sopenharmony_cistatic void lcd_disconnect(struct usb_interface *interface)
4178c2ecf20Sopenharmony_ci{
4188c2ecf20Sopenharmony_ci	struct usb_lcd *dev = usb_get_intfdata(interface);
4198c2ecf20Sopenharmony_ci	int minor = interface->minor;
4208c2ecf20Sopenharmony_ci
4218c2ecf20Sopenharmony_ci	/* give back our minor */
4228c2ecf20Sopenharmony_ci	usb_deregister_dev(interface, &lcd_class);
4238c2ecf20Sopenharmony_ci
4248c2ecf20Sopenharmony_ci	down_write(&dev->io_rwsem);
4258c2ecf20Sopenharmony_ci	dev->disconnected = 1;
4268c2ecf20Sopenharmony_ci	up_write(&dev->io_rwsem);
4278c2ecf20Sopenharmony_ci
4288c2ecf20Sopenharmony_ci	usb_kill_anchored_urbs(&dev->submitted);
4298c2ecf20Sopenharmony_ci
4308c2ecf20Sopenharmony_ci	/* decrement our usage count */
4318c2ecf20Sopenharmony_ci	kref_put(&dev->kref, lcd_delete);
4328c2ecf20Sopenharmony_ci
4338c2ecf20Sopenharmony_ci	dev_info(&interface->dev, "USB LCD #%d now disconnected\n", minor);
4348c2ecf20Sopenharmony_ci}
4358c2ecf20Sopenharmony_ci
4368c2ecf20Sopenharmony_cistatic struct usb_driver lcd_driver = {
4378c2ecf20Sopenharmony_ci	.name =		"usblcd",
4388c2ecf20Sopenharmony_ci	.probe =	lcd_probe,
4398c2ecf20Sopenharmony_ci	.disconnect =	lcd_disconnect,
4408c2ecf20Sopenharmony_ci	.suspend =	lcd_suspend,
4418c2ecf20Sopenharmony_ci	.resume =	lcd_resume,
4428c2ecf20Sopenharmony_ci	.id_table =	id_table,
4438c2ecf20Sopenharmony_ci	.supports_autosuspend = 1,
4448c2ecf20Sopenharmony_ci};
4458c2ecf20Sopenharmony_ci
4468c2ecf20Sopenharmony_cimodule_usb_driver(lcd_driver);
4478c2ecf20Sopenharmony_ci
4488c2ecf20Sopenharmony_ciMODULE_AUTHOR("Georges Toth <g.toth@e-biz.lu>");
4498c2ecf20Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_VERSION);
4508c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
451