On Wed, 25 Mar 2009, Wolfram Sang wrote:
> > Because I hurry to submit this, because merge window is open.
Ok, I waited for this driver, I really did, and I really prefer having one
driver for as many hardware (SoC) variants as possible instead of having
different drivers, and I really hoped its new version will work
out-of-the-box in my setup (pcm037 with a mt9t031 camera). Unfortunately,
it didn't. I've spent more than a full working day trying to get it to
work, but I didn't succeed so far. It works with the on-board rtc, but it
doesn't work with the camera, connected over a flex cable.
Attached to this email is a version of the i2c driver for i.mx31 that I've
been using with my setup for about half a year now. I adjusted it slightly
to accept the same platform data, so, it is really a drop-in replacement
for i2c-imx.c. Of course, you have to adjust or extend the Makefile. I
actually added a new Kconfig entry for this driver, so I could easily
compare them during the tests.
The source of "my" version does look more logical and more clean to me on
quite a few occasions. So, maybe someone could give it a quick spin on
other *mx* SoCs and see if we can use this one instead? You might have to
replace {read,write}w with readb and writeb, so, it might be a good idea
to abstract them into inlines or macros. Otherwise, I think, it might work
for other CPUs straight away.
I just would like to avoid committing a driver and having to spend hours
or days fixing it afterwards when a possibly more mature version exists.
Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer
/*
* Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved.
*
* The code contained herein is licensed under the GNU General Public
* License. You may obtain a copy of the GNU General Public License
* Version 2 or later at the following locations:
*
* http://www.opensource.org/licenses/gpl-license.html
* http://www.gnu.org/copyleft/gpl.html
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <mach/i2c.h>
#include <mach/iomux-mx3.h>
/* Address offsets of the I2C registers */
#define MXC_IADR 0x00 /* Address Register */
#define MXC_IFDR 0x04 /* Freq div register */
#define MXC_I2CR 0x08 /* Control regsiter */
#define MXC_I2SR 0x0C /* Status register */
#define MXC_I2DR 0x10 /* Data I/O register */
/* Bit definitions of I2CR */
#define MXC_I2CR_IEN 0x0080
#define MXC_I2CR_IIEN 0x0040
#define MXC_I2CR_MSTA 0x0020
#define MXC_I2CR_MTX 0x0010
#define MXC_I2CR_TXAK 0x0008
#define MXC_I2CR_RSTA 0x0004
/* Bit definitions of I2SR */
#define MXC_I2SR_ICF 0x0080
#define MXC_I2SR_IAAS 0x0040
#define MXC_I2SR_IBB 0x0020
#define MXC_I2SR_IAL 0x0010
#define MXC_I2SR_SRW 0x0004
#define MXC_I2SR_IIF 0x0002
#define MXC_I2SR_RXAK 0x0001
#define MXC_ADAPTER_NAME "MXC I2C Adapter"
/*
* In case the MXC device has multiple I2C modules, this structure is used to
* store information specific to each I2C module.
*/
struct mxc_i2c_device {
struct i2c_adapter adap;
wait_queue_head_t wq; /* Transfer completion wait queue */
void __iomem *membase;
int irq;
unsigned int clkdiv; /* Default clock divider */
struct clk *clk;
bool low_power; /* Current power state */
bool transfer_done;
bool tx_success; /* ACK received */
struct resource *res;
};
struct clk_div_table {
int reg_value;
int div;
};
static const struct clk_div_table i2c_clk_table[] = {
{0x20, 22}, {0x21, 24}, {0x22, 26}, {0x23, 28},
{0, 30}, {1, 32}, {0x24, 32}, {2, 36},
{0x25, 36}, {0x26, 40}, {3, 42}, {0x27, 44},
{4, 48}, {0x28, 48}, {5, 52}, {0x29, 56},
{6, 60}, {0x2A, 64}, {7, 72}, {0x2B, 72},
{8, 80}, {0x2C, 80}, {9, 88}, {0x2D, 96},
{0xA, 104}, {0x2E, 112}, {0xB, 128}, {0x2F, 128},
{0xC, 144}, {0xD, 160}, {0x30, 160}, {0xE, 192},
{0x31, 192}, {0x32, 224}, {0xF, 240}, {0x33, 256},
{0x10, 288}, {0x11, 320}, {0x34, 320}, {0x12, 384},
{0x35, 384}, {0x36, 448}, {0x13, 480}, {0x37, 512},
{0x14, 576}, {0x15, 640}, {0x38, 640}, {0x16, 768},
{0x39, 768}, {0x3A, 896}, {0x17, 960}, {0x3B, 1024},
{0x18, 1152}, {0x19, 1280}, {0x3C, 1280}, {0x1A, 1536},
{0x3D, 1536}, {0x3E, 1792}, {0x1B, 1920}, {0x3F, 2048},
{0x1C, 2304}, {0x1D, 2560}, {0x1E, 3072}, {0x1F, 3840},
{0, 0}
};
/**
* Transmit a \b STOP signal to the slave device.
*
* @param dev the mxc i2c structure used to get to the right i2c device
*/
static void mxc_i2c_stop(struct mxc_i2c_device * dev)
{
unsigned int cr;
int retry = 200;
cr = readw(dev->membase + MXC_I2CR);
cr &= ~(MXC_I2CR_MSTA | MXC_I2CR_MTX);
writew(cr, dev->membase + MXC_I2CR);
/*
* Make sure STOP meets setup requirement.
*/
for (;;) {
unsigned int sr = readw(dev->membase + MXC_I2SR);
if ((sr & MXC_I2SR_IBB) == 0)
break;
if (retry-- <= 0) {
printk(KERN_DEBUG "Bus busy\n");
break;
}
udelay(3);
}
}
/**
* Wait for the transmission of the data byte to complete. This function waits
* till we get a signal from the interrupt service routine indicating completion
* of the address cycle or we time out.
*
* @param dev the mxc i2c structure used to get to the right i2c device
* @param trans_flag transfer flag
*
*
* @return The function returns 0 on success or -1 if an ack was not received
*/
static int mxc_i2c_wait_for_tc(struct mxc_i2c_device *dev, int trans_flag)
{
int retry = 16;
while (retry-- && !dev->transfer_done)
wait_event_interruptible_timeout(dev->wq,
dev->transfer_done,
dev->adap.timeout);
dev->transfer_done = false;
if (retry <= 0) {
/* Unable to send data */
dev_warn(&dev->adap.dev, "Data not transmitted\n");
return -ETIMEDOUT;
}
if (!dev->tx_success) {
/* An ACK was not received for transmitted byte */
dev_dbg(&dev->adap.dev, "ACK not received \n");
return -EREMOTEIO;
}
return 0;
}
/**
* Transmit a \b START signal to the slave device.
*
* @param dev the mxc i2c structure used to get to the right i2c device
* @param *msg pointer to a message structure that contains the slave
* address
*/
static void mxc_i2c_start(struct mxc_i2c_device *dev, struct i2c_msg *msg)
{
unsigned int cr, sr;
unsigned int addr_trans;
int retry = 16;
/*
* Set the slave address and the requested transfer mode
* in the data register
*/
addr_trans = msg->addr << 1;
if (msg->flags & I2C_M_RD)
addr_trans |= 0x01;
dev_dbg(&dev->adap.dev, "%s: start %x\n", __func__, msg->addr);
/* Set the Master bit */
cr = readw(dev->membase + MXC_I2CR);
cr |= MXC_I2CR_MSTA;
writew(cr, dev->membase + MXC_I2CR);
/* Wait till the Bus Busy bit is set */
sr = readw(dev->membase + MXC_I2SR);
while (retry-- && (!(sr & MXC_I2SR_IBB))) {
udelay(3);
sr = readw(dev->membase + MXC_I2SR);
}
if (retry <= 0)
dev_warn(&dev->adap.dev, "Could not grab Bus ownership\n");
/* Set the Transmit bit */
cr = readw(dev->membase + MXC_I2CR);
cr |= MXC_I2CR_MTX;
writew(cr, dev->membase + MXC_I2CR);
writew(addr_trans, dev->membase + MXC_I2DR);
}
/**
* Transmit a \b REPEAT START to the slave device
*
* @param dev the mxc i2c structure used to get to the right i2c device
* @param *msg pointer to a message structure that contains the slave
* address
*/
static void mxc_i2c_repstart(struct mxc_i2c_device *dev, struct i2c_msg *msg)
{
unsigned int cr;
unsigned int addr_trans;
/*
* Set the slave address and the requested transfer mode
* in the data register
*/
addr_trans = msg->addr << 1;
if (msg->flags & I2C_M_RD)
addr_trans |= 0x01;
dev_dbg(&dev->adap.dev, "%s: repeat start %x\n", __func__, msg->addr);
cr = readw(dev->membase + MXC_I2CR);
cr |= MXC_I2CR_RSTA;
writew(cr, dev->membase + MXC_I2CR);
udelay(3);
writew(addr_trans, dev->membase + MXC_I2DR);
}
/**
* Read the received data. The function waits till data is available or times
* out. Generates a stop signal if this is the last message to be received.
* Sends an ack for all the bytes received except the last byte.
*
* @param dev the mxc i2c structure used to get to the right i2c device
* @param *msg pointer to a message structure that contains the slave
* address and a pointer to the receive buffer
* @param last indicates that this is the last message to be received
* @param addr_comp flag indicates that we just finished the address cycle
*
* @return The function returns the number of bytes read or -1 on time out.
*/
static int mxc_i2c_readbytes(struct mxc_i2c_device *dev, struct i2c_msg *msg,
int last, int addr_comp)
{
int i;
char *buf = msg->buf;
int len = msg->len;
unsigned int cr;
dev_dbg(&dev->adap.dev, "%s: last %d, addr_comp %d\n",
__func__, last, addr_comp);
cr = readw(dev->membase + MXC_I2CR);
/*
* Clear MTX to switch to receive mode.
*/
cr &= ~MXC_I2CR_MTX;
/*
* Clear the TXAK bit to gen an ack when receiving only one byte.
*/
if (len == 1)
cr |= MXC_I2CR_TXAK;
else
cr &= ~MXC_I2CR_TXAK;
writew(cr, dev->membase + MXC_I2CR);
/*
* Dummy read only at the end of an address cycle
*/
if (addr_comp > 0)
readw(dev->membase + MXC_I2DR);
for (i = 0; i < len; i++) {
int ret;
/*
* Wait for data transmission to complete
*/
ret = mxc_i2c_wait_for_tc(dev, msg->flags);
if (ret < 0) {
dev_err(&dev->adap.dev, "%s: rx #%d of %d failed!\n",
__func__, i, len);
mxc_i2c_stop(dev);
return ret;
}
/*
* Do not generate an ACK for the last byte
*/
if (i == len - 2) {
cr = readw(dev->membase + MXC_I2CR);
cr |= MXC_I2CR_TXAK;
writew(cr, dev->membase + MXC_I2CR);
} else if (i == len - 1) {
if (last)
mxc_i2c_stop(dev);
}
/* Read the data */
*buf++ = readw(dev->membase + MXC_I2DR);
}
return i;
}
/**
* Write the data to the data register. Generates a stop signal if this is
* the last message to be sent or if no ack was received for the data sent.
*
* @param dev the mxc i2c structure used to get to the right i2c device
* @param *msg pointer to a message structure that contains the slave
* address and data to be sent
* @param last indicates that this is the last message to be received
*
* @return The function returns the number of bytes written or -1 on time out
* or if no ack was received for the data that was sent.
*/
static int mxc_i2c_writebytes(struct mxc_i2c_device *dev, struct i2c_msg *msg,
int last)
{
int i;
char *buf = msg->buf;
int len = msg->len;
unsigned int cr;
dev_dbg(&dev->adap.dev, "%s: last %d\n", __func__, last);
cr = readw(dev->membase + MXC_I2CR);
/* Set MTX to switch to transmit mode */
writew(cr | MXC_I2CR_MTX, dev->membase + MXC_I2CR);
for (i = 0; i < len; i++) {
int ret;
/*
* Write the data
*/
writew(*buf++, dev->membase + MXC_I2DR);
ret = mxc_i2c_wait_for_tc(dev, msg->flags);
if (ret < 0) {
dev_err(&dev->adap.dev, "%s: tx #%d of %d timed out!\n",
__func__, i, len);
mxc_i2c_stop(dev);
return ret;
}
}
if (last > 0)
mxc_i2c_stop(dev);
return i;
}
/**
* Function enables the I2C module and initializes the registers.
*
* @param dev the mxc i2c structure used to get to the right i2c device
* @param trans_flag transfer flag
*/
static void mxc_i2c_module_en(struct mxc_i2c_device *dev, int trans_flag)
{
clk_enable(dev->clk);
/* Set the frequency divider */
writew(dev->clkdiv, dev->membase + MXC_IFDR);
/* Clear the status register */
writew(0x0, dev->membase + MXC_I2SR);
/* Enable I2C and its interrupts */
writew(MXC_I2CR_IEN, dev->membase + MXC_I2CR);
writew(MXC_I2CR_IEN | MXC_I2CR_IIEN, dev->membase + MXC_I2CR);
}
/**
* Disables the I2C module.
*
* @param dev the mxc i2c structure used to get to the right i2c device
*/
static void mxc_i2c_module_dis(struct mxc_i2c_device * dev)
{
writew(0x0, dev->membase + MXC_I2CR);
clk_disable(dev->clk);
}
/**
* The function is registered in the adapter structure. It is called when an MXC
* driver wishes to transfer data to a device connected to the I2C device.
*
* @param adap adapter structure for the MXC i2c device
* @param msgs[] array of messages to be transferred to the device
* @param num number of messages to be transferred to the device
*
* @return The function returns the number of messages transferred,
* \b -EREMOTEIO on I2C failure and a 0 if the num argument is
* less than 0.
*/
static int mxc_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
struct mxc_i2c_device *dev = i2c_get_adapdata(adap);
int i, ret = 0, addr_comp = 0;
unsigned int sr;
if (dev->low_power) {
dev_warn(&dev->adap.dev, "I2C Device in low power mode\n");
return -EREMOTEIO;
}
if (num < 1)
return 0;
dev_dbg(&dev->adap.dev, "%s: xfer %d msgs\n", __func__, num);
mxc_i2c_module_en(dev, msgs[0].flags);
sr = readw(dev->membase + MXC_I2SR);
/*
* Check bus state
*/
if (sr & MXC_I2SR_IBB) {
mxc_i2c_module_dis(dev);
printk(KERN_DEBUG "Bus busy\n");
return -EREMOTEIO;
}
dev->transfer_done = false;
dev->tx_success = false;
for (i = 0; i < num && ret >= 0; i++) {
addr_comp = 0;
/*
* Send the slave address and transfer direction in the
* address cycle
*/
if (i == 0) {
/*
* Send a start or repeat start signal
*/
mxc_i2c_start(dev, &msgs[0]);
/* Wait for the address cycle to complete */
if (mxc_i2c_wait_for_tc(dev, msgs[0].flags)) {
mxc_i2c_stop(dev);
mxc_i2c_module_dis(dev);
dev_err(&dev->adap.dev,
"%s: Address failed!\n", __func__);
return -EREMOTEIO;
}
addr_comp = 1;
} else {
/*
* Generate repeat start only if required i.e. the
* address changed or the transfer direction changed
*/
if ((msgs[i].addr != msgs[i - 1].addr) ||
((msgs[i].flags & I2C_M_RD) !=
(msgs[i - 1].flags & I2C_M_RD))) {
mxc_i2c_repstart(dev, &msgs[i]);
/* Wait for the address cycle to complete */
if (mxc_i2c_wait_for_tc(dev, msgs[i].flags)) {
mxc_i2c_stop(dev);
mxc_i2c_module_dis(dev);
dev_err(&dev->adap.dev,
"%s: Address repeat failed!\n",
__func__);
return -EREMOTEIO;
}
addr_comp = 1;
}
}
/* Transfer the data */
if (msgs[i].flags & I2C_M_RD) {
/* Read the data */
ret = mxc_i2c_readbytes(dev, &msgs[i], i + 1 == num,
addr_comp);
if (ret < 0) {
dev_err(&dev->adap.dev, "mxc_i2c_readbytes: fail.\n");
break;
}
} else {
/* Write the data */
ret = mxc_i2c_writebytes(dev, &msgs[i], i + 1 == num);
if (ret < 0) {
dev_err(&dev->adap.dev, "mxc_i2c_writebytes: fail.\n");
break;
}
}
}
mxc_i2c_module_dis(dev);
dev_dbg(&dev->adap.dev, "%s: %d msgs success\n", __func__, i);
/*
* Decrease by 1 as we do not want Start message to be included in
* the count
*/
return i;
}
/**
* Returns the i2c functionality supported by this driver.
*
* @param adap adapter structure for this i2c device
*
* @return Returns the functionality that is supported.
*/
static u32 mxc_i2c_func(struct i2c_adapter *adap)
{
return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}
static struct i2c_algorithm mxc_i2c_algorithm = {
.master_xfer = mxc_i2c_xfer,
.functionality = mxc_i2c_func
};
/*
* Interrupt Service Routine. It signals to the process about the data transfer
* completion. Also sets a flag if bus arbitration is lost.
*/
static irqreturn_t mxc_i2c_handler(int irq, void *dev_id)
{
struct mxc_i2c_device *dev = dev_id;
unsigned int sr, cr;
sr = readw(dev->membase + MXC_I2SR);
cr = readw(dev->membase + MXC_I2CR);
/*
* Clear the interrupt bit
*/
writew(0x0, dev->membase + MXC_I2SR);
if (sr & MXC_I2SR_IAL) {
printk(KERN_DEBUG "Bus Arbitration lost\n");
} else {
/* Interrupt due byte transfer completion */
dev->tx_success = true;
/* Check if RXAK is received in Transmit mode */
if ((cr & MXC_I2CR_MTX) && (sr & MXC_I2SR_RXAK))
dev->tx_success = false;
dev->transfer_done = true;
wake_up_interruptible(&dev->wq);
}
return IRQ_HANDLED;
}
static int mxci2c_suspend(struct platform_device *pdev, pm_message_t state)
{
struct mxc_i2c_device *mxcdev = platform_get_drvdata(pdev);
struct imxi2c_platform_data *i2c_plat_data = pdev->dev.platform_data;
unsigned int sr;
if (mxcdev == NULL)
return -ENODEV;
/* Prevent further calls to be processed */
mxcdev->low_power = true;
/* Wait till we finish the current transfer */
sr = readw(mxcdev->membase + MXC_I2SR);
while (sr & MXC_I2SR_IBB) {
msleep(10);
sr = readw(mxcdev->membase + MXC_I2SR);
}
if (i2c_plat_data->exit)
i2c_plat_data->exit(&pdev->dev);
return 0;
}
static int mxci2c_resume(struct platform_device *pdev)
{
struct mxc_i2c_device *mxcdev = platform_get_drvdata(pdev);
struct imxi2c_platform_data *i2c_plat_data = pdev->dev.platform_data;
if (mxcdev == NULL)
return -ENODEV;
mxcdev->low_power = false;
if (i2c_plat_data->init)
i2c_plat_data->init(&pdev->dev);
return 0;
}
static int __init mxci2c_probe(struct platform_device *pdev)
{
struct mxc_i2c_device *mxc_i2c;
struct imxi2c_platform_data *i2c_plat_data = pdev->dev.platform_data;
struct resource *res;
int id = pdev->id;
u32 clk_freq;
int ret;
int i;
mxc_i2c = kzalloc(sizeof(struct mxc_i2c_device), GFP_KERNEL);
if (!mxc_i2c)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (res == NULL) {
ret = -ENODEV;
goto egetres;
}
if (!request_mem_region(res->start, resource_size(res), res->name)) {
ret = -ENOMEM;
goto ereqmemr;
}
mxc_i2c->membase = ioremap(res->start, resource_size(res));
if (!mxc_i2c->membase) {
ret = -ENOMEM;
goto eiomap;
}
mxc_i2c->res = res;
/*
* Request the I2C interrupt
*/
mxc_i2c->irq = platform_get_irq(pdev, 0);
if (mxc_i2c->irq < 0) {
ret = mxc_i2c->irq;
goto epgirq;
}
ret = request_irq(mxc_i2c->irq, mxc_i2c_handler,
0, pdev->name, mxc_i2c);
if (ret < 0)
goto ereqirq;
init_waitqueue_head(&mxc_i2c->wq);
mxc_i2c->low_power = false;
mxc_i2c->transfer_done = false;
mxc_i2c->tx_success = false;
if (i2c_plat_data->init)
i2c_plat_data->init(&pdev->dev);
mxc_i2c->clk = clk_get(&pdev->dev, "i2c_clk");
if (IS_ERR(mxc_i2c->clk)) {
ret = PTR_ERR(mxc_i2c->clk);
dev_err(&pdev->dev, "can't get I2C clock\n");
goto eclkget;
}
clk_freq = clk_get_rate(mxc_i2c->clk);
mxc_i2c->clkdiv = -1;
if (i2c_plat_data->bitrate) {
/* Calculate divider and round up any fractional part */
int div = (clk_freq + i2c_plat_data->bitrate - 1) /
i2c_plat_data->bitrate;
for (i = 0; i2c_clk_table[i].div != 0; i++) {
if (i2c_clk_table[i].div >= div) {
mxc_i2c->clkdiv = i2c_clk_table[i].reg_value;
break;
}
}
}
if (mxc_i2c->clkdiv == -1) {
i = ARRAY_SIZE(i2c_clk_table) - 2;
/* Use max divider */
mxc_i2c->clkdiv = i2c_clk_table[i].reg_value;
}
dev_dbg(&pdev->dev, "i2c speed is %d/%d = %d bps, reg val = 0x%02X\n",
clk_freq, i2c_clk_table[i].div, clk_freq / i2c_clk_table[i].div,
mxc_i2c->clkdiv);
/*
* Set the adapter information
*/
strcpy(mxc_i2c->adap.name, MXC_ADAPTER_NAME);
mxc_i2c->adap.nr = id;
mxc_i2c->adap.algo = &mxc_i2c_algorithm;
mxc_i2c->adap.timeout = 1;
mxc_i2c->adap.dev.parent= &pdev->dev;
mxc_i2c->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD;
platform_set_drvdata(pdev, mxc_i2c);
i2c_set_adapdata(&mxc_i2c->adap, mxc_i2c);
if ((ret = i2c_add_numbered_adapter(&mxc_i2c->adap)) < 0)
goto eaddadap;
return 0;
eaddadap:
platform_set_drvdata(pdev, NULL);
clk_put(mxc_i2c->clk);
eclkget:
if (i2c_plat_data->exit)
i2c_plat_data->exit(&pdev->dev);
free_irq(mxc_i2c->irq, mxc_i2c);
ereqirq:
epgirq:
iounmap(mxc_i2c->membase);
eiomap:
release_mem_region(res->start, resource_size(res));
ereqmemr:
egetres:
kfree(mxc_i2c);
dev_err(&pdev->dev, "failed to probe i2c adapter\n");
return ret;
}
static int __exit mxci2c_remove(struct platform_device *pdev)
{
struct mxc_i2c_device *mxc_i2c = platform_get_drvdata(pdev);
struct imxi2c_platform_data *i2c_plat_data = pdev->dev.platform_data;
free_irq(mxc_i2c->irq, mxc_i2c);
i2c_del_adapter(&mxc_i2c->adap);
if (i2c_plat_data->exit)
i2c_plat_data->exit(&pdev->dev);
clk_put(mxc_i2c->clk);
platform_set_drvdata(pdev, NULL);
iounmap(mxc_i2c->membase);
release_mem_region(mxc_i2c->res->start, resource_size(mxc_i2c->res));
kfree(mxc_i2c);
return 0;
}
static struct platform_driver mxci2c_driver = {
.driver = {
.name = "imx-i2c",
.owner = THIS_MODULE,
},
.remove = __exit_p(mxci2c_remove),
.suspend = mxci2c_suspend,
.resume = mxci2c_resume,
};
static int __init mxc_i2c_init(void)
{
return platform_driver_probe(&mxci2c_driver, mxci2c_probe);
}
static void __exit mxc_i2c_exit(void)
{
platform_driver_unregister(&mxci2c_driver);
}
subsys_initcall(mxc_i2c_init);
module_exit(mxc_i2c_exit);
MODULE_AUTHOR("Freescale Semiconductor, Inc.");
MODULE_DESCRIPTION("MXC I2C driver");
MODULE_LICENSE("GPL");