Files
Last update 8 years 3 weeks
by
Ronny
FilesSoftwareI2C-Kerneldrivermcp2221_0_1 | |
---|---|
.. | |
Makefile | |
ReadMe | |
driver_load.sh | |
i2c-mcp2221.c |
i2c-mcp2221.c/* * i2c bus driver for MCP2221 * * Derived from: * i2c-tiny-usb.c * i2c-diolan-u2c.c * usb-serial.c * onetouch.c * usb-skeleton.c * * Copyright (C) 2014 Microchip Technology Inc. * * Author: Bogdan Bolocan http://www.microchip.com/support * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 2 of the License, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * * * */ #include <linux/kernel.h> #include <linux/errno.h> #include <linux/module.h> #include <linux/types.h> #include <linux/slab.h> #include <linux/mutex.h> #include <linux/usb.h> #include <linux/i2c.h> #include <linux/delay.h> #define DRIVER_NAME "i2c-mcp2221" #define USB_VENDOR_ID_MCP2221 0x04d8 #define USB_DEVICE_ID_MCP2221 0x00dd #define MCP2221_OUTBUF_LEN 64 /* USB write packet length */ #define MCP2221_INBUF_LEN 64 /* USB read packet length */ #define MCP2221_MAX_I2C_DATA_LEN 60 #define MCP2221_FREQ_STD 100000 #define MCP2221_FREQ_MAX 500000 #define MCP2221_RETRY_MAX 50 #define MCP2221_STD_DELAY_MS 1 #define RESP_ERR_NOERR 0x00 #define RESP_ADDR_NACK 0x25 #define RESP_READ_ERR 0x7F #define RESP_READ_COMPL 0x55 #define RESP_I2C_IDLE 0x00 #define RESP_I2C_START_TOUT 0x12 #define RESP_I2C_RSTART_TOUT 0x17 #define RESP_I2C_WRADDRL_TOUT 0x23 #define RESP_I2C_WRADDRL_WSEND 0x21 #define RESP_I2C_WRADDRL_NACK 0x25 #define RESP_I2C_WRDATA_TOUT 0x44 #define RESP_I2C_RDDATA_TOUT 0x52 #define RESP_I2C_STOP_TOUT 0x62 #define CMD_MCP2221_STATUS 0x10 #define SUBCMD_STATUS_CANCEL 0x10 #define SUBCMD_STATUS_SPEED 0x20 #define MASK_ADDR_NACK 0x40 #define CMD_MCP2221_RDDATA7 0x91 #define CMD_MCP2221_GET_RDDATA 0x40 #define CMD_MCP2221_WRDATA7 0x90 /* Structure to hold all of our device specific stuff */ struct i2c_mcp2221 { u8 obuffer[MCP2221_OUTBUF_LEN]; /* USB write buffer */ u8 ibuffer[MCP2221_INBUF_LEN]; /* USB read buffer */ /* I2C/SMBus data buffer */ u8 user_data_buffer[MCP2221_MAX_I2C_DATA_LEN]; int ep_in, ep_out; /* Endpoints */ 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 */ uint frequency; /* I2C/SMBus communication frequency */ /* Mutex for low-level USB transactions */ struct mutex mcp2221_usb_op_lock; /* wq to wait for an ongoing read/write */ wait_queue_head_t usb_urb_completion_wait; bool ongoing_usb_ll_op; /* a ll is in progress */ struct urb *interrupt_in_urb; struct urb *interrupt_out_urb; }; static uint frequency = MCP2221_FREQ_STD; /* I2C clock frequency in Hz */ module_param(frequency, uint, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(frequency, "I2C clock frequency in hertz"); /* usb layer */ /* * Return list of supported functionality. */ static u32 mcp2221_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; } static void mcp2221_usb_cmpl_cbk(struct urb *urb) { struct i2c_mcp2221 *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 waitting function modify the flag indicating the ll status */ dev->ongoing_usb_ll_op = 0; wake_up_interruptible(&dev->usb_urb_completion_wait); return; resubmit: retval = usb_submit_urb(urb, GFP_ATOMIC); if (retval) { dev_err(&dev->interface->dev, "mcp2221(irq): can't resubmit intrerrupt urb, retval %d\n", retval); } } static int mcp2221_ll_cmd(struct i2c_mcp2221 *dev) { int rv; /* tell everybody to leave the URB alone */ dev->ongoing_usb_ll_op = 1; /* submit the interrupt out ep packet */ if (usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL)) { dev_err(&dev->interface->dev, "mcp2221(ll): usb_submit_urb intr out failed\n"); dev->ongoing_usb_ll_op = 0; return -EIO; } /* wait for its completion */ rv = wait_event_interruptible(dev->usb_urb_completion_wait, (!dev->ongoing_usb_ll_op)); if (rv < 0) { dev_err(&dev->interface->dev, "mcp2221(ll): wait interrupted\n"); goto ll_exit_clear_flag; } /* tell everybody to leave the URB alone */ dev->ongoing_usb_ll_op = 1; /* submit the interrupt in ep packet */ if (usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL)) { dev_err(&dev->interface->dev, "mcp2221(ll): usb_submit_urb intr in failed\n"); dev->ongoing_usb_ll_op = 0; return -EIO; } /* wait for its completion */ rv = wait_event_interruptible(dev->usb_urb_completion_wait, (!dev->ongoing_usb_ll_op)); if (rv < 0) { dev_err(&dev->interface->dev, "mcp2221(ll): wait interrupted\n"); goto ll_exit_clear_flag; } ll_exit_clear_flag: dev->ongoing_usb_ll_op = 0; return rv; } static int mcp2221_init(struct i2c_mcp2221 *dev) { int ret; ret = 0; if (frequency > MCP2221_FREQ_MAX) frequency = MCP2221_FREQ_MAX; /* initialize the MCP2221 and bring it to "idle/ready" state */ dev_info(&dev->interface->dev, "MCP2221 at USB bus %03d address %03d -- mcp2221_init()\n", dev->usb_dev->bus->busnum, dev->usb_dev->devnum); /* initialize unlocked mutex */ mutex_init(&dev->mcp2221_usb_op_lock); dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); if (!dev->interrupt_out_urb) 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, MCP2221_OUTBUF_LEN, mcp2221_usb_cmpl_cbk, dev, 1); dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); if (!dev->interrupt_in_urb) goto init_error; usb_fill_int_urb(dev->interrupt_in_urb, dev->usb_dev, usb_rcvintpipe(dev->usb_dev, dev->ep_in), (void *)&dev->ibuffer, MCP2221_INBUF_LEN, mcp2221_usb_cmpl_cbk, dev, 1); ret = 0; goto init_no_error; init_error: dev_err(&dev->interface->dev, "mcp2221_init: Error = %d\n", ret); ret = -ENODEV; init_no_error: dev_info(&dev->interface->dev, "mcp2221_init: Success\n"); return ret; } static int mcp2221_i2c_readwrite(struct i2c_mcp2221 *dev, struct i2c_msg *pmsg) { u8 ucI2cDiv, ucCancelXfer, ucXferLen; int rv, retries; unsigned int sleepCmd; u8 *pSrc, *pDst, usbCmdStatus; retries = 0; ucCancelXfer = 0; /* clock divider for I2C operations */ ucI2cDiv = (u8)((12000000/frequency) - 3); /* determine the best delay value here */ /* (MCP2221_STD_DELAY_MS * MCP2221_FREQ_MAX)/frequency; */ sleepCmd = MCP2221_STD_DELAY_MS; if (pmsg->len > MCP2221_MAX_I2C_DATA_LEN) return -EINVAL; readwrite_reinit: dev->obuffer[0] = CMD_MCP2221_STATUS; /* code for STATUS cmd */ dev->obuffer[1] = 0x00; dev->obuffer[2] = ucCancelXfer; /* cancel subcmd */ dev->obuffer[3] = SUBCMD_STATUS_SPEED; /* set the xfer speed */ dev->obuffer[4] = ucI2cDiv; dev->obuffer[5] = 0x00; dev->obuffer[6] = 0x00; dev->obuffer[7] = 0x00; rv = mcp2221_ll_cmd(dev); if (rv < 0) return -EFAULT; if (dev->ibuffer[1] != RESP_ERR_NOERR) return -EFAULT; if (dev->ibuffer[3] != SUBCMD_STATUS_SPEED) { /* the speed could not be set - wait a while and retry */ if (retries < MCP2221_RETRY_MAX) { /* wait a while and retry the operation */ retries++; msleep(MCP2221_STD_DELAY_MS); ucCancelXfer = SUBCMD_STATUS_CANCEL; goto readwrite_reinit; } else { /* max number of retries was reached - return error */ dev_err(&dev->interface->dev, "mcp2221 CANCEL ERROR:retries = %d\n", retries); return -EFAULT; } } if (pmsg->flags & I2C_M_RD) { /* I2C read */ ucXferLen = (u8)pmsg->len; dev->obuffer[0] = CMD_MCP2221_RDDATA7; dev->obuffer[1] = ucXferLen; /* LSB of the xfer length */ dev->obuffer[2] = 0; /* no MSB for the xfer length */ /* address in 8-bit format */ dev->obuffer[3] = (u8)((pmsg->addr) << 1); rv = mcp2221_ll_cmd(dev); if (rv < 0) return -EFAULT; if (dev->ibuffer[1] != RESP_ERR_NOERR) return -EFAULT; retries = 0; dev->obuffer[0] = CMD_MCP2221_GET_RDDATA; dev->obuffer[1] = 0x00; dev->obuffer[2] = 0x00; dev->obuffer[3] = 0x00; while (retries < MCP2221_RETRY_MAX) { msleep(sleepCmd); rv = mcp2221_ll_cmd(dev); if (rv < 0) return -EFAULT; if (dev->ibuffer[1] != RESP_ERR_NOERR) return -EFAULT; if (dev->ibuffer[2] == RESP_ADDR_NACK) return -EFAULT; /* break the loop - cmd ended ok - used for bus scan */ if ((dev->ibuffer[3] == 0x00) && (dev->ibuffer[2] == 0x00)) break; if (dev->ibuffer[3] == RESP_READ_ERR) { retries++; continue; } if ((dev->ibuffer[2] == RESP_READ_COMPL) && (dev->ibuffer[3] == ucXferLen)) { /* we got the data - copy it */ pSrc = (u8 *)&dev->ibuffer[4]; pDst = (u8 *)&pmsg->buf[0]; memcpy(pDst, pSrc, ucXferLen); if (pmsg->flags & I2C_M_RECV_LEN) pmsg->len = ucXferLen; break; } } if (retries >= MCP2221_RETRY_MAX) return -EFAULT; } else { /* I2C write */ ucXferLen = (u8)pmsg->len; dev->obuffer[0] = CMD_MCP2221_WRDATA7; dev->obuffer[1] = ucXferLen; /* LSB of the xfer length */ dev->obuffer[2] = 0; /* no MSB for the xfer length */ /* address in 8-bit format */ dev->obuffer[3] = (u8)((pmsg->addr) << 1); /* copy the data we've read back */ pSrc = (u8 *)&pmsg->buf[0]; pDst = (u8 *)&dev->obuffer[4]; memcpy(pDst, pSrc, ucXferLen); retries = 0; while (retries < MCP2221_RETRY_MAX) { rv = mcp2221_ll_cmd(dev); if (rv < 0) return -EFAULT; if (dev->ibuffer[1] != RESP_ERR_NOERR) { usbCmdStatus = dev->ibuffer[2]; if (usbCmdStatus == RESP_I2C_START_TOUT) return -EFAULT; if (usbCmdStatus == RESP_I2C_WRADDRL_TOUT) return -EFAULT; if (usbCmdStatus == RESP_I2C_WRADDRL_NACK) return -EFAULT; if (usbCmdStatus == RESP_I2C_WRDATA_TOUT) return -EFAULT; if (usbCmdStatus == RESP_I2C_STOP_TOUT) return -EFAULT; msleep(sleepCmd); retries++; continue; } else { /* command completed successfully */ break; } } if (retries >= MCP2221_RETRY_MAX) return -EFAULT; /* now, prepare for the STATUS stage */ retries = 0; dev->obuffer[0] = CMD_MCP2221_STATUS; /* code for STATUS cmd */ dev->obuffer[1] = 0x00; dev->obuffer[2] = 0x00; dev->obuffer[3] = 0x00; dev->obuffer[4] = 0x00; dev->obuffer[5] = 0x00; dev->obuffer[6] = 0x00; dev->obuffer[7] = 0x00; while (retries < MCP2221_RETRY_MAX) { rv = mcp2221_ll_cmd(dev); if (rv < 0) return -EFAULT; if (dev->ibuffer[1] != RESP_ERR_NOERR) return -EFAULT; /* i2c slave address was nack-ed */ if (dev->ibuffer[20] & MASK_ADDR_NACK) return -EFAULT; usbCmdStatus = dev->ibuffer[8]; if (usbCmdStatus == RESP_I2C_IDLE) break; if (usbCmdStatus == RESP_I2C_START_TOUT) return -EFAULT; if (usbCmdStatus == RESP_I2C_WRADDRL_TOUT) return -EFAULT; if (usbCmdStatus == RESP_I2C_WRADDRL_WSEND) return -EFAULT; if (usbCmdStatus == RESP_I2C_WRADDRL_NACK) return -EFAULT; if (usbCmdStatus == RESP_I2C_WRDATA_TOUT) return -EFAULT; if (usbCmdStatus == RESP_I2C_STOP_TOUT) return -EFAULT; msleep(sleepCmd); retries++; } if (retries >= MCP2221_RETRY_MAX) return -EFAULT; } return 0; } /* device layer */ static int mcp2221_usb_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) { struct i2c_mcp2221 *dev = i2c_get_adapdata(adap); struct i2c_msg *pmsg; int ret, count; for (count = 0; count < num; count++) { pmsg = &msgs[count]; /* no concurrent users of the mcp2221 i2c xfer */ ret = mutex_lock_interruptible(&dev->mcp2221_usb_op_lock); if (ret < 0) goto abort; ret = mcp2221_i2c_readwrite(dev, pmsg); mutex_unlock(&dev->mcp2221_usb_op_lock); if (ret < 0) goto abort; } /* if all the messages were transferred ok, return "num" */ ret = num; abort: return ret; } static const struct i2c_algorithm mcp2221_usb_algorithm = { .master_xfer = mcp2221_usb_i2c_xfer, .functionality = mcp2221_usb_func, }; static const struct usb_device_id mcp2221_table[] = { { USB_DEVICE(USB_VENDOR_ID_MCP2221, USB_DEVICE_ID_MCP2221) }, { } }; MODULE_DEVICE_TABLE(usb, mcp2221_table); static void mcp2221_free(struct i2c_mcp2221 *dev) { usb_put_dev(dev->usb_dev); kfree(dev); } static int mcp2221_probe(struct usb_interface *interface, const struct usb_device_id *id) { struct usb_host_interface *hostif = interface->cur_altsetting; struct i2c_mcp2221 *dev; int ret; if ((hostif->desc.bInterfaceNumber != 2) || (hostif->desc.bInterfaceClass != 3)) { pr_info("i2c-mcp2221(probe): Interface doesn't match the MCP2221 HID\n"); return -ENODEV; } /* allocate memory for our device state and initialize it */ dev = kzalloc(sizeof(*dev), GFP_KERNEL); if (dev == NULL) { pr_info("i2c-mcp2221(probe): no memory for device state\n"); ret = -ENOMEM; goto error; } dev->ep_in = hostif->endpoint[0].desc.bEndpointAddress; 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 = &mcp2221_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 mcp2221 i2c interface */ ret = mcp2221_init(dev); if (ret < 0) { dev_err(&interface->dev, "failed to initialize adapter\n"); goto error_free; } /* and finally attach to i2c layer */ ret = i2c_add_adapter(&dev->adapter); if (ret < 0) { dev_err(&interface->dev, "failed to add I2C adapter\n"); goto error_free; } dev_info(&dev->interface->dev, "mcp2221_probe() -> chip connected -> Success\n"); return 0; error_free: usb_set_intfdata(interface, NULL); mcp2221_free(dev); error: return ret; } static void mcp2221_disconnect(struct usb_interface *interface) { struct i2c_mcp2221 *dev = usb_get_intfdata(interface); i2c_del_adapter(&dev->adapter); usb_kill_urb(dev->interrupt_in_urb); usb_kill_urb(dev->interrupt_out_urb); usb_free_urb(dev->interrupt_in_urb); usb_free_urb(dev->interrupt_out_urb); usb_set_intfdata(interface, NULL); mcp2221_free(dev); pr_info("i2c-mcp2221(disconnect) -> chip disconnected"); } static struct usb_driver mcp2221_driver = { .name = DRIVER_NAME, .probe = mcp2221_probe, .disconnect = mcp2221_disconnect, .id_table = mcp2221_table, }; module_usb_driver(mcp2221_driver); MODULE_AUTHOR("Bogdan Bolocan"); MODULE_DESCRIPTION(DRIVER_NAME "I2C MCP2221"); MODULE_LICENSE("GPL v2");