USB(通用串行总线)最早的USB总线速率包括低速(1.5Mbps)、高速(480Mbps),USB 3.0规范出现后,速率达到4.8Gbps。USB最大优点是它支持动态连接和移除,一种称”即插即用“的接口。
USB主机控制轮询总线,其中所有十五均由USB主机发起。
端点、描述符。
一旦枚举完成,主机和设备就可以自由地进行通信。
四种不同类型的传输:
控制传输;
批量数据传输;
中断数据传输;
等时数据传输;
常见的有HID、打印机、成像设备、大容量存储设备和通信设备。
设备描述符
配置描述符
接口描述符
端点描述符
Linux USB是一类特定API实现,用于支持USB外设和主机控制器。
Linux USB API支持对控制消息和批量消息的同步调用。
注册USB设备驱动程序,usb_driver结构体定义在,如下:
/linux/driver/misc/usbsevseg.c
USB设备驱动程序实际上绑定到接口,而不是绑定到设备。
USB HID设备固件,该设备能够使用HID报告来发送和接收数据。
#include
#include
#include
//创建ID表示支持热插拔
#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,
};
//将驱动注册到USB总线
module_usb_driver(led_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(" ");
MODULE_DESCRIPTION("This is a synchronous led usb controlled module");
#include
#include
#include
#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_LICENSE("GPL");
MODULE_AUTHOR(" ");
MODULE_DESCRIPTION("This is a led/switch usb controlled module with irq in/out endpoints");
使用芯片LTC3206 I2C 多显LED控制器。
#include
#include
#include
#include
/* 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;
}
//分配并初始化用于主机和设备之间通信的中断输出URB
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 = <c3206_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_AUTHOR(" ");
MODULE_DESCRIPTION("This is a usb controlled i2c ltc3206 device");
MODULE_LICENSE("GPL");
感谢阅读,祝君成功!
-by aiziyou