
The Linux driver introductory course is now online and will be continuously updated!
https://www.bilibili.com/cheese/play/ss635583370

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
