blob: 64920176950e0d11dc10d0ec62873692e77046c7 [file] [log] [blame]
/*
* drivers/i2c/busses/i2c-comcerto.c
*
* Copyright (C) 2008 Mindspeed Technologies, Inc.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <asm/sizes.h>
#include <mach/i2c.h>
#include <mach/irqs.h>
#include <linux/err.h>
#include <linux/clk.h>
#include <mach/reset.h>
MODULE_AUTHOR("Mindspeed Technologies, Inc.");
MODULE_DESCRIPTION("Comcerto I2C bus driver");
MODULE_LICENSE("GPL");
#define SPEED_HIGH_KHZ 3400
#define SPEED_FULL_KHZ 400
#define SPEED_NORMAL_KHZ 100
static int force_poll = 0;
static struct clk *clk_i2c;
module_param(force_poll, bool, S_IRUGO);
MODULE_PARM_DESC(force_poll, "Force polling mode: 0=interrupt mode, polling mode otherwise");
static int speed = 0;
module_param(speed, int, S_IRUGO);
MODULE_PARM_DESC(speed, "I2C speed: 0=standard, 1=fast, 2=high speed");
struct comcerto_i2c
{
struct i2c_adapter *adapter;
struct device *dev;
unsigned long membase;
struct resource *io;
int irq;
u32 speed_khz;
wait_queue_head_t wait;
struct i2c_msg *msg;
int msg_state;
int msg_status; /* < 0: error, == 0: success, > 0: message in progress */
int msg_len;
int msg_retries;
};
#define REG_ADDR(i2c, offset) ((i2c)->membase + (offset))
#define RD_REG(i2c, offset) __raw_readb(REG_ADDR(i2c, offset))
#define WR_REG(i2c, offset, byte) __raw_writeb(byte, REG_ADDR(i2c, offset))
#define RD_DATA(i2c) RD_REG(i2c, COMCERTO_I2C_DATA)
#define WR_DATA(i2c, byte) WR_REG(i2c, COMCERTO_I2C_DATA, byte)
#define RD_CNTR(i2c) RD_REG(i2c, COMCERTO_I2C_CNTR)
#define WR_CNTR(i2c, byte) WR_REG(i2c, COMCERTO_I2C_CNTR, byte)
#define RD_STAT(i2c) RD_REG(i2c, COMCERTO_I2C_STAT)
#define WR_CCRFS(i2c, byte) WR_REG(i2c, COMCERTO_I2C_CCRFS, byte)
#define WR_CCRH(i2c, byte) WR_REG(i2c, COMCERTO_I2C_CCRH, byte)
#define WR_RESET(i2c, byte) WR_REG(i2c, COMCERTO_I2C_RESET, byte)
enum
{
TR_IDLE = 0,
TR_START_ACK,
TR_ADDR_ACK,
TR_DATA_ACK,
RX_DATA_NACK,
};
static u8 comcerto_i2c_calculate_dividers(struct comcerto_i2c *i2c)
{
int m, n, hz, speed_hz;
int saved_n, saved_m, saved_hz;
u8 dividers;
unsigned int i2c_clk;
/* Get the i2c clock rate */
i2c_clk = clk_get_rate(clk_i2c);
speed_hz = i2c->speed_khz * 1000;
saved_hz = saved_n = saved_m = 0;
for (m = 0; m < 16; m++) {
for (n = 0; n < 8; n++) {
hz = i2c_clk / ((1 << n) * (m + 1) * 10);
if (!saved_hz || abs(speed_hz - hz) < abs(speed_hz - saved_hz)) {
saved_n = n;
saved_m = m;
saved_hz = hz;
}
}
}
dividers = (saved_m << 3) | saved_n;
dev_dbg(i2c->dev, "%s: speed=%dkHz, M=%d, N=%d, dividers=0x%02x\n", __FUNCTION__,
saved_hz/1000, saved_m, saved_n, dividers);
printk("%s: speed=%dkHz, M=%d, N=%d, dividers=0x%02x\n", __FUNCTION__, saved_hz/1000, saved_m, saved_n, dividers);
return dividers;
}
/*
* Returns the timeout (in jiffies) for the given message.
*/
static int comcerto_i2c_calculate_timeout(struct comcerto_i2c *i2c, struct i2c_msg *msg)
{
int timeout;
/* if no timeout was specified, calculate it */
if (i2c->adapter->timeout <= 0) {
if (i2c->irq >= 0) {
/* for the interrupt mode calculate timeout for 'full' message */
timeout = ((int)msg->len) * 10; /* convert approx. to bits */
timeout /= i2c->speed_khz; /* convert to bits per ms (note of kHz scale) */
timeout += timeout >> 1; /* add 50% */
timeout = timeout*HZ / 1000; /* convert to jiffies */
if (timeout < HZ / 5) /* at least 200ms */
timeout = HZ / 5;
}
else
timeout = HZ; /* 1 second for the polling mode */
}
else
timeout = i2c->adapter->timeout;
return timeout;
}
/*
* Initialize I2C core. Zero CNTR and DATA, try RESET. Short busy wait and check core status.
* After that set dividers for choosen speed.
*/
static void comcerto_i2c_reset(struct comcerto_i2c *i2c)
{
u8 status, dividers;
dev_dbg(i2c->dev, "%s\n", __FUNCTION__);
WR_CNTR(i2c, 0);
WR_DATA(i2c, 0);
WR_RESET(i2c, 1);
udelay(10);
status = RD_STAT(i2c);
if (status != STAT_NO_RELEVANT_INFO)
dev_printk(KERN_DEBUG, i2c->dev, "%s: unexpected status after reset: 0x%02x\n", __FUNCTION__, status);
/* dividers should be placed in CCRH for high-sped mode and in CCRFS for standard/full modes */
dividers = comcerto_i2c_calculate_dividers(i2c);
if (i2c->speed_khz == SPEED_HIGH_KHZ)
WR_CCRH(i2c, dividers);
else
WR_CCRFS(i2c, dividers);
}
static inline void comcerto_i2c_message_complete(struct comcerto_i2c *i2c, int status)
{
WR_CNTR(i2c, CNTR_STP);
i2c->msg_status = status;
}
static inline int comcerto_i2c_message_in_progress(struct comcerto_i2c *i2c)
{
return i2c->msg_status > 0;
}
/*
* Wait event. This function sleeps in polling mode, in interrupt
* mode it enables IRQ from I2C core and exits immediately.
*/
static int comcerto_i2c_wait(struct comcerto_i2c *i2c, u8 cntr)
{
cntr &= ~(CNTR_IFLG | CNTR_IEN); /* clear both IFLG and IEN */
if (i2c->irq < 0) {
ulong jiffies_mark = jiffies + comcerto_i2c_calculate_timeout(i2c, i2c->msg);
WR_CNTR(i2c, cntr);
while ((RD_CNTR(i2c) & CNTR_IFLG) == 0) {
if (need_resched())
schedule();
if (time_after(jiffies, jiffies_mark)) {
dev_printk(KERN_DEBUG, i2c->dev, "%s: polling transfer timeout\n", __FUNCTION__);
comcerto_i2c_message_complete(i2c, -ETIME);
comcerto_i2c_reset(i2c);
break;
}
}
}
else {
/* enable interrupt */
WR_CNTR(i2c, cntr | CNTR_IEN);
}
return 0;
}
static void comcerto_i2c_state_idle(struct comcerto_i2c *i2c, u8 *cntr)
{
if (unlikely(i2c->msg->flags & I2C_M_NOSTART)) {
i2c->msg_state = TR_ADDR_ACK;
}
else {
*cntr = CNTR_STP|CNTR_STA; /* SPT|STA to auto recover from bus error state transparently at the start of the transfer */
i2c->msg_state = TR_START_ACK;
}
}
static void comcerto_i2c_state_start_ack(struct comcerto_i2c *i2c, u8 *cntr)
{
u8 status, addr;
*cntr = 0; /* zero IFLG, IEN (for the interrupt mode it will be enabled in wait function) */
status = RD_STAT(i2c);
if (status == STAT_START || status == STAT_START_REPEATED) {
i2c->msg_state = TR_ADDR_ACK;
addr = i2c->msg->addr << 1;
if (i2c->msg->flags & I2C_M_RD)
addr |= 1;
if (i2c->msg->flags & I2C_M_REV_DIR_ADDR)
addr ^= 1; /* invert RW bit if it's requested */
WR_DATA(i2c, addr); /* write address and read/write bit */
} else {
dev_printk(KERN_DEBUG, i2c->dev, "%s: unexpected state (%#x) on start phase, %s\n",
__FUNCTION__, status, i2c->msg_retries > 1 ? "retrying":"aborting");
if (--i2c->msg_retries < 0)
comcerto_i2c_message_complete(i2c, -1);
else
comcerto_i2c_state_idle(i2c, cntr);
}
}
static void comcerto_i2c_rx(struct comcerto_i2c *i2c)
{
u8 status, cntr = 0;
restart:
switch (i2c->msg_state) {
case TR_IDLE:
comcerto_i2c_state_idle(i2c, &cntr);
if (unlikely(i2c->msg->flags & I2C_M_NOSTART))
goto restart; /* needed to avoid event loss in interrupt mode */
break;
case TR_START_ACK:
comcerto_i2c_state_start_ack(i2c, &cntr);
break;
case TR_ADDR_ACK:
if (unlikely(i2c->msg->flags & I2C_M_NOSTART)) {
/* we can enter this state if skip start/addr flag is set, so fake good ack */
status = STAT_ADDR_RD_ACK;
}
else {
status = RD_STAT(i2c);
/* check whether we should ignore NACK */
if (status == STAT_DATA_RD_NACK && (i2c->msg->flags & I2C_M_IGNORE_NAK))
status = STAT_DATA_RD_ACK;
}
if (likely(status == STAT_ADDR_RD_ACK)) {
/* start reception phase - wait until data is ready and loop in RX_DATA_ACK state
* until we read all the data, sending ACK after each byte (but the last)
*/
i2c->msg_len = 0;
if (i2c->msg->len > 1) {
i2c->msg_state = TR_DATA_ACK;
cntr = CNTR_AAK;
}
else if (i2c->msg->len == 1) {
i2c->msg_state = RX_DATA_NACK;
}
else { /* nothing to receive, send STOP and signal success */
comcerto_i2c_message_complete(i2c, 0);
}
}
else {
dev_printk(KERN_DEBUG, i2c->dev, "%s: unexpected state (%#x) on address phase, %s\n",
__FUNCTION__, status, i2c->msg_retries > 1 ? "retrying":"aborting");
if (--i2c->msg_retries < 0)
comcerto_i2c_message_complete(i2c, -1);
else
comcerto_i2c_state_idle(i2c, &cntr);
}
break;
case TR_DATA_ACK:
status = RD_STAT(i2c);
if (likely(status == STAT_DATA_RD_ACK)) {
i2c->msg->buf[i2c->msg_len++] = RD_DATA(i2c);
if (likely(i2c->msg->len - i2c->msg_len > 1)) {
cntr = CNTR_AAK;
}
else {
i2c->msg_state = RX_DATA_NACK;
/* NACK should be transmitted on the last byte */
}
}
else {
dev_printk(KERN_DEBUG, i2c->dev, "%s: unexpected state (%#x) on read phase\n", __FUNCTION__, status);
comcerto_i2c_message_complete(i2c, -1);
}
break;
case RX_DATA_NACK:
status = RD_STAT(i2c);
if (likely(status == STAT_DATA_RD_NACK)) {
i2c->msg->buf[i2c->msg_len++] = RD_DATA(i2c);
comcerto_i2c_message_complete(i2c, 0);
}
else {
dev_printk(KERN_DEBUG, i2c->dev, "%s: unexpected state (%#x) on finishing read phase\n", __FUNCTION__, status);
comcerto_i2c_message_complete(i2c, -1);
}
}
/* no wait if we completed message */
if (comcerto_i2c_message_in_progress(i2c))
comcerto_i2c_wait(i2c, cntr);
}
static void comcerto_i2c_tx(struct comcerto_i2c *i2c)
{
u8 status, cntr = 0;
restart:
switch (i2c->msg_state) {
case TR_IDLE:
comcerto_i2c_state_idle(i2c, &cntr);
if (unlikely(i2c->msg->flags & I2C_M_NOSTART))
goto restart; /* needed to avoid event loss in interrupt mode */
break;
case TR_START_ACK:
comcerto_i2c_state_start_ack(i2c, &cntr);
break;
case TR_ADDR_ACK:
if (unlikely(i2c->msg->flags & I2C_M_NOSTART)) {
/* we can enter this state if skip start/addr flag is set, so fake good ack */
status = STAT_ADDR_WR_ACK;
}
else {
status = RD_STAT(i2c);
if (status == STAT_DATA_WR_NACK && (i2c->msg->flags & I2C_M_IGNORE_NAK))
status = STAT_DATA_WR_ACK;
}
if (likely(status == STAT_ADDR_WR_ACK)) {
/* start reception phase - wait until data is ready and loop in TX_DATA_ACK state
* until we read all the data, sending ACK after each byte (but the last)
*/
i2c->msg_state = TR_DATA_ACK;
i2c->msg_len = 0;
if (likely(i2c->msg->len != 0)) {
WR_DATA(i2c, i2c->msg->buf[i2c->msg_len++]);
//printk("comcerto_i2c_tx: i2c->msg->buf[i2c->msg_len - 1]=%d\n", i2c->msg->buf[i2c->msg_len - 1]);
}
else {
/* nothing to transmit, send STOP and signal success */
comcerto_i2c_message_complete(i2c, 0);
}
}
else {
dev_printk(KERN_DEBUG, i2c->dev, "%s: unexpected state (%#x) on address phase, %s\n",
__FUNCTION__, status, i2c->msg_retries > 1 ? "retrying":"aborting");
if (--i2c->msg_retries < 0)
comcerto_i2c_message_complete(i2c, -1);
else
comcerto_i2c_state_idle(i2c, &cntr);
}
break;
case TR_DATA_ACK:
status = RD_STAT(i2c);
if (status == STAT_DATA_WR_NACK && (i2c->msg->flags & I2C_M_IGNORE_NAK))
status = STAT_DATA_WR_ACK;
if (likely(status == STAT_DATA_WR_ACK)) {
if (i2c->msg->len > i2c->msg_len)
WR_DATA(i2c, i2c->msg->buf[i2c->msg_len++]);
else
comcerto_i2c_message_complete(i2c, 0);
}
else {
dev_printk(KERN_DEBUG, i2c->dev, "%s: unexpected state (%#x) on read data phase\n", __FUNCTION__, status);
comcerto_i2c_message_complete(i2c, -1);
}
break;
}
if (comcerto_i2c_message_in_progress(i2c))
comcerto_i2c_wait(i2c, cntr);
}
static irqreturn_t comcerto_i2c_interrupt(int irq, void *dev_id)
{
struct comcerto_i2c *i2c = dev_id;
if (!(RD_CNTR(i2c) & CNTR_IFLG))
goto none;
/* IRQ enable/disable logic is hidden in state handlers, all we need is to wake
* process when message completed.
*/
if (i2c->msg->flags & I2C_M_RD)
comcerto_i2c_rx(i2c);
else
comcerto_i2c_tx(i2c);
if (!comcerto_i2c_message_in_progress(i2c)) {
WR_CNTR(i2c, RD_CNTR(i2c) & ~CNTR_IEN); /* disable interrupt unconditionally */
wake_up(&i2c->wait);
}
return IRQ_HANDLED;
none:
return IRQ_NONE;
}
static void comcerto_i2c_message_process(struct comcerto_i2c *i2c, struct i2c_msg *msg)
{
i2c->msg = msg;
i2c->msg_state = TR_IDLE;
i2c->msg_status = 1;
i2c->msg_retries = i2c->adapter->retries;
polling_mode:
if (msg->flags & I2C_M_RD)
comcerto_i2c_rx(i2c);
else
comcerto_i2c_tx(i2c);
if (i2c->irq < 0) {
/*if (i2c->msg != NULL)*/
goto polling_mode;
}
else {
int timeout, res;
ulong flags;
timeout = comcerto_i2c_calculate_timeout(i2c, msg);
res = wait_event_timeout(i2c->wait, i2c->msg_status <= 0, timeout);
local_irq_save(flags);
/* check if we timed out and set respective error codes */
if (res == 0) {
if (comcerto_i2c_message_in_progress(i2c)) {
dev_printk(KERN_DEBUG, i2c->dev, "%s: interrupt transfer timeout\n", __FUNCTION__);
comcerto_i2c_message_complete(i2c, -ETIME);
comcerto_i2c_reset(i2c);
}
}
local_irq_restore(flags);
}
}
/*
* Generic master transfer entrypoint.
* Returns the number of processed messages or error value
*/
static int comcerto_i2c_master_xfer(struct i2c_adapter *adapter, struct i2c_msg msgs[], int num)
{
struct comcerto_i2c *i2c = i2c_get_adapdata(adapter);
int i;
dev_dbg(i2c->dev, "%s: %d messages to process\n", __FUNCTION__, num);
for (i = 0; i < num; i++) {
dev_dbg(i2c->dev, "%s: message #%d: addr=%#x, flags=%#x, len=%u\n", __FUNCTION__,
i, msgs[i].addr, msgs[i].flags, msgs[i].len);
comcerto_i2c_message_process(i2c, &msgs[i]);
if (i2c->msg_status < 0) {
dev_printk(KERN_DEBUG, i2c->dev, "%s: transfer failed on message #%d (addr=%#x, flags=%#x, len=%u)\n",
__FUNCTION__, i, msgs[i].addr, msgs[i].flags, msgs[i].len);
break;
}
}
if (i2c->msg_status == -1)
i2c->msg_status = -EIO;
if (i2c->msg_status == 0)
i2c->msg_status = num;
return i2c->msg_status;
}
static u32 comcerto_i2c_functionality(struct i2c_adapter *adap)
{
return (I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL);
}
static struct i2c_algorithm comcerto_i2c_algo = {
.master_xfer = comcerto_i2c_master_xfer,
.functionality = comcerto_i2c_functionality,
};
static struct i2c_adapter comcerto_i2c_adapter = {
.name = "comcerto_i2c",
.owner = THIS_MODULE,
.algo = &comcerto_i2c_algo,
.timeout = 0, /* <= zero means that we calculate timeout in run-time, can be changed with ioctl call */
.retries = 0, /* no retries by default - let the user decide what's the best, can be changed with ioctl call */
};
static int comcerto_i2c_probe(struct platform_device *pdev)
{
struct comcerto_i2c *i2c;
struct resource *irq;
int res = -1;
dev_dbg(&pdev->dev, "%s\n", __FUNCTION__);
/* Put the I2C device Out-Of-Reset*/
c2000_block_reset(COMPONENT_AXI_I2C,0);
/* Clock divider configuration, get the i2c clock*/
clk_i2c = clk_get(NULL, "spi_i2c");
if (IS_ERR(clk_i2c)){
pr_err("%s: Unable to obtain i2c clock: %ld\n", __func__, PTR_ERR(clk_i2c));
return PTR_ERR(clk_i2c);
}
/*Enable the i2c clock */
res = clk_enable(clk_i2c);
if (res){
pr_err("%s: i2c clock failed to enable:\n", __func__);
goto err0;
}
i2c = kzalloc(sizeof(*i2c), GFP_KERNEL);
if (i2c == NULL) {
dev_err(&pdev->dev, "%s: failed allocate memory\n", __FUNCTION__);
res = -ENOMEM;
goto err0;
}
i2c->adapter = &comcerto_i2c_adapter;
i2c->adapter->dev.parent = &pdev->dev;
i2c->dev = &pdev->dev;
init_waitqueue_head(&i2c->wait);
platform_set_drvdata(pdev, i2c);
i2c_set_adapdata(&comcerto_i2c_adapter, i2c);
i2c->io = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (i2c->io == NULL) {
dev_err(&pdev->dev, "%s: no IO region specified\n", __FUNCTION__);
res = -ENOENT;
goto err1;
}
if (!request_mem_region(i2c->io->start, i2c->io->end - i2c->io->start + 1, "I2C")) {
dev_err(i2c->dev, "%s: failed to request memory region\n", __FUNCTION__);
goto err1;
}
i2c->membase = APB_VADDR(i2c->io->start);
irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (irq == NULL && !force_poll) {
dev_warn(i2c->dev, "%s: no IRQ specified in resources, polling mode forced\n", __FUNCTION__);
force_poll = 1;
i2c->irq = -1;
}
if (speed == 0) {
i2c->speed_khz = SPEED_NORMAL_KHZ;
}
else if (speed == 1) {
i2c->speed_khz = SPEED_FULL_KHZ;
}
else if (speed == 2) {
i2c->speed_khz = SPEED_HIGH_KHZ;
}
else {
dev_err(i2c->dev, "%s: invalid 'speed' module option provided (%d, must be 0,1,2 for normal/full/high modes)\n",
__FUNCTION__, speed);
goto err2;
}
comcerto_i2c_reset(i2c);
if (!force_poll) {
i2c->irq = irq->start;
res = request_irq(i2c->irq, comcerto_i2c_interrupt, IRQF_SHARED, "I2C", i2c);
if (res < 0) {
dev_warn(i2c->dev, "%s: failed to request IRQ%d, polling mode forced\n", __FUNCTION__, i2c->irq);
force_poll = 1;
i2c->irq = -1;
}
}
else
i2c->irq = -1;
if (i2c_add_numbered_adapter(&comcerto_i2c_adapter) != 0) {
dev_err(i2c->dev, "%s: failed to add I2C adapter\n", __FUNCTION__);
goto err3;
}
dev_dbg(&pdev->dev, "%s: I2C adapter registered\n", __FUNCTION__);
return 0;
err3:
if (i2c->irq >= 0)
free_irq(i2c->irq, i2c);
err2:
release_mem_region(i2c->io->start, i2c->io->end - i2c->io->end + 1);
err1:
kfree(i2c);
err0:
return res;
}
static int comcerto_i2c_remove(struct platform_device *pdev)
{
struct comcerto_i2c *i2c = platform_get_drvdata(pdev);
dev_dbg(i2c->dev, "%s\n", __FUNCTION__);
platform_set_drvdata(pdev, NULL);
i2c_del_adapter(i2c->adapter);
if (i2c->irq >= 0)
free_irq(i2c->irq, i2c);
release_mem_region(i2c->io->start, i2c->io->end - i2c->io->start + 1);
kfree(i2c);
/* Disable the Clock */
clk_disable(clk_i2c);
clk_put(clk_i2c);
/* Put the I2C device in reset state*/
c2000_block_reset(COMPONENT_AXI_I2C,1);
return 0;
}
#ifdef CONFIG_PM
static int comcerto_i2c_suspend(struct platform_device *pdev, pm_message_t state)
{
/* No need to suspend client drivers here. Because clients are
* children, client drivers get suspended before adapter driver
*/
/* So do the Clock disable here , This clock is depends upon Legacy SPI*/
clk_disable(clk_i2c);
return 0;
}
static int comcerto_i2c_resume(struct platform_device *pdev)
{
int ret;
/* No need to suspend client drivers here. Because clients are
* children, client drivers get suspended before adapter driver
*/
/* So do the Clock Enable here , This clock is depends upon Legacy SPI*/
ret = clk_enable(clk_i2c);
if (ret)
{
pr_err("%s: I2C clock enable failed \n",__func__);
return ret;
}
return 0;
}
#endif
static struct platform_driver comcerto_i2c_driver = {
.driver = {
.name = "comcerto_i2c",
.owner = THIS_MODULE,
},
.probe = comcerto_i2c_probe,
.remove = comcerto_i2c_remove,
#ifdef CONFIG_PM
.suspend = comcerto_i2c_suspend,
.resume = comcerto_i2c_resume,
#endif
};
static int __init comcerto_i2c_init(void)
{
if (platform_driver_register(&comcerto_i2c_driver)) {
return -1;
}
return 0;
}
static void __exit comcerto_i2c_exit(void)
{
platform_driver_unregister(&comcerto_i2c_driver);
}
module_init(comcerto_i2c_init);
module_exit(comcerto_i2c_exit);