Analysis of I2C of Marvell (Marvell PXA310 I2C驱动分析)

本文档详细分析了Marvell PXA310平台的I2C驱动,包括数据结构、初始化代码、传输函数以及中断处理。主要关注于驱动的探测、重置、数据传输等关键函数,同时也提到了中断模式的重要性,但该模式在当前实现中未被启用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.               Abstract

 抱歉这篇报告是我当初用英文写的,有兴趣的朋友可以参考。

2.               Introduction

Document [1].chapter8 is a good tip for readers to understanding I2C basic ideas.

3.               Adapter Codes

Codes exist in drivers/i2c/busses/i2c-pxa.c.

3.1        Data Structure

The device structure in arch/arm/mach-pxa/generic.c

static struct platform_device i2c_device = {

       .name           = "pxa2xx-i2c",

       .id          = 0,

       .resource      = i2c_resources,

       .num_resources   = ARRAY_SIZE(i2c_resources),

};

static struct resource i2c_resources[] = {

       {

              .start      = 0x40301680,

              .end       = 0x403016a3,

              .flags     = IORESOURCE_MEM,

       }, {

              .start      = IRQ_I2C,

              .end       = IRQ_I2C,

              .flags     = IORESOURCE_IRQ,

       },

};

The driver data structure in drivers/i2c/busses/i2c-pxa.c:

static struct platform_driver i2c_pxa_driver = {

       .probe          = i2c_pxa_probe,

       .remove        = i2c_pxa_remove,

       .driver           = {

              .name    = "pxa2xx-i2c",

       },

};

The driver private data data structure in drivers/i2c/busses/i2c-pxa.c:

struct pxa_i2c {

       spinlock_t            lock;

       wait_queue_head_t    wait;

       struct i2c_msg            *msg;

       unsigned int        msg_num;

       unsigned int        msg_idx;

       unsigned int        msg_ptr;

       unsigned int        slave_addr;

       struct i2c_adapter      adap;

#ifdef CONFIG_I2C_PXA_SLAVE

       struct i2c_slave_client *slave;

#endif

       unsigned int        irqlogidx;

       u32               isrlog[32];

       u32               icrlog[32];

       void __iomem            *reg_base;

       unsigned long             iobase;

       unsigned long             iosize;

       int                 irq;

};

And its global variable instance:

static struct pxa_i2c i2c_pxa = {

       .lock      = SPIN_LOCK_UNLOCKED,

       .adap     = {

              .owner          = THIS_MODULE,

              .algo             = &i2c_pxa_algorithm,

              .name            = "pxa2xx-i2c.0",

              .retries   = 5,

       },

};

static const struct i2c_algorithm i2c_pxa_algorithm = {

       .master_xfer = i2c_pxa_xfer,

       .functionality       = i2c_pxa_functionality,

};

3.2        Initialize Codes

3.2.1          Probe Function

Codes exist in drivers/i2c/busses/i2c-pxa.c.

static int i2c_pxa_probe(struct platform_device *dev)

{

       struct pxa_i2c *i2c = &i2c_pxa;

       struct resource *res;

#ifdef CONFIG_I2C_PXA_SLAVE

       struct i2c_pxa_platform_data *plat = dev->dev.platform_data;

#endif

       int ret;

       int irq;

       res = platform_get_resource(dev, IORESOURCE_MEM, 0);

       irq = platform_get_irq(dev, 0);

       ……

       if (!request_mem_region(res->start, res_len(res), res->name))

              return -ENOMEM;

       i2c = kmalloc(sizeof(struct pxa_i2c), GFP_KERNEL);

       ……

       memcpy(i2c, &i2c_pxa, sizeof(struct pxa_i2c));

       init_waitqueue_head(&i2c->wait);

       i2c->adap.name[strlen(i2c->adap.name) - 1] = '0' + dev->id % 10;

       i2c->reg_base = ioremap(res->start, res_len(res));

       ……

       i2c->iobase = res->start;

       i2c->iosize = res_len(res);

       i2c->irq = irq;

       i2c->slave_addr = I2C_PXA_SLAVE_ADDR; // I2C_PXA_SLAVE_ADDR equals to 1.

#ifdef CONFIG_I2C_PXA_SLAVE

       if (plat) {

              i2c->slave_addr = plat->slave_addr;

              i2c->slave = plat->slave;

       }

#endif

       switch (dev->id) { //In our case it is 0, please refer to section 3.1, this part is used to set the clock.

       case 0:

       ……

#ifndef CONFIG_PXA3xx

              pxa_set_cken(CKEN14_I2C, 1);

#else

              pxa_set_cken(CKEN_I2C, 1);

#endif

              break;

       ……

       }

#ifndef CONFIG_I2C_POLLING //We have not invoke following codes, since we have defined this micro

       ret = request_irq(irq, i2c_pxa_handler, IRQF_DISABLED,

                       i2c->adap.name, i2c);

       if (ret)

              goto ereqirq;

#endif

       i2c_pxa_reset(i2c); //Initialize I2C Registers.

       i2c->adap.algo_data = i2c; //Let algo to reference the data structure.

       i2c->adap.dev.parent = &dev->dev;

       ret = i2c_add_adapter(&i2c->adap); //This is a subroutine of i2c-core

       ……

       platform_set_drvdata(dev, i2c); //let dev->driver_data point to i2c

       return 0;

       ……

}

3.2.2          Function i 2c _pxa_reset

Please refer to comments following the codes.

static void i2c_pxa_reset(struct pxa_i2c *i2c)

{

       /* abort any transfer currently under way */

       i2c_pxa_abort(i2c); //Please see codes below

       /* reset according to 9.8 */

       writel(ICR_UR, _ICR(i2c)); //Unit Reset, ICR[14].

#ifdef CONFIG_I2C_FAST_MODE

       writel(readl(_ICR(i2c)) | ICR_FM, _ICR(i2c)); //Open fast mode, which is 400kbps. ICR[15]

#endif

       writel(I2C_ISR_INIT, _ISR(i2c)); // I2C_ISR_INIT equals to 0x7FF, which then set ISR[10..0] to 1 (ISR[31..11] are reserved bits.

       writel(readl(_ICR(i2c)) & ~ICR_UR, _ICR(i2c)); //We have already reset it, and now clear it.

       writel(i2c->slave_addr, _ISAR(i2c));// i2c->slave_addr is 1, which is set in i2c_pxa_probe function.

       /* set control register values */

       writel(I2C_ICR_INIT, _ICR(i2c)); // I2C_ICR_INIT  is (ICR_BEIE | ICR_IRFIE | ICR_ITEIE | ICR_GCD | ICR_SCLE), which means that: BEIE is used to enable BUS Error interruption, which is used to monitor if no ACK received from slave, or NACK is received from master if we behavior as slave. IRFIE is defined as DRFIE in SPEC, which is used to enable interruption when a full byte is received. ITEIE is used to enable interruption when a full byte is sent out.   GCD is used to enable or disable generation and reception of general call message, and this kind of message is in fact broadcast message with address equals to zero. SCLE is used to enable I2C to generate clock as a master.

#ifdef CONFIG_I2C_PXA_SLAVE

       dev_info(&i2c->adap.dev, "Enabling slave mode/n");

       writel(readl(_ICR(i2c)) | ICR_SADIE | ICR_ALDIE | ICR_SSDIE, _ICR(i2c));

#endif

       i2c_pxa_set_slave(i2c, 0); //This function is empty since we have not define the Slave macro.

       /* enable unit */

       writel(readl(_ICR(i2c)) | ICR_IUE, _ICR(i2c)); //IUE is used to enable I2C module

       udelay(100);

}

 

static inline int i2c_pxa_is_slavemode(struct pxa_i2c *i2c)

{

       return !(readl(_ICR(i2c)) & ICR_SCLE);//Check if SCL is enabled. If SCL is enabled, I2C is working at master mode since it will output clock, otherwise it is working on slave mode. ICR[5].

}

 

static void i2c_pxa_abort(struct pxa_i2c *i2c)

{

       unsigned long timeout = jiffies + HZ/4; //1/4Seconds

       if (i2c_pxa_is_slavemode(i2c)) {

              return;

       }

       while (time_before(jiffies, timeout) && (readl(_IBMR(i2c)) & 0x1) == 0) { //Wait for there is a high value on SDA Pin, MR[0] monitor the SDA PIN

              unsigned long icr = readl(_ICR(i2c));

              icr &= ~ICR_START; //This bit initialize an I2C transaction. ICR[0].

              icr |= ICR_ACKNAK | ICR_STOP | ICR_TB; //ACKNAK is used to give slave a stop signal after reading the last byte, STOP is used to end a transaction, and TB is used to indicate that we can start new transactions, since that every time it is set indicate there is a transaction complete signal which will invoke an interruption.

              writel(icr, _ICR(i2c));

              msleep(1);

       }

       writel(readl(_ICR(i2c)) & ~(ICR_MA | ICR_START | ICR_STOP),

              _ICR(i2c)); //MA is Master Abort, which is also used in stop a transaction while reading more than one words. START and STOP are used to initialize and stop a transaction.

}

3.3        Function i 2c _pxa_xfer

3.3.1          Main Function

The most important function to implement an adaptor is i2c_algorithm.master_xfer . In our system, this function is i2c_pxa_xfer.

static int i2c_pxa_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num)

{

       struct pxa_i2c *i2c = adap->algo_data;

       int ret, i;

       ……

       for (i = adap->retries; i >= 0; i--) { // retries equals to 5.

              ret = i2c_pxa_do_xfer(i2c, msgs, num);

              ……

              udelay(100);

       }

       ……

  out:

       i2c_pxa_set_slave(i2c, ret);//According to Spec’s requirements, we should set I2C to slave mode after every transfer. But this function is empty since we have not define the Slave macro.

       return ret;

}

3.3.2          Function i 2c _pxa_do_xfer

static int i2c_pxa_do_xfer(struct pxa_i2c *i2c, struct i2c_msg *msg, int num)

{

       long timeout;

       int ret;

       /*

         * Wait for the bus to become free.

         */

       ret = i2c_pxa_wait_bus_not_busy(i2c);//This function check if I2C unit and bus are in free status, and will wait if I2C module is in slave mode.

       ……

       /*

         * Set master mode.

         */

       ret = i2c_pxa_set_master(i2c);//This function used to check if I2C is in mater mode, which is judged according to following standard: not in slave mode(ISR_SAD is not set), unit and bus not busy(ISR_UB and ISR_IBB are not set), IBMR equals to 3 (SCL and SDA in high level voltage). After these conditions are met, set I2C module to master mode (set ICR_SCLE to 1)

       ……

#if defined(CONFIG_I2C_POLLING)

       {

               int i, err = 0;

               struct i2c_msg *p;

               for (i=0; !err && i<num; i++) {//For each i2c_msg, do the action.

                       p = &msg[i];

                       if (!p->len)

                               continue;

                       if (p->flags & I2C_M_RD)

                               err = i2c_pxa_do_read(p, p->buf, p->len); //Do the write on a message.

                       else

                               err = i2c_pxa_do_write(p, p->buf, p->len); //Do the read on a message.

               }

                if (!err)

                       ret = num - 1;

               else

                       ret = -err;

       }

#else //I have not analyze this part of codes since we do not use interruption mechanism.

       spin_lock_irq(&i2c->lock);

       i2c->msg = msg;

       i2c->msg_num = num;

       i2c->msg_idx = 0;

       i2c->msg_ptr = 0;

       i2c->irqlogidx = 0;

       i2c_pxa_start_message(i2c);

       spin_unlock_irq(&i2c->lock);

       /*

         * The rest of the processing occurs in the interrupt handler.

         */

       timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5);

       i2c_pxa_stop_message(i2c);

       /*

         * We place the return code in i2c->msg_idx.

         */

       ret = i2c->msg_idx;

       if (timeout == 0)

              i2c_pxa_scream_blue_murder(i2c, "timeout");

#endif

  out:

       return ret;

}

3.3.3          Function i 2c _pxa_do_read

static int i2c_pxa_do_read(struct i2c_msg *msg, unsigned char *buf, int len)

{

       int icr;

       /*

        * Step 1: target slave address into IDBR

        */

       IDBR = i2c_pxa_addr_byte(msg);//Fetch out the address 7 bits from the message, and set the direction of the bit 0 in the byte. And set it to IDBR. Note: I do not think it is a good programming style, since register should be referenced from the result of ioremap, but here used the style of __REG.

       /*

        * Step 2: initiate the write.

         */

       icr = ICR & ~(ICR_STOP | ICR_ALDIE); //Clear the stop tag, and disable interruption of arbitration loss.

       ICR = icr | ICR_START | ICR_TB; //Set the start flag, and set the send flag(TB)

       if (i2c_pxa_polling_txempty(4000)) //Wait for previous sending data process to finish, since we have already started the process to send the address byte.

               return 1;

       while (len--) {//From now we can read in data.

               icr = (ICR & ~ICR_START) | ICR_ALDIE | ICR_TB;//Clear the start tag, enable interruption of arbitration lost, and set the send flag.

               if (len == 0)

                       icr |= ICR_ACKNAK | ICR_STOP;//If it will be the last bit, set the NACK flag and STOP flag.

               else

                        icr &= ~ICR_ACKNAK;//Otherwise should clear NACK flag, since we will sent ACK flag to slave after reading a byte.

               ICR = icr;

               if (i2c_pxa_polling_rxfull(12000)) //Wait until a byte is read in.(ISR_IRF is full) .

                       return 1;

               *buf++ = (unsigned char) IDBR & 0xff; // record the read byte.

       }

       ICR &= ~(ICR_STOP | ICR_ACKNAK); //The read process is finished. Now we can clear STOP and NACK for future usage.

       return 0;

}

3.3.4          Function i2c_pxa_do_write

This function is very similar to read process, first write the address byte, and then write following bytes. We only list the codes here.

static int i2c_pxa_do_write(struct i2c_msg *msg, unsigned char *buf, int len)

{

       int icr;

       /*

        * Step 1: target slave address into IDBR

        */

       IDBR = i2c_pxa_addr_byte(msg);

       /*

        * Step 2: initiate the write.

        */

       icr = ICR & ~(ICR_STOP | ICR_ALDIE);

       ICR = icr | ICR_START | ICR_TB;

       if (i2c_pxa_polling_txempty(4000))

               return 1;

       while (len--) {

               IDBR = (unsigned int) (*buf++);

               icr = (ICR & ~ICR_START) | ICR_ALDIE | ICR_TB;

               if (len == 0)

                       icr |= ICR_STOP;

               ICR = icr;

               if (i2c_pxa_polling_txempty(50000))

                       return 1;

       }

       ICR &= ~ICR_STOP;

       return 0;

}

3.4        The Interruption Process

The interruption mode is not implemented in our platform. However, this kind of process is recommended in both SPEC and Internet reference codes. I want to optimize this part in the future.

4.               I 2C Core Sub-system

I will cover it in the future if I got time.

5.               I 2C Device Codes-Micco

5.1        Initialize Codes

static int __init micco_init(void)

{

       int ret;

       if ((ret = i2c_add_driver(&i2c_micco_driver))) {

              printk(KERN_WARNING "Micco: Driver registration failed,"

                     " module not inserted./n");

              return ret;

       }

       ret = platform_driver_register(&micco_driver);

       return ret;

}

We will cover these two drivers one by one:

struct i2c_driver i2c_micco_driver  =

{

       .driver = {

              .name     = "micco i2c client driver",

       },

       .attach_adapter   = &i2c_micco_attach_adapter,

       .detach_client      = &i2c_micco_detach_client, 

};

static struct platform_driver micco_driver = {

       .driver = {

              .name     = "pxa3xx_pmic",

       },

       .probe           = micco_probe,

       .remove = micco_remove,

       .suspend       = micco_suspend,

       .resume  = micco_resume,

};

5.2        Function of Driver i 2c _micco_driver

The codes before i2c_micco_detect_client is very routine, which just call i2c_probe:

static int i2c_micco_attach_adapter(struct i2c_adapter *adap)

{    

       return i2c_probe(adap,&addr_data, &i2c_micco_detect_client);

}

5.2.1          Function i 2c _micco_detect_client

static int i2c_micco_detect_client(struct i2c_adapter *adapter,

              int address, int kind)

{

       struct i2c_client *new_client;

       int err = 0;

       int chip_id;

       /* Let's see whether this adapter can support what we need.

              Please substitute the things you need here!  */

       if ( !i2c_check_functionality(adapter,I2C_FUNC_SMBUS_BYTE_DATA) ) {

              ……

       }

       /* OK. For now, we presume we have a valid client. We now create the

          client structure, even though we cannot fill it completely yet.

          But it allows us to access several i2c functions safely */

          /* Note that we reserve some space for micco_data too. If you don't

          need it, remove it. We do it here to help to lessen memory

          fragmentation. */

       new_client = kmalloc(sizeof(struct i2c_client), GFP_KERNEL );

       ……

       new_client->addr = address; //The slave addr of this I2C client. I have not dip into how this address is figured out, since it is in function i2c_proble of i2c core.

       new_client->adapter = adapter;

       new_client->driver = &i2c_micco_driver;

       new_client->flags = 0;

       chip_id = i2c_smbus_read_byte_data(new_client, MICCO_CHIP_ID);//This function will finally call i2c_transfer, since we have defined emulated smbus

       if (chip_id < 0){

              printk("micco unavailable!/n");

              goto ERROR1;

       } else {

              printk("micco(chip id:0x%02x) detected./n", chip_id);

       }

       g_client = new_client;

 

       strcpy(new_client->name, "MICCO");

       /* Tell the i2c layer a new client has arrived */

       if ((err = i2c_attach_client(new_client))) // A new I2C Device will be created.

              goto ERROR1;

       return 0;

       …….

}

5.3        Function of Driver micco_driver

5.3.1          Function micco_probe

I have not gone through this function one by one since I have not read through the SPEC of Dialog.

static int micco_probe(struct platform_device *pdev)

{

       int ret;

       u8 value;

       ret = micco_initchip();

       ……

       pxa3xx_mfp_set_configs(lt_micco_pins, ARRAY_SIZE(lt_micco_pins));

       pxa3xx_gpio_set_direction(MFP_PMIC_INT, GPIO_DIR_IN);

       ret = request_irq(IRQ_MICCO, micco_irq_handler, IRQF_TRIGGER_FALLING,

                     "Micco", NULL); //All events are dispatched here.

       ……

       /* Mask interrupts that are not needed */

       micco_write(MICCO_IRQ_MASK_A, 0xFE);

       micco_write(MICCO_IRQ_MASK_B, 0xFF);

       micco_write(MICCO_IRQ_MASK_C, 0xFF);

       micco_write(MICCO_IRQ_MASK_D, 0xFF);

       /* avoid SRAM power off during sleep*/

       micco_write(MICCO_OVER1, 0x05);

//     micco_write(MICCO_APP_OVER2, 0xff);

//     micco_write(MICCO_APP_OVER3, 0xff);

       /* Wjt replace begin, by Raymond, 2008-5-14  14:24:31 */

       #if 0

       /* Enable the ONKEY power down functionality */

       micco_write(MICCO_SYSCTRL_B, 0x20);

       #endif

       /*======== replaced by ===================*/

       /* Disable the ONKEY power down functionality */

       micco_write(MICCO_SYSCTRL_B, 0x00);     

       /* Wjt replace end, by Raymond, 2008-5-14  14:24:31 */

       micco_write(MICCO_SYSCTRL_A, 0x60);

       /* IRQ is masked during the power-up sequence and will not be released

         * until they have been read for the first time */

       micco_read(MICCO_EVENT_A, &value);

       micco_read(MICCO_EVENT_B, &value);

       micco_read(MICCO_EVENT_C, &value);

       micco_read(MICCO_EVENT_D, &value);

       micco_enable_LDO12();

#ifdef    CONFIG_PROC_FS

       create_micco_proc_file();

#endif

       pmic_set_ops(&micco_pmic_ops);

       return 0;

}

5.3.2          Function micco_irq_handler

static irqreturn_t micco_irq_handler(int irq, void *dev_id)

{

       unsigned int event;

       u8 val;

       event = micco_event_change(); //Read in the event from registers

       pmic_event_handle(event); //Process the events

       /* We don't put these codes to USB specific code because

         * we need handle it even when no USB callback registered.

         */

       if (event & PMIC_EVENT_OTGCP_IOVER) {

              /* According to Micco spec, when OTGCP_IOVER happen,

                * Need clean the USBPCP_EN in MISC. and then set

                * it again.

                */

              micco_read(MICCO_MISC, &val);

              val &= ~MICCO_MISC_USBCP_EN;

              micco_write(MICCO_MISC, val);

              val |= MICCO_MISC_USBCP_EN;

              micco_write(MICCO_MISC, val);

       }

       return IRQ_HANDLED;

}

5.3.3          Function pmic_event_handle

int pmic_event_handle(unsigned long event)

{

       int ret;

       unsigned long flags;

       struct pmic_callback *pmic_cb;

       ret = check_pmic_ops();

       if (ret < 0)

              return ret;

 

       spin_lock_irqsave(&pxa3xx_pmic_ops->cb_lock, flags);

       list_for_each_entry(pmic_cb, &pxa3xx_pmic_ops->list, list) {

              spin_unlock_irqrestore(&pxa3xx_pmic_ops->cb_lock, flags);

              /* event is bit-wise parameter, need bit AND here as filter */

              if ((pmic_cb->event & event) && (pmic_cb->func))

                     pmic_cb->func(event); //Here event is dispatched to related handler

              spin_lock_irqsave(&pxa3xx_pmic_ops->cb_lock, flags);

       }

       spin_unlock_irqrestore(&pxa3xx_pmic_ops->cb_lock, flags);

       return 0;

}

6.               Misc Points

6.1        SMBUS

I will cover it in the future if I got time.

7.               Future Works

7.1        Update Polling Mode to Interruption Mode

Polling mode will waste a lot of resources. I will cover it in the future if I got time.

8.               References

[1]. Essential Linux Device Drivers, Prentice.Hall.Essential.Linux.Device.Drivers.Apr.2008.chm

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值