Linux USB设备驱动(二)

Note: 本文主要列举几个usb设备驱动实例

一、“USB LED”驱动模块

在前面的实验室中,开发了一个功能齐全的USB HID设备的固件,该设备能够通过使用HID报告发送和接收数据。现在,将开发一个Linux USB主机驱动程序来控制USB设备。驱动将发送USB命令切换PIC32MX470开发板的LED1,LED2和LED3; 它将通过sysfs条目从Linux用户空间接收命令,然后将其传输到PIC32MX HID设备。命令值可以是0x01、0x02、0x03。HID设备在接收到report中的0x01命令值时打开LED1,在接收到report中的0x02命令值时打开LED2,在接收到report中的0x03命令值时打开LED3。

USB HID设备由PIC32MX开发板实现,开发板链接:https://www.microchip.com/DevelopmentTools/ProductDetails/dm320103#additional-summary,

工程代码获取:

链接:https://pan.baidu.com/s/13SdRewnSWavIv3yCGEIsmQ?pwd=mkuh
提取码:mkuh

你必须阻止 hid-generic 驱动程序获得我们的自定义驱动程序的控制,所以你需要把包括我们的驱动程序的USB_VENDOR_ID和USB_DEVICE_ID添加到列表hid_ignore_list[]。打开内核源码/drivers/hid文件夹下的hidquirks.c文件,并将下一行代码(粗体)添加到列表的末尾:

static const struct hid_device_id hid_ignore_list[] = {
  ...
#if IS_ENABLED(CONFIG_MOUSE_SYNAPTICS_USB)
  { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_TP) },
  { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_INT_TP) },
  { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_CPAD) },
  { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_STICK) },
  { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_WP) },
  { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_COMP_TP) },
  { HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_WTP) },{ HID_USB_DEVICE(USB_VENDOR_ID_SYNAPTICS, USB_DEVICE_ID_SYNAPTICS_DPAD) },
#endif
  { HID_USB_DEVICE(USB_VENDOR_ID_YEALINK, USB_DEVICE_ID_YEALINK_P1K_P4K_B2K) },
  { HID_USB_DEVICE(0x04d8, 0x003f) },
  { }
};

1.1 “USB LED”驱动模块的代码描述

下面将描述驱动程序的主要代码部分:

1. 包含的函数头文件:

#include <linux/slab.h>
#include <linux/module.h>
#include <linux/usb.h>

2. 创建ID Table支持热插拔。Vendor ID和Product ID值必须与PIC32MX USB HID设备中使用的值匹配。

#define USBLED_VENDOR_ID 0x04D8
#define USBLED_PRODUCT_ID 0x003F
/* table of devices that work with this driver */ static const struct usb_device_id id_table[] = {   { USB_DEVICE(USBLED_VENDOR_ID, USBLED_PRODUCT_ID) },   { } }; MODULE_DEVICE_TABLE(usb, id_table);

3.创建一个私有结构来存储驱动程序的数据。

struct usb_led {
  struct usb_device *udev;
  u8 led_number;
};

4. 请参阅下面probe()例程的摘录,包含驱动程序的主要代码行和注释。

static int led_probe(struct usb_interface *interface,
          const struct usb_device_id *id)
{
  /* Get the usb_device structure from the usb_interface one */
  struct usb_device *udev = interface_to_usbdev(interface);
  struct usb_led *dev = NULL;
  int retval = -ENOMEM;
  dev_info(
&interface->dev, "led_probe() function is called.\n");
  
/* Allocate our private data structure */   dev = kzalloc(sizeof(struct usb_led), GFP_KERNEL);
  /* store the usb device in our data structure */   dev->udev = usb_get_dev(udev);
  /* Attach the USB device data to the USB interface */   usb_set_intfdata(interface, dev);
  /* create a led sysfs entry to interact with the user space */   device_create_file(&interface->dev, &dev_attr_led);
  return 0; }

5. 编写led_store()函数。每当你的用户空间应用程序写入usb设备下的led sysfs条目(/sys/bus/usb/devices/1-1.3:1.0/led)时,驱动程序的led_store()函数就会被调用。通过使用usb_get_intfdata()函数恢复与USB设备关联的usb_led结构。写入led sysfs条目的命令存储在val变量中。最后,您将使用usb_bulk_msg()函数通过USB发送命令值。

内核提供了两个usb_bulk_msg()和usb_control_msg()帮助函数,使得传输简单的批量和控制消息成为可能,而不必创建urb结构、初始化它、提交它并等待它的完成处理程序。这些函数是同步的,会使代码休眠。你不能从中断上下文或者自旋锁中调用它们。

int usb_bulk_msg(struct usb_device * usb_dev, unsigned int pipe, void * data,
int len, int * actual_length, int timeout);

下面是usb_bulk_msg()参数的简述:

  • usb_dev:指向要发送消息的usb设备的指针
  • pipe:端点“pipe”,将消息发送到它。
  • data:要发送数据的指针
  • len:要发送的数据的字节长度
  • actual_length:指向一个位置的指针,该位置用于放置以字节为单位传输的实际长度
  • timeout:在消息超时前等待消息完成的时间(以毫秒为单位)

下面是led_store()例程的摘录:

static ssize_t led_store(struct device *dev, struct device_attribute *attr,
            const char *buf, size_t count)
{
  struct usb_interface *intf = to_usb_interface(dev);
  struct usb_led *led = usb_get_intfdata(intf);
  u8 val;
  
/* transform char array to u8 value */   kstrtou8(buf, 10, &val);   led->led_number = val;
  
/* Toggle led */   usb_bulk_msg(led->udev, usb_sndctrlpipe(led->udev, 1),           &led->led_number,           1,           NULL,           0);
  
return count; } static DEVICE_ATTR_RW(led);

6. 添加一个usb_driver结构,它将被注册到USB核心:

static struct usb_driver led_driver = {
  .name = "usbled",
  .probe = led_probe,
  .disconnect = led_disconnect,
  .id_table = id_table,
};

7. 用USB总线注册你的驱动:

module_usb_driver(led_driver);

8. 构建模块并将其加载到目标处理器。

1.2 "USB LED" 驱动源码(usb_led.c)

#include <linux/slab.h>
#include <linux/module.h>
#include <linux/usb.h>

#define USBLED_VENDOR_ID    0x04D8    
#define USBLED_PRODUCT_ID    0x003F    

/* table of devices that work with this driver */
static const struct usb_device_id id_table[] = {
    { USB_DEVICE(USBLED_VENDOR_ID, USBLED_PRODUCT_ID) },
    { }
};
MODULE_DEVICE_TABLE(usb, id_table);

struct usb_led {
    struct usb_device *udev;
    u8 led_number;
};

static ssize_t led_show(struct device *dev, struct device_attribute *attr,
              char *buf)
{
    struct usb_interface *intf = to_usb_interface(dev);
    struct usb_led *led = usb_get_intfdata(intf);            
                                    
    return sprintf(buf, "%d\n", led->led_number);
}

static ssize_t led_store(struct device *dev, struct device_attribute *attr,
             const char *buf, size_t count)
{
    struct usb_interface *intf = to_usb_interface(dev);
    struct usb_led *led = usb_get_intfdata(intf);
    u8 val;
    int error, retval;
    dev_info(&intf->dev, "led_store() function is called.\n");
    
    /* transform char array to u8 value */
    error = kstrtou8(buf, 10, &val);
    if (error)
        return error;
    
    led->led_number = val;

    if (val == 1 || val == 2 || val == 3)
        dev_info(&led->udev->dev, "led = %d\n", led->led_number);
    else {
        dev_info(&led->udev->dev, "unknown led %d\n", led->led_number);
        retval = -EINVAL;
        return retval;
    }

    /* Toggle led */
    retval = usb_bulk_msg(led->udev, usb_sndctrlpipe(led->udev, 1),
                 &led->led_number, 
                 1,
                 NULL, 
                 0);
    if (retval) {
        retval = -EFAULT;
        return retval;
    }
    return count;
}
static DEVICE_ATTR_RW(led);

static int led_probe(struct usb_interface *interface,
             const struct usb_device_id *id)
{
    struct usb_device *udev = interface_to_usbdev(interface);
    struct usb_led *dev = NULL;
    int retval = -ENOMEM;

    dev_info(&interface->dev, "led_probe() function is called.\n");

    dev = kzalloc(sizeof(struct usb_led), GFP_KERNEL);
    if (!dev) {
        dev_err(&interface->dev, "out of memory\n");
        retval = -ENOMEM;
        goto error;
    }

    dev->udev = usb_get_dev(udev);

    usb_set_intfdata(interface, dev);
    
    retval = device_create_file(&interface->dev, &dev_attr_led);
    if (retval)
        goto error_create_file;

    return 0;

error_create_file:
    usb_put_dev(udev);
    usb_set_intfdata(interface, NULL);
error:
    kfree(dev);
    return retval;
}

static void led_disconnect(struct usb_interface *interface)
{
    struct usb_led *dev;

    dev = usb_get_intfdata(interface);

    device_remove_file(&interface->dev, &dev_attr_led);
    usb_set_intfdata(interface, NULL);
    usb_put_dev(dev->udev);
    kfree(dev);

    dev_info(&interface->dev, "USB LED now disconnected\n");
}

static struct usb_driver led_driver = {
    .name =        "usbled",
    .probe =    led_probe,
    .disconnect =    led_disconnect,
    .id_table =    id_table,
};

module_usb_driver(led_driver);

MODULE_DESCRIPTION("This is a synchronous led usb controlled module");
MODULE_LICENSE("GPL");

1.3 usb_led.ko使用示例

/* Keep the PIC32MX470 board powered off */
root@raspberrypi:/home# insmod usb_led.ko /* load the module */
usb_led: loading out-of-tree module taints kernel.
usbcore: registered new interface driver usbled
/* power now the PIC32MX Curiosity board */ root@raspberrypi:/home# usb 1-1.3: new full-speed USB device number 5 using dwc_otg usb 1-1.3: New USB device found, idVendor=04d8, idProduct=003f, bcdDevice= 1.00 usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=0 usb 1-1.3: Product: LED_USB HID Demo usb 1-1.3: Manufacturer: Microchip Technology Inc. usbled 1-1.3:1.0: led_probe() function is called.
/* check the new created USB device */ root@raspberrypi:/home# cd /sys/bus/usb/devices/1-1.3:1.0 root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# ls authorized bInterfaceProtocol ep_01 power bAlternateSetting bInterfaceSubClass ep_81 subsystem bInterfaceClass bNumEndpoints led supports_autosuspend bInterfaceNumber driver modalias uevent /* Read the configurations of the USB device */ root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# cat bNumEndpoints 02 root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0/ep_01# cat direction out root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0/ep_81# cat direction in root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# cat bAlternateSetting 0 root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# cat bInterfaceClass 03 root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# cat bNumEndpoints 02
/* Switch on the LED1 of the PIC32MX Curiosity board */ root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# echo 1 > led usbled 1-1.3:1.0: led_store() function is called. usb 1-1.3: led = 1
/* Switch on the LED2 of the PIC32MX Curiosity board */ root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# echo 2 > led usbled 1-1.3:1.0: led_store() function is called. usb 1-1.3: led = 2
/* Switch on the LED3 of the PIC32MX Curiosity board */ root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# echo 3 > led usbled 1-1.3:1.0: led_store() function is called. usb 1-1.3: led = 3
/* read the led status */ root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# cat led 3
root@raspberrypi:
/home# rmmod usb_led.ko /* remove the module */ usbcore: deregistering interface driver usbled usbled 1-1.3:1.0: USB LED now disconnected

二、“USB LED and Switch”驱动模块 

在这个新的实验中,您将增加先前驱动程序的功能。除了控制连接到USB设备的三个led外,Linux主机驱动程序还将从USB HID设备接收一个Pushbutton (PIC32MX470 Curiosity Development Board上的S1开关)状态。驱动程序将发送一个值为0x00的命令到USB设备,然后HID设备将回复一个report,其中第一个字节是S1按钮的状态(“0x00”按下,“0x01”未按下)。在这个驱动程序中,与前一个驱动程序不同,主机和设备之间的通信是通过使用USB请求块(urbs)异步完成的。

2.1 “USB LED and Switch”驱动模块的代码描述

 驱动程序的主要代码部分如下:

1. 包含的函数头文件:

#include <linux/slab.h>
#include <linux/module.h>
#include <linux/usb.h>

2. 创建ID Table支持热插拔。Vendor ID和Product ID值必须与PIC32MX USB HID设备中使用的值匹配。

#define USBLED_VENDOR_ID 0x04D8
#define USBLED_PRODUCT_ID 0x003F

/* table of devices that work with this driver */
static const struct usb_device_id id_table[] = {
    { USB_DEVICE(USBLED_VENDOR_ID, USBLED_PRODUCT_ID) },
    { }
};
MODULE_DEVICE_TABLE(usb, id_table);

3.创建一个私有结构来存储驱动程序的数据。

struct usb_led {
    struct usb_device *udev;
    struct usb_interface *intf;struct urb *interrupt_out_urb;
    struct urb *interrupt_in_urb;
    struct usb_endpoint_descriptor *interrupt_out_endpoint;
    struct usb_endpoint_descriptor *interrupt_in_endpoint;
    u8 irq_data;
    u8 led_number;
    u8 ibuffer;
    int interrupt_out_interval;
    int ep_in;
    int ep_out;
};

4. 请参阅下面probe()例程的摘录,包含驱动程序的主要代码行和注释。

static int led_probe(struct usb_interface *intf,
                        const struct usb_device_id *id)
{
    struct usb_device *udev = interface_to_usbdev(intf);

    /* Get the current altsetting of the USB interface */
    struct usb_host_interface *altsetting = intf->cur_altsetting;
    struct usb_endpoint_descriptor *endpoint;
    struct usb_led *dev = NULL;
    int ep;
    int ep_in, ep_out;
    int size;

    /*
     * Find the last interrupt out endpoint descriptor
     * to check its number and its size
     * Just for teaching purposes
     */
    usb_find_last_int_out_endpoint(altsetting, &endpoint);

    /* get the endpoint's number */
    ep = usb_endpoint_num(endpoint); /* value from 0 to 15, it is 1 */
    size = usb_endpoint_maxp(endpoint);

    /* Validate endpoint and size */
    if (size <= 0) {
        dev_info(&intf->dev, "invalid size (%d)", size);
        return -ENODEV;
    }

    dev_info(&intf->dev, "endpoint size is (%d)", size);
    dev_info(&intf->dev, "endpoint number is (%d)", ep);

    /* Get the two addresses (IN and OUT) of the Endpoint 1 */
    ep_in = altsetting->endpoint[0].desc.bEndpointAddress;
    ep_out = altsetting->endpoint[1].desc.bEndpointAddress;

    /* Allocate our private data structure */
    dev = kzalloc(sizeof(struct usb_led), GFP_KERNEL);

    /* Store values in the data structure */
    dev->ep_in = ep_in;
    dev->ep_out = ep_out;
    dev->udev = usb_get_dev(udev);
    dev->intf = intf;

    /* allocate the int_out_urb structure */
    dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL);

    /* initialize the int_out_urb */
    usb_fill_int_urb(dev->interrupt_out_urb,
                        dev->udev,
                        usb_sndintpipe(dev->udev, ep_out),
                        (void *)&dev->irq_data,
                        1,
                        led_urb_out_callback, dev, 1);

    /* allocate the int_in_urb structure */
    dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL);
    if (!dev->interrupt_in_urb)
        goto error_out;

    /* initialize the int_in_urb */
    usb_fill_int_urb(dev->interrupt_in_urb,
                        dev->udev,
                        usb_rcvintpipe(dev->udev, ep_in),
                        (void *)&dev->ibuffer,
                        1,
                        led_urb_in_callback, dev, 1);

    /* Attach the device data to the interface */
    usb_set_intfdata(intf, dev);

    /* create the led sysfs entry to interact with the user space */
    device_create_file(&intf->dev, &dev_attr_led);

    /* Submit the interrrupt IN URB */
    usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL);

    return 0;
}                                                           

5. 编写led_store()函数。每当你的用户空间应用程序写入usb设备下的led sysfs条目(/sys/bus/usb/devices/1-1.3:1.0/led)时,驱动程序的led_store()函数就会被调用。通过使用usb_get_intfdata()函数恢复与USB设备关联的usb_led结构。写入led sysfs条目的命令存储在irq_data变量中。最后,您将使用usb_submit_urb()函数通过USB发送命令值。

下面是led_store()例程的摘录:

static ssize_t led_store(struct device *dev, struct device_attribute *attr,
                             const char *buf, size_t count)
{
    struct usb_interface *intf = to_usb_interface(dev);
    struct usb_led *led = usb_get_intfdata(intf);
    u8 val;

    /* transform char array to u8 value */
    kstrtou8(buf, 10, &val);

    led->irq_data = val;

    /* send the data out */
    retval = usb_submit_urb(led->interrupt_out_urb, GFP_KERNEL);

    return count;
}
static DEVICE_ATTR_RW(led);    

6. 创建OUT和IN URB的完成回调。中断OUT完成回调仅仅检查URB状态并返回。中断IN完成回调检查URB状态,然后读取ibuffer以了解从PIC32MX板的S1开关接收到的状态,最后重新提交中断IN URB。

static void led_urb_out_callback(struct urb *urb)
{
    struct usb_led *dev;

    dev = urb->context;

    /* sync/async unlink faults aren't errors */
    if (urb->status) {
        if (!(urb->status == -ENOENT ||
            urb->status == -ECONNRESET ||
            urb->status == -ESHUTDOWN))
                dev_err(&dev->udev->dev,
                        "%s - nonzero write status received: %d\n",
                        __func__, urb->status);}
}

static void led_urb_in_callback(struct urb *urb)
{
    int retval;
    struct usb_led *dev;

    dev = urb->context;

    if (urb->status) {
        if (!(urb->status == -ENOENT ||
            urb->status == -ECONNRESET ||
            urb->status == -ESHUTDOWN))
                dev_err(&dev->udev->dev,
                        "%s - nonzero write status received: %d\n",
                        __func__, urb->status);
    }

    if (dev->ibuffer == 0x00)
        pr_info ("switch is ON.\n");
    else if (dev->ibuffer == 0x01)
        pr_info ("switch is OFF.\n");
    else
        pr_info ("bad value received\n");

    usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL);
}    

7. 添加一个struct usb_driver结构,它将被注册到USB核心:

static struct usb_driver led_driver = {
    .name = "usbled",
    .probe = led_probe,
    .disconnect = led_disconnect,
    .id_table = id_table,
};

7. 用USB总线注册你的驱动:

module_usb_driver(led_driver);

8. 构建模块并将其加载到目标处理器。

2.2 “USB LED and Switch”驱动程序源代码(usb_urb_int_ledc)

#include <linux/slab.h>
#include <linux/module.h>
#include <linux/usb.h>

#define USBLED_VENDOR_ID    0x04D8    
#define USBLED_PRODUCT_ID    0x003F    

static void led_urb_out_callback(struct urb *urb);
static void led_urb_in_callback(struct urb *urb);

/* table of devices that work with this driver */
static const struct usb_device_id id_table[] = {
    { USB_DEVICE(USBLED_VENDOR_ID, USBLED_PRODUCT_ID) },
    { }
};
MODULE_DEVICE_TABLE(usb, id_table);

struct usb_led {
    struct usb_device *udev;
    struct usb_interface *intf;
    struct urb       *interrupt_out_urb;
    struct urb       *interrupt_in_urb;
    struct usb_endpoint_descriptor *interrupt_out_endpoint;
    struct usb_endpoint_descriptor *interrupt_in_endpoint;
    u8          irq_data;
    u8          led_number;
    u8           ibuffer;
    int          interrupt_out_interval;
    int ep_in;
    int ep_out;
};

static ssize_t led_show(struct device *dev, struct device_attribute *attr,
              char *buf)
{
    struct usb_interface *intf = to_usb_interface(dev);
    struct usb_led *led = usb_get_intfdata(intf);

    return sprintf(buf, "%d\n", led->led_number);
}

static ssize_t led_store(struct device *dev, struct device_attribute *attr,
             const char *buf, size_t count)
{
    /* interface: related set of endpoints which present a single feature or function to the host */
    struct usb_interface *intf = to_usb_interface(dev);
    struct usb_led *led = usb_get_intfdata(intf);
    u8 val;
    int error, retval;
    
    dev_info(&intf->dev, "led_store() function is called.\n");
    
    /* transform char array to u8 value */
    error = kstrtou8(buf, 10, &val);
    if (error)
        return error;
    
    led->led_number = val;
    led->irq_data = val;

    if (val == 0)
        dev_info(&led->udev->dev, "read status\n");
    else if (val == 1 || val == 2 || val == 3)
        dev_info(&led->udev->dev, "led = %d\n", led->led_number);
    else {
        dev_info(&led->udev->dev, "unknown value %d\n", val);
        retval = -EINVAL;
        return retval;
    }
    
    /* send the data out */
    retval = usb_submit_urb(led->interrupt_out_urb, GFP_KERNEL);
    if (retval) {
        dev_err(&led->udev->dev,
              "Couldn't submit interrupt_out_urb %d\n", retval);
        return retval;
    }

    return count;
}
static DEVICE_ATTR_RW(led);

static void led_urb_out_callback(struct urb *urb)
{
    struct usb_led *dev;

    dev = urb->context;

    dev_info(&dev->udev->dev, "led_urb_out_callback() function is called.\n");

    /* sync/async unlink faults aren't errors */
    if (urb->status) {
        if (!(urb->status == -ENOENT ||
            urb->status == -ECONNRESET ||
            urb->status == -ESHUTDOWN))
            dev_err(&dev->udev->dev,
                  "%s - nonzero write status received: %d\n",
                  __func__, urb->status);
    }
}

static void led_urb_in_callback(struct urb *urb)
{
    int retval;
    struct usb_led *dev;

    dev = urb->context;

    dev_info(&dev->udev->dev, "led_urb_in_callback() function is called.\n");

    if (urb->status) {
        if (!(urb->status == -ENOENT ||
            urb->status == -ECONNRESET ||
            urb->status == -ESHUTDOWN))
            dev_err(&dev->udev->dev,
                  "%s - nonzero write status received: %d\n",
                  __func__, urb->status);
    }

    if (dev->ibuffer == 0x00)
        pr_info ("switch is ON.\n");
    else if (dev->ibuffer == 0x01)
        pr_info ("switch is OFF.\n");
    else
        pr_info ("bad value received\n");

    retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL);
    if (retval) 
            dev_err(&dev->udev->dev,
                "Couldn't submit interrupt_in_urb %d\n", retval);
}

static int led_probe(struct usb_interface *intf,
             const struct usb_device_id *id)
{
    struct usb_device *udev = interface_to_usbdev(intf);
    struct usb_host_interface *altsetting = intf->cur_altsetting;
    struct usb_endpoint_descriptor *endpoint;
    struct usb_led *dev = NULL;
    int ep;
    int ep_in, ep_out;
    int retval, size, res;
    retval = 0;

    dev_info(&intf->dev, "led_probe() function is called.\n");

    res = usb_find_last_int_out_endpoint(altsetting, &endpoint);
    if (res) {
        dev_info(&intf->dev, "no endpoint found");
        return res;
    }

    ep = usb_endpoint_num(endpoint); /* value from 0 to 15, it is 1 */
    size = usb_endpoint_maxp(endpoint);

    /* Validate endpoint and size */
    if (size <= 0) {
        dev_info(&intf->dev, "invalid size (%d)", size);
        return -ENODEV;
    }

    dev_info(&intf->dev, "endpoint size is (%d)", size);
    dev_info(&intf->dev, "endpoint number is (%d)", ep);

    ep_in = altsetting->endpoint[0].desc.bEndpointAddress;
    ep_out = altsetting->endpoint[1].desc.bEndpointAddress;

    dev_info(&intf->dev, "endpoint in address is (%d)", ep_in);
    dev_info(&intf->dev, "endpoint out address is (%d)", ep_out);

    dev = kzalloc(sizeof(struct usb_led), GFP_KERNEL);

    if (!dev) 
        return -ENOMEM;

    dev->ep_in = ep_in;
    dev->ep_out = ep_out;

    dev->udev = usb_get_dev(udev);

    dev->intf = intf;

    /* allocate int_out_urb structure */
    dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL);
    if (!dev->interrupt_out_urb)
        goto error_out;

    /* initialize int_out_urb */
    usb_fill_int_urb(dev->interrupt_out_urb, 
            dev->udev, 
            usb_sndintpipe(dev->udev, ep_out), 
            (void *)&dev->irq_data,
            1,
            led_urb_out_callback, dev, 1);

    /* allocate int_in_urb structure */
    dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL);
    if (!dev->interrupt_in_urb)
        goto error_out;

    /* initialize int_in_urb */
    usb_fill_int_urb(dev->interrupt_in_urb, 
            dev->udev, 
            usb_rcvintpipe(dev->udev, ep_in), 
            (void *)&dev->ibuffer,
            1,
            led_urb_in_callback, dev, 1);

    usb_set_intfdata(intf, dev);
    
    retval = device_create_file(&intf->dev, &dev_attr_led);
    if (retval)
        goto error_create_file;

    retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL);
    if (retval) {
        dev_err(&dev->udev->dev,
              "Couldn't submit interrupt_in_urb %d\n", retval);
        device_remove_file(&intf->dev, &dev_attr_led);
        goto error_create_file;
    }
    
    dev_info(&dev->udev->dev,"int_in_urb submitted\n");

    return 0;

error_create_file:
    usb_free_urb(dev->interrupt_out_urb);
    usb_free_urb(dev->interrupt_in_urb);
    usb_put_dev(udev);
    usb_set_intfdata(intf, NULL);

error_out:
    kfree(dev);
    return retval;
}

static void led_disconnect(struct usb_interface *interface)
{
    struct usb_led *dev;

    dev = usb_get_intfdata(interface);

    device_remove_file(&interface->dev, &dev_attr_led);
    usb_free_urb(dev->interrupt_out_urb);
    usb_free_urb(dev->interrupt_in_urb);
    usb_set_intfdata(interface, NULL);
    usb_put_dev(dev->udev);
    kfree(dev);

    dev_info(&interface->dev, "USB LED now disconnected\n");
}

static struct usb_driver led_driver = {
    .name =        "usbled",
    .probe =    led_probe,
    .disconnect =    led_disconnect,
    .id_table =    id_table,
};

module_usb_driver(led_driver);

MODULE_DESCRIPTION("This is a led/switch usb controlled module with irq in/out endpoints");
MODULE_LICENSE("GPL");

2.3 usb_urb_int_led.ko使用示例

/* Keep the PIC32MX470 board powered off */
root@raspberrypi:/home# insmod usb_urb_int_led.ko /* load the module */ usb_urb_int_led: loading out-of-tree module taints kernel. usbcore: registered new interface driver usbled /* power now the PIC32MX Curiosity board */ root@raspberrypi:/home# usb 1-1.3: new full-speed USB device number 4 using dwc_otg usb 1-1.3: New USB device found, idVendor=04d8, idProduct=003f, bcdDevice= 1.00 usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=0 usb 1-1.3: Product: LED_USB HID Demo usb 1-1.3: Manufacturer: Microchip Technology Inc. usbled 1-1.3:1.0: led_probe() function is called. usbled 1-1.3:1.0: endpoint size is (64) usbled 1-1.3:1.0: endpoint number is (1) usbled 1-1.3:1.0: endpoint in address is (129)
usbled 1-1.3:1.0: endpoint out address is (1) usb 1-1.3: int_in_urb submitted /* Go to the new created USB device */ root@raspberrypi:/home# cd /sys/bus/usb/devices/1-1.3:1.0 /* Switch on the LED1 of the PIC32MX Curiosity board */ root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# echo 1 > led usbled 1-1.3:1.0: led_store() function is called. usb 1-1.3: led = 1 usb 1-1.3: led_urb_out_callback() function is called. /* Switch on the LED2 of the PIC32MX Curiosity board */ root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# echo 2 > led usbled 1-1.3:1.0: led_store() function is called. usb 1-1.3: led = 2 usb 1-1.3: led_urb_out_callback() function is called. /* Switch on the LED3 of the PIC32MX Curiosity board */ root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# echo 3 > led usbled 1-1.3:1.0: led_store() function is called. usb 1-1.3: led = 3 usb 1-1.3: led_urb_out_callback() function is called. /* Keep pressed the S1 switch of PIC32MX Curiosity board and get SW status*/ root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# echo 0 > led usbled 1-1.3:1.0: led_store() function is called. usb 1-1.3: read status usb 1-1.3: led_urb_out_callback() function is called. usb 1-1.3: led_urb_in_callback() function is called. switch is ON. /* Release the S1 switch of PIC32MX Curiosity board and get SW status */ root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# echo 0 > led usbled 1-1.3:1.0: led_store() function is called. usb 1-1.3: read status usb 1-1.3: led_urb_out_callback() function is called. usb 1-1.3: led_urb_in_callback() function is called. switch is OFF. root@raspberrypi:/home# rmmod usb_urb_int_led.ko /* remove the module */ usbcore: deregistering interface driver usbled usb 1-1.3: led_urb_in_callback() function is called. switch is OFF. usb 1-1.3: Couldn't submit interrupt_in_urb -1 usbled 1-1.3:1.0: USB LED now disconnected

三、"I2C to USB Multidisplay LED"驱动模块

在本实验中,您将编写一个Linux USB驱动程序,该驱动程序由用户空间控制,使用I2C Tools for Linux; 要执行此任务,您必须在已创建的USB驱动程序中创建一个新的I2C适配器。

驱动程序模型是递归的。在下面的内容中,你可以看到集成了USB转I2C转换器的PCI board,控制I2C设备所需的所有驱动程序。以下是创建这个递归驱动程序模型的主要步骤:

  • 首先,您必须开发一个PCI设备驱动程序,它将创建一个USB适配器(PCI设备驱动程序是USB适配器驱动程序的父驱动程序)。
  • 其次,你必须开发一个USB设备驱动程序,通过USB核心将USB数据发送到USB适配器驱动程序;这个USB设备驱动程序还将创建一个I2C适配器驱动程序(USB设备驱动程序是I2C适配器驱动程序的父驱动程序)。
  • 最后,您将创建一个I2C设备驱动程序,它将通过I2C核心将数据发送到I2C适配器驱动程序,并将创建一个struct file_operations结构来定义驱动程序的函数,这些函数在Linux用户空间读取和写入字符设备时被调用。

 

 

 这个递归模型将在本实验的驱动程序中得到简化,您只需要执行前面提到的三个步骤中的第二步。在这个驱动程序中,主机和设备之间的通信是通过使用中断OUT URB异步完成的。

 本实验中,还需要一个LTC3206开发板,LTC3206 DC749A - Demo Board (http://www.analog.com/en/designcenter/evaluation-hardware-and-software/evaluation-boards-kits/dc749a.html)。将LTC3206开发板通过i2c总线连接到PIC32MX470开发板上。

 3.1 “I2C to USB Multidisplay LED”驱动模块的代码描述

驱动程序的主要代码部分如下:

1. 包含的函数头文件:

#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/i2c.h>

2. 创建ID Table支持热插拔。Vendor ID和Product ID值必须与PIC32MX USB HID设备中使用的值匹配。

#define USBLED_VENDOR_ID 0x04D8
#define USBLED_PRODUCT_ID 0x003F

/* table of devices that work with this driver */
static const struct usb_device_id id_table[] = {
    { USB_DEVICE(USBLED_VENDOR_ID, USBLED_PRODUCT_ID) },
    { }
};
MODULE_DEVICE_TABLE(usb, id_table);

3.创建一个私有结构来存储驱动程序的数据。

struct i2c_ltc3206 {
    u8 obuffer[LTC3206_OUTBUF_LEN]; /* USB write buffer */
    /* I2C/SMBus data buffer */
    u8 user_data_buffer[LTC3206_I2C_DATA_LEN]; /* LEN is 3 bytes */
    int ep_out; /* out endpoint */
    struct usb_device *usb_dev; /* the usb device for this device */
    struct usb_interface *interface;/* the interface for this device */
    struct i2c_adapter adapter; /* i2c related things */
    /* wq to wait for an ongoing write */
    wait_queue_head_t usb_urb_completion_wait;
    bool ongoing_usb_ll_op; /* all is in progress */
    struct urb *interrupt_out_urb; /* interrupt out URB */
};

4. 请参阅下面probe()例程的摘录,包含驱动程序的主要代码行和注释。

static int ltc3206_probe(struct usb_interface *interface,
                        const struct usb_device_id *id)
{
    /* Get the current altsetting of the USB interface */
    struct usb_host_interface *hostif = interface->cur_altsetting;
    struct i2c_ltc3206 *dev; /* the data structure */

    /* allocate data memory for our USB device and initialize it */
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);

    /* get interrupt ep_out address */
    dev->ep_out = hostif->endpoint[1].desc.bEndpointAddress;
    dev->usb_dev = usb_get_dev(interface_to_usbdev(interface));
    dev->interface = interface;

    /* declare dynamically a wait queue */
    init_waitqueue_head(&dev->usb_urb_completion_wait);

    /* save our data pointer in this USB interface device */
    usb_set_intfdata(interface, dev);

    /* setup I2C adapter description */
    dev->adapter.owner = THIS_MODULE;
    dev->adapter.class = I2C_CLASS_HWMON;
    dev->adapter.algo = &ltc3206_usb_algorithm;
    i2c_set_adapdata(&dev->adapter, dev);

    /* Attach the I2C adapter to the USB interface */
    dev->adapter.dev.parent = &dev->interface->dev;

    /* initialize the I2C device */
    ltc3206_init(dev);

    /* and finally attach the adapter to the I2C layer */
    i2c_add_adapter(&dev->adapter);

    return 0;
}

5. 编写ltc3206_init()函数。在这个函数中,您将分配和初始化中断OUT URB,它用于主机和设备之间的通信。请看下面ltc3206_init()例程的摘录:

static int ltc3206_init(struct i2c_ltc3206 *dev)
{
    /* allocate int_out_urb structure */
    interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL);

    /* initialize int_out_urb structure */
    usb_fill_int_urb(dev->interrupt_out_urb, dev->usb_dev,
                        usb_sndintpipe(dev->usb_dev,
                        dev->ep_out),
                        (void *)&dev->obuffer, LTC3206_OUTBUF_LEN,
                        ltc3206_usb_cmpl_cbk, dev,
                        1);

    return 0;
}

6. 创建一个表示I2C传输方法的i2c_algorithm结构体。你将在这个结构中初始化两个变量:

  • master_xfer: 向msgs数组定义的给定i2c适配器发出一组i2c事务,其中num个消息可通过adap指定的适配器传输。
  • functionality: 根据I2C_FUNC_*标志,返回此algorithm/adapter对支持的标志。
static const struct i2c_algorithm ltc3206_usb_algorithm = {
    .master_xfer = ltc3206_usb_i2c_xfer,
    .functionality = ltc3206_usb_func,
};

7. 编写ltc3206_usb_i2c_xfer()函数。每次从Linux用户空间向I2C适配器写入数据时,都会调用此函数。该函数将调用ltc32016_i2c_write(),将从Linux用户空间接收到的I2C数据存储在obuffer[ ] char数组中,然后ltc32016_i2c_write()将调用ltc3206_ll_cmd(),将中断OUT URB提交给USB设备并等待URB的完成。

static int ltc3206_usb_i2c_xfer(struct i2c_adapter *adap,
                struct i2c_msg *msgs, int num)
{
    /* get the private data structure */
    struct i2c_ltc3206 *dev = i2c_get_adapdata(adap);
    struct i2c_msg *pmsg;
    int ret, count;

    pr_info("number of i2c msgs is = %d\n", num);

    for (count = 0; count < num; count++) {
        pmsg = &msgs[count];
        ret = ltc3206_i2c_write(dev, pmsg);
        if (ret < 0)
            goto abort;
} 

    /* if all the messages were transferred ok, return "num" */
    ret = num;
abort:
    return ret;
}

static int ltc3206_i2c_write(struct i2c_ltc3206 *dev,
                        struct i2c_msg *pmsg)
{
    u8 ucXferLen;
    int rv;
    u8 *pSrc, *pDst;

    /* I2C write lenght */
    ucXferLen = (u8)pmsg->len;

    pSrc = &pmsg->buf[0];
    pDst = &dev->obuffer[0];
    memcpy(pDst, pSrc, ucXferLen);

    pr_info("oubuffer[0] = %d\n", dev->obuffer[0]);
    pr_info("oubuffer[1] = %d\n", dev->obuffer[1]);
    pr_info("oubuffer[2] = %d\n", dev->obuffer[2]);

    rv = ltc3206_ll_cmd(dev);
    if (rv < 0)
        return -EFAULT;
    
    return 0;
}

static int ltc3206_ll_cmd(struct i2c_ltc3206 *dev)
{
    int rv;

    /* tell everybody to leave the URB alone
     * we are going to write to the LTC3206
     */
    dev->ongoing_usb_ll_op = 1; /* doing USB communication */

    /* submit the interrupt out ep packet */
    if (usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL)) {
        dev_err(&dev->interface->dev,
                "ltc3206(ll): usb_submit_urb intr out failed\n");
      dev->ongoing_usb_ll_op = 0;
      return -EIO;
  }

    /* wait for its completion, the USB URB callback will signal it */
    rv = wait_event_interruptible(dev->usb_urb_completion_wait,
                    (!dev->ongoing_usb_ll_op));
    if (rv < 0) {
        dev_err(&dev->interface->dev, "ltc3206(ll): wait
                interrupted\n");
        goto ll_exit_clear_flag;
    }

    return 0;

ll_exit_clear_flag:
    dev->ongoing_usb_ll_op = 0;
    return rv;
}     

8. 创建中断OUT URB的完成回调。完成回调检查URB状态,如果存在错误状态,则重新提交URB。如果传输成功,回调将唤醒休眠进程并返回。

static void ltc3206_usb_cmpl_cbk(struct urb *urb)
{
    struct i2c_ltc3206 *dev = urb->context;
    int status = urb->status;
    int retval;

    switch (status) {
    case 0: /* success */
        break;
    case -ECONNRESET: /* unlink */
    case -ENOENT:
    case -ESHUTDOWN:
        return;
    /* -EPIPE: should clear the halt */
    default: /* error */
        goto resubmit;
    }

    /*
     * wake up the waiting function
     * modify the flag indicating the ll status
     */
    dev->ongoing_usb_ll_op = 0; /* communication is OK */
    wake_up_interruptible(&dev->usb_urb_completion_wait);
    return;

resubmit:
    retval = usb_submit_urb(urb, GFP_ATOMIC);
    if (retval) {
        dev_err(&dev->interface->dev,
                "ltc3206(irq): can't resubmit intrerrupt urb, retval %d\n", retval);
    }
}  

9. 添加一个usb_driver结构,它将被注册到USB核心:

static struct usb_driver ltc3206_driver = {
    .name = DRIVER_NAME,
    .probe = ltc3206_probe,
    .disconnect = ltc3206_disconnect,
    .id_table = ltc3206_table,
};                            

10. 用USB总线注册你的驱动程序:

module_usb_driver(ltc3206_driver);

11. 构建模块并将其加载到目标处理器。

3.2 "I2C to USB Multidisplay LED"驱动源码(usb_ltc3206.c)

#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/i2c.h>

/* i2cset -y 4 0x1b 0x00 0xf0 0x00 i -> this is a full I2C block write blue and toggle the leds
i2cset -y 4 0x1b 0xf0 0x00 0x00 i -> red full 
i2cset -y 4 0x1b 0x10 0x00 0x00 i -> red low
i2cset -y 4 0x1b 0x00 0x0f 0x00 i -> green full
i2cset -y 4 0x1b 0x00 0x0f 0x0f i -> sub and green full
i2cset -y 4 0x1b 0x00 0x00 0xf0 i -> main full */

#define DRIVER_NAME    "usb-ltc3206"

#define USB_VENDOR_ID_LTC3206        0x04d8
#define USB_DEVICE_ID_LTC3206        0x003f

#define LTC3206_OUTBUF_LEN        3    /* USB write packet length */
#define LTC3206_I2C_DATA_LEN        3

/* Structure to hold all of our device specific stuff */
struct i2c_ltc3206 {
    u8 obuffer[LTC3206_OUTBUF_LEN];    /* USB write buffer */
    /* I2C/SMBus data buffer */
    u8 user_data_buffer[LTC3206_I2C_DATA_LEN];
    int ep_out;                  /* out endpoint */
    struct usb_device *usb_dev;    /* the usb device for this device */
    struct usb_interface *interface;/* the interface for this device */
    struct i2c_adapter adapter;    /* i2c related things */
    /* wq to wait for an ongoing write */
    wait_queue_head_t usb_urb_completion_wait;
    bool ongoing_usb_ll_op;        /* all is in progress */
    struct urb *interrupt_out_urb;
};

/*
 * Return list of I2C supported functionality
 */
static u32 ltc3206_usb_func(struct i2c_adapter *a)
{
    return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL |
           I2C_FUNC_SMBUS_READ_BLOCK_DATA | I2C_FUNC_SMBUS_BLOCK_PROC_CALL;
}

/* usb out urb callback function */
static void ltc3206_usb_cmpl_cbk(struct urb *urb)
{
    struct i2c_ltc3206 *dev = urb->context;
    int status = urb->status;
    int retval;

    switch (status) {
    case 0:            /* success */
        break;
    case -ECONNRESET:    /* unlink */
    case -ENOENT:
    case -ESHUTDOWN:
        return;
    /* -EPIPE:  should clear the halt */
    default:        /* error */
        goto resubmit;
    }

    /* 
     * wake up the waiting function
     * modify the flag indicating the ll status 
     */
    dev->ongoing_usb_ll_op = 0; /* communication is OK */
    wake_up_interruptible(&dev->usb_urb_completion_wait);
    return;

resubmit:
    retval = usb_submit_urb(urb, GFP_ATOMIC);
    if (retval) {
        dev_err(&dev->interface->dev,
              "ltc3206(irq): can't resubmit intrerrupt urb, retval %d\n",
              retval);
    }
}

static int ltc3206_ll_cmd(struct i2c_ltc3206 *dev)
{
    int rv;

    /* 
     * tell everybody to leave the URB alone
     * we are going to write to the LTC3206
     */
    dev->ongoing_usb_ll_op = 1; /* doing USB communication */

    /* submit the interrupt out ep packet */
    if (usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL)) {
        dev_err(&dev->interface->dev,
                "ltc3206(ll): usb_submit_urb intr out failed\n");
        dev->ongoing_usb_ll_op = 0;
        return -EIO;
    }

    /* wait for its completion, the USB URB callback will signal it */
    rv = wait_event_interruptible(dev->usb_urb_completion_wait,
            (!dev->ongoing_usb_ll_op));
    if (rv < 0) {
        dev_err(&dev->interface->dev, "ltc3206(ll): wait interrupted\n");
        goto ll_exit_clear_flag;
    }

    return 0;

ll_exit_clear_flag:
    dev->ongoing_usb_ll_op = 0;
    return rv;
}

static int ltc3206_init(struct i2c_ltc3206 *dev)
{
    int ret;

    /* initialize the LTC3206 */
    dev_info(&dev->interface->dev,
           "LTC3206 at USB bus %03d address %03d -- ltc3206_init()\n",
           dev->usb_dev->bus->busnum, dev->usb_dev->devnum);

    dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL);
    if (!dev->interrupt_out_urb){
        ret = -ENODEV;
        goto init_error;
    }

    usb_fill_int_urb(dev->interrupt_out_urb, dev->usb_dev,
                usb_sndintpipe(dev->usb_dev,
                          dev->ep_out),
                (void *)&dev->obuffer, LTC3206_OUTBUF_LEN, 
                ltc3206_usb_cmpl_cbk, dev,
                1);

    ret = 0;
    goto init_no_error;

init_error:
    dev_err(&dev->interface->dev, "ltc3206_init: Error = %d\n", ret);
    return ret;

init_no_error:
    dev_info(&dev->interface->dev, "ltc3206_init: Success\n");
    return ret;
}

static int ltc3206_i2c_write(struct i2c_ltc3206 *dev,
                    struct i2c_msg *pmsg)
{
    u8 ucXferLen;
    int rv;
    u8 *pSrc, *pDst;
    
    if (pmsg->len > LTC3206_I2C_DATA_LEN)
    {
        pr_info ("problem with the lenght\n");
        return -EINVAL;
    }

    /* I2C write lenght */
    ucXferLen = (u8)pmsg->len;

    pSrc = &pmsg->buf[0];
    pDst = &dev->obuffer[0];
    memcpy(pDst, pSrc, ucXferLen);

    pr_info("oubuffer[0] = %d\n", dev->obuffer[0]);
    pr_info("oubuffer[1] = %d\n", dev->obuffer[1]);
    pr_info("oubuffer[2] = %d\n", dev->obuffer[2]);
        
    rv = ltc3206_ll_cmd(dev);
    if (rv < 0)
        return -EFAULT;

    return 0;
}

/* device layer */
static int ltc3206_usb_i2c_xfer(struct i2c_adapter *adap,
        struct i2c_msg *msgs, int num)
{
    struct i2c_ltc3206 *dev = i2c_get_adapdata(adap);
    struct i2c_msg *pmsg;
    int ret, count;

    pr_info("number of i2c msgs is = %d\n", num);

    for (count = 0; count < num; count++) {
        pmsg = &msgs[count];
        ret = ltc3206_i2c_write(dev, pmsg);
        if (ret < 0)
            goto abort;
    }

    /* if all the messages were transferred ok, return "num" */
    ret = num;
abort:
    return ret;
}

static const struct i2c_algorithm ltc3206_usb_algorithm = {
    .master_xfer = ltc3206_usb_i2c_xfer,
    .functionality = ltc3206_usb_func,
};

static const struct usb_device_id ltc3206_table[] = {
    { USB_DEVICE(USB_VENDOR_ID_LTC3206, USB_DEVICE_ID_LTC3206) },
    { }
};
MODULE_DEVICE_TABLE(usb, ltc3206_table);

static void ltc3206_free(struct i2c_ltc3206 *dev)
{
    usb_put_dev(dev->usb_dev);
    usb_set_intfdata(dev->interface, NULL);
    kfree(dev);
}

static int ltc3206_probe(struct usb_interface *interface,
                const struct usb_device_id *id)
{
    struct usb_host_interface *hostif = interface->cur_altsetting;
    struct i2c_ltc3206 *dev;
    int ret;

    dev_info(&interface->dev, "ltc3206_probe() function is called.\n");

    /* allocate memory for our device state and initialize it */
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    if (dev == NULL) {
        pr_info("i2c-ltc3206(probe): no memory for device state\n");
        ret = -ENOMEM;
        goto error;
    }

    /* get ep_out */
    dev->ep_out = hostif->endpoint[1].desc.bEndpointAddress;

    dev->usb_dev = usb_get_dev(interface_to_usbdev(interface));
    dev->interface = interface;

    init_waitqueue_head(&dev->usb_urb_completion_wait);

    /* save our data pointer in this interface device */
    usb_set_intfdata(interface, dev);

    /* setup i2c adapter description */
    dev->adapter.owner = THIS_MODULE;
    dev->adapter.class = I2C_CLASS_HWMON;
    dev->adapter.algo = &ltc3206_usb_algorithm;
    i2c_set_adapdata(&dev->adapter, dev);

    snprintf(dev->adapter.name, sizeof(dev->adapter.name),
           DRIVER_NAME " at bus %03d device %03d",
           dev->usb_dev->bus->busnum, dev->usb_dev->devnum);

    dev->adapter.dev.parent = &dev->interface->dev;

    /* initialize ltc3206 i2c device */
    ret = ltc3206_init(dev);
    if (ret < 0) {  
        dev_err(&interface->dev, "failed to initialize adapter\n");
        goto error_init;
    }

    /* and finally attach to i2c layer */
    ret = i2c_add_adapter(&dev->adapter);
    if (ret < 0) {
        dev_info(&interface->dev, "failed to add I2C adapter\n");
        goto error_i2c;
    }

    dev_info(&dev->interface->dev,
            "ltc3206_probe() -> chip connected -> Success\n");
    return 0;

error_init:
    usb_free_urb(dev->interrupt_out_urb);

error_i2c:
    usb_set_intfdata(interface, NULL);
    ltc3206_free(dev);
error:
    return ret;
}

static void ltc3206_disconnect(struct usb_interface *interface)
{
    struct i2c_ltc3206 *dev = usb_get_intfdata(interface);

    i2c_del_adapter(&dev->adapter);

    usb_kill_urb(dev->interrupt_out_urb);
    usb_free_urb(dev->interrupt_out_urb);

    usb_set_intfdata(interface, NULL);
    ltc3206_free(dev);

    pr_info("i2c-ltc3206(disconnect) -> chip disconnected");
}

static struct usb_driver ltc3206_driver = {
    .name = DRIVER_NAME,
    .probe = ltc3206_probe,
    .disconnect = ltc3206_disconnect,
    .id_table = ltc3206_table,
};

module_usb_driver(ltc3206_driver);

MODULE_DESCRIPTION("This is a usb controlled i2c ltc3206 device");
MODULE_LICENSE("GPL");

3.3 usb_ltc3206.ko使用示例

/* Keep the PIC32MX470 board powered off */
/* check the i2c adapters of the Raspberry Pi 4 Model B board */
root@raspberrypi:/home# i2cdetect -l
i2c-1 i2c bcm2835 (i2c@7e804000) I2C adapter

root@raspberrypi:/home# insmod usb_ltc3206.ko /* load the module */
usb_ltc3206: loading out-of-tree module taints kernel.
usbcore: registered new interface driver usb-ltc3206

/* power now the PIC32MX Curiosity board */
root@raspberrypi:/home# usb 1-1.3: new full-speed USB device number 4 using
dwc_otg
usb 1-1.3: New USB device found, idVendor=04d8, idProduct=003f, bcdDevice= 1.00
usb 1-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=0
usb 1-1.3: Product: USB to I2C demo
usb 1-1.3: Manufacturer: Microchip Technology Inc.
usb-ltc3206 1-1.3:1.0: ltc3206_probe() function is called.
usb-ltc3206 1-1.3:1.0: LTC3206 at USB bus 001 address 004 -- ltc3206_init()
usb-ltc3206 1-1.3:1.0: ltc3206_init: Success
usb-ltc3206 1-1.3:1.0: ltc3206_probe() -> chip connected -> Success

/* check again the i2c adapters of the Raspberry Pi 4 Model B board, find the new
one */
root@raspberrypi:/home# i2cdetect -l
i2c-1 i2c bcm2835 (i2c@7e804000) I2C adapter
i2c-11 i2c usb-ltc3206 at bus 001 device 004 I2C adapter root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# ls authorized bInterfaceProtocol ep_01 power bAlternateSetting bInterfaceSubClass ep_81 subsystem bInterfaceClass bNumEndpoints i2c-4 supports_autosuspend bInterfaceNumber driver modalias uevent /* * verify the communication between the host and device * these commands toggle the three leds of the PIC32MX board and * set maximum brightness of the LTC3206 LED BLUE */ root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# i2cset -y 11 0x1b 0x00 0xf0 0x00 i number of i2c msgs is = 1 oubuffer[0] = 0 oubuffer[1] = 240 oubuffer[2] = 0 /* set maximum brightness of the LTC3206 LED RED */ root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# i2cset -y 11 0x1b 0xf0 0x00 0x00 i /* decrease brightness of the LTC3206 LED RED */ root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# i2cset -y 11 0x1b 0x10 0x00 0x00 i /* set maximum brightness of the LTC3206 LED GREEN */ root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# i2cset -y 11 0x1b 0x00 0x0f 0x00 i /* set maximum brightness of the LTC3206 LED GREEN and SUB display */ root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# i2cset -y 11 0x1b 0x00 0x0f 0x0f i /* set maximum brightness of the LTC3206 MAIN display */ root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# i2cset -y 11 0x1b 0x00 0x00 0xf0 i root@raspberrypi:/home# rmmod usb_ltc3206.ko /* remove the module */ usbcore: deregistering interface driver usb-ltc3206 /* Power off the PIC32MX Curiosity board */ root@raspberrypi:/home# i2c-ltc3206(disconnect) -> chip disconnected usb 1-1.3: USB disconnect, device number 4
posted @ 2023-01-06 19:01  闹闹爸爸  阅读(769)  评论(0编辑  收藏  举报