I2C Controller Driver in Linux

I2C Controller Driver in Linux

The Linux driver introductory course is now online and will be continuously updated!

https://www.bilibili.com/cheese/play/ss635583370

I2C Controller Driver in Linux

SoC: RK3399

Linux Version: 5.10

01

Load and Unload Functions

// Device tree matching table
static const struct of_device_id rk3x_i2c_match[] = {
    {
        .compatible = "rockchip,rk3288-i2c",
        .data = &rk3288_soc_data
    },
    {
        .compatible = "rockchip,rk3399-i2c",
        .data = &rk3399_soc_data
    },
    {},
};
MODULE_DEVICE_TABLE(of, rk3x_i2c_match);

static struct platform_driver rk3x_i2c_driver = {
    .probe   = rk3x_i2c_probe,
    .remove  = rk3x_i2c_remove,
    .driver  = {
        .name  = "rk3x-i2c",
        .of_match_table = rk3x_i2c_match,
        .pm = &rk3x_i2c_pm_ops,
    },
};
module_platform_driver(rk3x_i2c_driver);

Controllers are generally registered using the Platform bus, so we use platform_driver for registration. The module_platform_driver macro encapsulates module_init and module_exit.

02

probe() Function

static int rk3x_i2c_probe(struct platform_device *pdev) {
    struct device_node *np = pdev->dev.of_node;
    const struct of_device_id *match;
    struct rk3x_i2c *i2c;
    int ret = 0;
    int bus_nr;
    u32 value;
    int irq;
    unsigned long clk_rate;

    i2c = devm_kzalloc(&pdev->dev, sizeof(struct rk3x_i2c), GFP_KERNEL);
    if (!i2c)
        return -ENOMEM;

    // Matching of of_device_id
    match = of_match_node(rk3x_i2c_match, np);
    // Get data (data for different SoCs)
    i2c->soc_data = match->data;

    /* use common interface to get I2C timing properties */
    i2c_parse_fw_timings(&pdev->dev, &i2c->t, true);
    strlcpy(i2c->adap.name, "rk3x-i2c", sizeof(i2c->adap.name)); // Controller name
    i2c->adap.owner = THIS_MODULE;
    i2c->adap.algo = &rk3x_i2c_algorithm;  // I2C transmission method
    i2c->adap.retries = 3;                 // Retry count
    i2c->adap.dev.of_node = np;            // Device tree node
    i2c->adap.algo_data = i2c;
    i2c->adap.dev.parent = &pdev->dev;

    i2c->dev = &pdev->dev;
    spin_lock_init(&i2c->lock);
    init_waitqueue_head(&i2c->wait);

    // Get IO resources (register address) and map to virtual address
    i2c->regs = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(i2c->regs))
        return PTR_ERR(i2c->regs);

    // Controller number (there may be multiple I2C controllers on one SoC)
    bus_nr = of_alias_get_id(np, "i2c");

    // Get interrupt number
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;  // Request interrupt
    ret = devm_request_irq(&pdev->dev, irq, rk3x_i2c_irq,
                           0, dev_name(&pdev->dev), i2c);
    if (ret < 0) {
        dev_err(&pdev->dev, "cannot request IRQ\n");
        return ret;
    }
    i2c->irq = irq;
    platform_set_drvdata(pdev, i2c);
    // Set clock
    if (i2c->soc_data->calc_timings == rk3x_i2c_v0_calc_timings) {
        /* Only one clock to use for bus clock and peripheral clock */
        i2c->clk = devm_clk_get(&pdev->dev, NULL);
        i2c->pclk = i2c->clk;
    } else {
        i2c->clk = devm_clk_get(&pdev->dev, "i2c");
        i2c->pclk = devm_clk_get(&pdev->dev, "pclk");
    }
    ret = clk_prepare(i2c->clk);
    if (ret < 0) {
        dev_err(&pdev->dev, "Can't prepare bus clk: %d\n", ret);
        return ret;
    }
    ret = clk_prepare(i2c->pclk);
    if (ret < 0) {
        dev_err(&pdev->dev, "Can't prepare periph clock: %d\n", ret);
        goto err_clk;
    }
    //.....
    // Register controller
    ret = i2c_add_adapter(&i2c->adap);
    if (ret < 0)
        goto err_clk_notifier;
    return 0;
    //.... Error handling
}

The above mainly accomplishes the following tasks:

1. Initializes the members of struct i2c_adapter

2. Gets the register address and maps it to a virtual address

3. Requests an interrupt

4. Sets the clock

5. Registers struct i2c_adapter (completes the controller registration)

03

I2C Communication Interface

static const struct i2c_algorithm rk3x_i2c_algorithm = {
    .master_xfer    = rk3x_i2c_xfer,      // I2C transmission
    .master_xfer_atomic  = rk3x_i2c_xfer_polling,
    .functionality    = rk3x_i2c_func,    // Supported functionalities
};

struct i2c_algorithm defines the data transmission and supported functionalities of the I2C controller.

3.1. Functionalities Supported by the I2C Controller

static u32 rk3x_i2c_func(struct i2c_adapter *adap) {
    /*
     ** I2C_FUNC_I2C: Standard I2C protocol
     ** I2C_FUNC_SMBUS_EMUL: Simulate SMBus operations using I2C
     ** I2C_FUNC_PROTOCOL_MANGLING: Special/non-standard operations
     */
    return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_PROTOCOL_MANGLING;
}

Returns the functionalities supported by this I2C controller

3.2. I2C Data Transmission

static int rk3x_i2c_xfer(struct i2c_adapter *adap,
       struct i2c_msg *msgs, int num) {
    return rk3x_i2c_xfer_common(adap, msgs, num, false);
}
static int rk3x_i2c_xfer_common(struct i2c_adapter *adap,
        struct i2c_msg *msgs, int num, bool polling) {
    struct rk3x_i2c *i2c = (struct rk3x_i2c *)adap->algo_data;
    unsigned long timeout, flags;
    u32 val;
    int ret = 0;
    int i;
    spin_lock_irqsave(&i2c->lock, flags);
    clk_enable(i2c->clk);
    clk_enable(i2c->pclk);
    i2c->is_last_msg = false;
    // Process messages
    for (i = 0; i < num; i += ret) {
        // Set slave device address (slave_addr) and slave device register address (reg_addr)
        ret = rk3x_i2c_setup(i2c, msgs + i, num - i);
        if (ret < 0) {
            dev_err(i2c->dev, "rk3x_i2c_setup() failed\n");
            break;
        }
        if (i + ret >= num)
            i2c->is_last_msg = true;
        spin_unlock_irqrestore(&i2c->lock, flags);
        // Polling or interrupt
        if (!polling) {
            // Transmit Start signal
            rk3x_i2c_start(i2c);
            // Wait for timeout
            timeout = wait_event_timeout(i2c->wait, !i2c->busy,
                     msecs_to_jiffies(WAIT_TIMEOUT));
        } else {
            disable_irq(i2c->irq);
            rk3x_i2c_start(i2c);
            timeout = rk3x_i2c_wait_xfer_poll(i2c);
            enable_irq(i2c->irq);
        }
        spin_lock_irqsave(&i2c->lock, flags);
        // Handle Start signal timeout
        if (timeout == 0) {
            dev_err(i2c->dev, "timeout, ipd: 0x%02x, state: %d\n",
                     i2c_readl(i2c, REG_IPD), i2c->state);
            /* Force a STOP condition without interrupt */
            i2c_writel(i2c, 0, REG_IEN);
            val = i2c_readl(i2c, REG_CON) & REG_CON_TUNING_MASK;
            val |= REG_CON_EN | REG_CON_STOP;
            i2c_writel(i2c, val, REG_CON);
            i2c->state = STATE_IDLE;
            ret = -ETIMEDOUT;
            break;
        }
        if (i2c->error) {
            ret = i2c->error;
            break;
        }
    }
    clk_disable(i2c->pclk);
    clk_disable(i2c->clk);
    spin_unlock_irqrestore(&i2c->lock, flags);
    return ret < 0 ? ret : num;
}

rk3x_i2c_setup: Sets the slave device address and slave device register address.rk3x_i2c_start: Generates Start signal. After the Start signal transmission is complete, a Start interrupt will be generated.

Both of the above functions operate on the registers of the I2C controller. Next, we will enter the interrupt handler function.

static irqreturn_t rk3x_i2c_irq(int irqno, void *dev_id) {
    struct rk3x_i2c *i2c = dev_id;
    unsigned int ipd;

    //....
    switch (i2c->state) {
        case STATE_START:
            rk3x_i2c_handle_start(i2c, ipd);
            break;
        case STATE_WRITE:
            rk3x_i2c_handle_write(i2c, ipd);
            break;
        case STATE_READ:
            rk3x_i2c_handle_read(i2c, ipd);
            break;
        case STATE_STOP:
            rk3x_i2c_handle_stop(i2c, ipd);
            break;
        case STATE_IDLE:
            break;
    }
out:
    spin_unlock(&i2c->lock);
    return IRQ_HANDLED;
}

It is essentially a state machine, where i2c->state holds the current state. Next, we enter rk3x_i2c_handle_start.

static void rk3x_i2c_handle_start(struct rk3x_i2c *i2c, unsigned int ipd) {
    if (!(ipd & REG_INT_START)) {
        rk3x_i2c_stop(i2c, -EIO);
        dev_warn(i2c->dev, "unexpected irq in START: 0x%x\n", ipd);
        rk3x_i2c_clean_ipd(i2c);
        return;
    }
    // Clear Start interrupt flag
    i2c_writel(i2c, REG_INT_START, REG_IPD);
    // Disable Start interrupt
    i2c_writel(i2c, i2c_readl(i2c, REG_CON) & ~REG_CON_START, REG_CON);
    // Enable corresponding interrupts and transmission
    if (i2c->mode == REG_CON_MOD_TX) {   // Write
        i2c_writel(i2c, REG_INT_MBTF | REG_INT_NAKRCV, REG_IEN);
        i2c->state = STATE_WRITE;
        rk3x_i2c_fill_transmit_buf(i2c);
    } else {                            // Read
        /* in any other case, we are going to be reading. */
        i2c_writel(i2c, REG_INT_MBRF | REG_INT_NAKRCV, REG_IEN);
        i2c->state = STATE_READ;
        rk3x_i2c_prepare_read(i2c);
    }
}

Depending on whether it is a read or write, the corresponding transmission is performed. After the transmission is complete, a write interrupt or read interrupt is triggered, and then it enters the above interrupt handler function, executing the corresponding function based on the state machine.STATE_WRITE –> rk3x_i2c_handle_writeSTATE_READ –> rk3x_i2c_handle_read

For example, let’s look at the write:

static void rk3x_i2c_handle_write(struct rk3x_i2c *i2c, unsigned int ipd) {
    if (!(ipd & REG_INT_MBTF)) {
        rk3x_i2c_stop(i2c, -EIO);
        dev_err(i2c->dev, "unexpected irq in WRITE: 0x%x\n", ipd);
        rk3x_i2c_clean_ipd(i2c);
        return;
    }
    // Clear interrupt
    i2c_writel(i2c, REG_INT_MBTF, REG_IPD);
    // Check if all transmissions are complete, if so send Stop signal, if not continue sending
    if (i2c->processed == i2c->msg->len)
        rk3x_i2c_stop(i2c, i2c->error);
    else
        rk3x_i2c_fill_transmit_buf(i2c);
}

After the transmission is complete, a Stop signal is generated, thus completing a write transmission process. The read process is similar, following the I2C communication timing for transmission.

END

Previous Issues

RECOMMEND

Recommended

· Linux Driver for I2C Subsystem

· Understanding I2C Bus Protocol in One Article

· Virtual File System: debugfs

· Virtual File System: sysfs

I2C Controller Driver in Linux

Leave a Comment