/*****************************************************************************
* am335x i2c分析
* i2c驱动主要关注i2c_algorithm结构体,不同芯片实现自己的master_xfer函数.
* 不同芯片i2c驱动框架都类似。
* 本文主要描述am335x_i2c设备和驱动的注册,提及文件:
* arch/arm/mach-omap2/board-am335xevm.c
* drivers/i2c/busses/i2c-omap.c
* drivers/i2c/i2c-core.c
* Tony Liu, 2016-5-2, Shenzhen
***************************************************************************/
. i2c设备注册,配置引脚复用
arch/arm/mach-omap2/board-am335xevm.c
MACHINE_START(AM335XEVM, "am335xevm")
/* Maintainer: Texas Instruments */
.atag_offset = 0x100,
.map_io = am335x_evm_map_io,
.init_early = am33xx_init_early,
.init_irq = ti81xx_init_irq,
.handle_irq = omap3_intc_handle_irq,
.timer = &omap3_am33xx_timer,
.init_machine = am335x_evm_init,
MACHINE_END static void __init am335x_evm_init(void)
{
am33xx_cpuidle_init();
am33xx_mux_init(board_mux);
omap_serial_init();
am335x_evm_i2c_init(); ---------------------+ //i2c0
omap_sdrc_init(NULL, NULL); |
usb_musb_init(&musb_board_data); |
|
omap_board_config = am335x_evm_config; |
omap_board_config_size = ARRAY_SIZE(am335x_evm_config); |
|
daughter_brd_detected = false; |
setup_xxx_xxxx(); ---------------|--+ //i2c1, i2c2
| |
/*create /proc/boardname to export info to userspace*/ | |
proc_init(); | |
| |
/* Create an alias for icss clock */ | |
if (clk_add_alias("pruss", NULL, "pruss_uart_gclk", NULL)) | |
pr_warn("failed to create an alias: icss_uart_gclk --> pruss\n"); | |
/* Create an alias for gfx/sgx clock */ | |
if (clk_add_alias("sgx_ck", NULL, "gfx_fclk", NULL)) | |
pr_warn("failed to create an alias: gfx_fclk --> sgx_ck\n"); | |
} | |
//初始化i2c 0,由于i2c0引脚的mode0就是i2c功能,所以不需要配置引脚复用 | |
static void __init am335x_evm_i2c_init(void) <-------------+ |
{ |
/* Initially assume General Purpose EVM Config */ |
am335x_evm_id = EVM_SK; |
// i2c 0, speed: 100k |
omap_register_i2c_bus(, , i2c0_boardinfo,ARRAY_SIZE(i2c0_boardinfo)); --+ |
} | | |
| | |
// i2c设备的设备地址 | | |
static struct i2c_board_info i2c0_boardinfo[] = { <-----+ | |
{ | |
I2C_BOARD_INFO("tps65910", TPS65910_I2C_ID1), | |
.platform_data = &am335x_tps65910_info, | |
}, | |
{ | |
I2C_BOARD_INFO("24c02", 0x50), | |
}, | |
}; | |
| |
int __init omap_register_i2c_bus(int bus_id, u32 clkrate, <----+ |
struct i2c_board_info const *info, |
unsigned len) |
{ |
int err; |
|
BUG_ON(bus_id < || bus_id > omap_i2c_nr_ports()); |
|
if (info) { |
err = i2c_register_board_info(bus_id, info, len); |
if (err) |
return err; |
} |
|
if (!i2c_pdata[bus_id - ].clkrate) |
i2c_pdata[bus_id - ].clkrate = clkrate; |
|
i2c_pdata[bus_id - ].clkrate &= ~OMAP_I2C_CMDLINE_SETUP; |
//注册i2c设备 |
return omap_i2c_add_bus(bus_id); |
} |
|
static void setup_xxx_xxxx(void) <-------------+
{ /*which doesn't have Write Protect pin LAN8710A_PHY_ID */
am335x_mmc[].gpio_wp = -EINVAL; int ret; _configure_device(EVM_SK, xxx_xxxx_dev_cfg, PROFILE_NONE); ---+
...... |
} |
|
static struct evm_dev_cfg xxx_xxxx_dev_cfg[] = { <--+
...... ---+
{i2c1_init, DEV_ON_BASEBOARD, PROFILE_ALL}, |
{i2c2_init, DEV_ON_BASEBOARD, PROFILE_ALL}, |
...... |
{NULL, , }, |
}; |
//初始化i2c1 |
static void i2c1_init(int evm_id, int profile) <----+
{
setup_pin_mux(i2c1_pin_mux); //设置i2c引脚复用 -----------+
omap_register_i2c_bus(, , am335x_i2c1_boardinfo2,ARRAY_SIZE(am335x_i2c1_boardinfo2));|
return; |
} |
|
static struct i2c_board_info am335x_i2c1_boardinfo2[] = { |
{ |
I2C_BOARD_INFO("ds1337", 0x68), |
}, |
{ |
I2C_BOARD_INFO("tlv320aic3x", 0x1b), |
}, |
}; |
|
static struct pinmux_config i2c1_pin_mux[] = { <-------+
{"spi0_d1.i2c1_sda", OMAP_MUX_MODE2 | AM33XX_SLEWCTRL_SLOW |
AM33XX_PULL_ENBL | AM33XX_INPUT_EN},
{"spi0_cs0.i2c1_scl", OMAP_MUX_MODE2 | AM33XX_SLEWCTRL_SLOW |
AM33XX_PULL_ENBL | AM33XX_INPUT_EN},
{NULL, },
}; . i2c 驱动注册
drivers/i2c/busses/i2c-omap.c
static int __init
omap_i2c_init_driver(void)
{
return platform_driver_register(&omap_i2c_driver); -----+
} |
subsys_initcall(omap_i2c_init_driver); |
|
static struct platform_driver omap_i2c_driver = { <----+
.probe = omap_i2c_probe, -----+
.remove = omap_i2c_remove, |
.driver = { |
.name = "omap_i2c", |
.owner = THIS_MODULE, |
.pm = OMAP_I2C_PM_OPS, |
}, |
}; |
|
static int __devinit |
omap_i2c_probe(struct platform_device *pdev) <---+
{
struct omap_i2c_dev *dev;
struct i2c_adapter *adap;
struct resource *mem, *irq, *ioarea;
struct omap_i2c_bus_platform_data *pdata = pdev->dev.platform_data;
irq_handler_t isr;
int r;
u32 speed = ; /* NOTE: driver uses the static register mapping */
mem = platform_get_resource(pdev, IORESOURCE_MEM, );
if (!mem) {
dev_err(&pdev->dev, "no mem resource?\n");
return -ENODEV;
}
irq = platform_get_resource(pdev, IORESOURCE_IRQ, );
if (!irq) {
dev_err(&pdev->dev, "no irq resource?\n");
return -ENODEV;
} ioarea = request_mem_region(mem->start, resource_size(mem),
pdev->name);
if (!ioarea) {
dev_err(&pdev->dev, "I2C region already claimed\n");
return -EBUSY;
} dev = kzalloc(sizeof(struct omap_i2c_dev), GFP_KERNEL);
if (!dev) {
r = -ENOMEM;
goto err_release_region;
} if (pdata != NULL) {
speed = pdata->clkrate;
dev->set_mpu_wkup_lat = pdata->set_mpu_wkup_lat;
} else {
speed = ; /* Default speed */
dev->set_mpu_wkup_lat = NULL;
} dev->speed = speed;
dev->dev = &pdev->dev;
dev->irq = irq->start;
dev->base = ioremap(mem->start, resource_size(mem));
if (!dev->base) {
r = -ENOMEM;
goto err_free_mem;
} platform_set_drvdata(pdev, dev); dev->reg_shift = (pdata->flags >> OMAP_I2C_FLAG_BUS_SHIFT__SHIFT) & ; if (pdata->rev == OMAP_I2C_IP_VERSION_2)
dev->regs = (u8 *)reg_map_ip_v2;
else
dev->regs = (u8 *)reg_map_ip_v1; pm_runtime_enable(dev->dev);
pm_runtime_get_sync(dev->dev); dev->rev = omap_i2c_read_reg(dev, OMAP_I2C_REV_REG) & 0xff; if (dev->rev <= OMAP_I2C_REV_ON_3430)
dev->errata |= I2C_OMAP3_1P153; if (!(pdata->flags & OMAP_I2C_FLAG_NO_FIFO)) {
u16 s; /* Set up the fifo size - Get total size */
s = (omap_i2c_read_reg(dev, OMAP_I2C_BUFSTAT_REG) >> ) & 0x3;
dev->fifo_size = 0x8 << s; /*
* Set up notification threshold as half the total available
* size. This is to ensure that we can handle the status on int
* call back latencies.
*/ dev->fifo_size = (dev->fifo_size / ); if (dev->rev >= OMAP_I2C_REV_ON_3530_4430)
dev->b_hw = ; /* Disable hardware fixes */
else
dev->b_hw = ; /* Enable hardware fixes */ /* calculate wakeup latency constraint for MPU */
if (dev->set_mpu_wkup_lat != NULL)
dev->latency = ( * dev->fifo_size) /
( * speed / );
} /* reset ASAP, clearing any IRQs */
omap_i2c_init(dev); isr = (dev->rev < OMAP_I2C_OMAP1_REV_2) ? omap_i2c_omap1_isr :
omap_i2c_isr;
r = request_irq(dev->irq, isr, IRQF_NO_SUSPEND, pdev->name, dev); if (r) {
dev_err(dev->dev, "failure requesting irq %i\n", dev->irq);
goto err_unuse_clocks;
} dev_info(dev->dev, "bus %d rev%d.%d.%d at %d kHz\n", pdev->id,
pdata->rev, dev->rev >> , dev->rev & 0xf, dev->speed); pm_runtime_put(dev->dev); adap = &dev->adapter;
i2c_set_adapdata(adap, dev);
adap->owner = THIS_MODULE;
adap->class = I2C_CLASS_HWMON;
strlcpy(adap->name, "OMAP I2C adapter", sizeof(adap->name));
adap->algo = &omap_i2c_algo; //i2c发送和接受的算法函数 ------+
adap->dev.parent = &pdev->dev; |
|
/* i2c device drivers may be active on return from add_adapter() */ |
adap->nr = pdev->id; |
r = i2c_add_numbered_adapter(adap); ----------|--+
if (r) { | |
dev_err(dev->dev, "failure adding adapter\n"); | |
goto err_free_irq; | |
} | |
| |
return ; | |
| |
err_free_irq: | |
free_irq(dev->irq, dev); | |
err_unuse_clocks: | |
omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, ); | |
pm_runtime_put(dev->dev); | |
iounmap(dev->base); | |
err_free_mem: | |
platform_set_drvdata(pdev, NULL); | |
kfree(dev); | |
err_release_region: | |
release_mem_region(mem->start, resource_size(mem)); | |
| |
return r; | |
} | |
| |
static const struct i2c_algorithm omap_i2c_algo = { <-----+ |
.master_xfer = omap_i2c_xfer, ------+ |
.functionality = omap_i2c_func, | |
}; | | |
V | |
omap_i2c_func(struct i2c_adapter *adap) | |
{ | |
return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK); | |
} | |
| |
static int | |
omap_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], int num) <-+ |
{ |
struct omap_i2c_dev *dev = i2c_get_adapdata(adap); |
int i; |
int r; |
|
pm_runtime_get_sync(dev->dev); |
|
r = omap_i2c_wait_for_bb(dev); |
if (r < ) |
goto out; |
|
if (dev->set_mpu_wkup_lat != NULL) |
dev->set_mpu_wkup_lat(dev->dev, dev->latency); |
|
for (i = ; i < num; i++) { |
r = omap_i2c_xfer_msg(adap, &msgs[i], (i == (num - ))); -----+ |
if (r != ) | |
break; | |
} | |
| |
if (dev->set_mpu_wkup_lat != NULL) | |
dev->set_mpu_wkup_lat(dev->dev, -); | |
| |
if (r == ) | |
r = num; | |
| |
omap_i2c_wait_for_bb(dev); | |
out: | |
pm_runtime_put(dev->dev); | |
return r; | |
} | |
| |
static int omap_i2c_xfer_msg(struct i2c_adapter *adap, <----+ |
struct i2c_msg *msg, int stop) |
{ |
struct omap_i2c_dev *dev = i2c_get_adapdata(adap); |
int r; |
u16 w; |
|
dev_dbg(dev->dev, "addr: 0x%04x, len: %d, flags: 0x%x, stop: %d\n", |
msg->addr, msg->len, msg->flags, stop); |
|
if (msg->len == ) |
return -EINVAL; |
//写设备地址 |
omap_i2c_write_reg(dev, OMAP_I2C_SA_REG, msg->addr); |
|
/* REVISIT: Could the STB bit of I2C_CON be used with probing? */ |
dev->buf = msg->buf; |
dev->buf_len = msg->len; |
//接受数据的长度 |
omap_i2c_write_reg(dev, OMAP_I2C_CNT_REG, dev->buf_len); |
|
/* Clear the FIFO Buffers */ |
w = omap_i2c_read_reg(dev, OMAP_I2C_BUF_REG); |
w |= OMAP_I2C_BUF_RXFIF_CLR | OMAP_I2C_BUF_TXFIF_CLR; |
omap_i2c_write_reg(dev, OMAP_I2C_BUF_REG, w); |
|
init_completion(&dev->cmd_complete); |
dev->cmd_err = ; |
|
w = OMAP_I2C_CON_EN | OMAP_I2C_CON_MST | OMAP_I2C_CON_STT; |
|
/* High speed configuration */ |
if (dev->speed > ) |
w |= OMAP_I2C_CON_OPMODE_HS; |
|
if (msg->flags & I2C_M_TEN) |
w |= OMAP_I2C_CON_XA; |
if (!(msg->flags & I2C_M_RD)) |
w |= OMAP_I2C_CON_TRX; |
|
if (!dev->b_hw && stop) |
w |= OMAP_I2C_CON_STP; |
|
omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w); |
|
/* |
* Don't write stt and stp together on some hardware. |
*/ |
if (dev->b_hw && stop) { |
unsigned long delay = jiffies + OMAP_I2C_TIMEOUT; |
u16 con = omap_i2c_read_reg(dev, OMAP_I2C_CON_REG); |
while (con & OMAP_I2C_CON_STT) { |
con = omap_i2c_read_reg(dev, OMAP_I2C_CON_REG); |
|
/* Let the user know if i2c is in a bad state */ |
if (time_after(jiffies, delay)) { |
dev_err(dev->dev, "controller timed out " |
"waiting for start condition to finish\n"); |
return -ETIMEDOUT; |
} |
cpu_relax(); |
} |
|
w |= OMAP_I2C_CON_STP; |
w &= ~OMAP_I2C_CON_STT; |
omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w); |
} |
|
/* |
* REVISIT: We should abort the transfer on signals, but the bus goes |
* into arbitration and we're currently unable to recover from it. |
*/ |
r = wait_for_completion_timeout(&dev->cmd_complete, |
OMAP_I2C_TIMEOUT); |
dev->buf_len = ; |
if (r < ) |
return r; |
if (r == ) { |
dev_err(dev->dev, "controller timed out\n"); |
omap_i2c_init(dev); |
return -ETIMEDOUT; |
} |
|
if (likely(!dev->cmd_err)) |
return ; |
|
/* We have an error */ |
if (dev->cmd_err & (OMAP_I2C_STAT_AL | OMAP_I2C_STAT_ROVR | |
OMAP_I2C_STAT_XUDF)) { |
omap_i2c_init(dev); |
return -EIO; |
} |
|
if (dev->cmd_err & OMAP_I2C_STAT_NACK) { |
if (msg->flags & I2C_M_IGNORE_NAK) |
return ; |
if (stop) { |
w = omap_i2c_read_reg(dev, OMAP_I2C_CON_REG); |
w |= OMAP_I2C_CON_STP; |
omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, w); |
} |
return -EREMOTEIO; |
} |
return -EIO; |
} |
|
int i2c_add_numbered_adapter(struct i2c_adapter *adap) <------+
{
int id;
int status; if (adap->nr == -) /* -1 means dynamically assign bus id */
return i2c_add_adapter(adap);
if (adap->nr & ~MAX_ID_MASK)
return -EINVAL; retry:
if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == )
return -ENOMEM; mutex_lock(&core_lock);
/* "above" here means "above or equal to", sigh;
* we need the "equal to" result to force the result
*/
status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);
if (status == && id != adap->nr) {
status = -EBUSY;
idr_remove(&i2c_adapter_idr, id);
}
mutex_unlock(&core_lock);
if (status == -EAGAIN)
goto retry; if (status == )
status = i2c_register_adapter(adap); ----+
return status; |
} |
EXPORT_SYMBOL_GPL(i2c_add_numbered_adapter); |
|
static int i2c_register_adapter(struct i2c_adapter *adap) <---+
{
int res = ; /* Can't register until after driver model init */
if (unlikely(WARN_ON(!i2c_bus_type.p))) {
res = -EAGAIN;
goto out_list;
} /* Sanity checks */
if (unlikely(adap->name[] == '\0')) {
pr_err("i2c-core: Attempt to register an adapter with "
"no name!\n");
return -EINVAL;
}
if (unlikely(!adap->algo)) {
pr_err("i2c-core: Attempt to register adapter '%s' with "
"no algo!\n", adap->name);
return -EINVAL;
} rt_mutex_init(&adap->bus_lock);
mutex_init(&adap->userspace_clients_lock);
INIT_LIST_HEAD(&adap->userspace_clients); /* Set default timeout to 1 second if not already set */
if (adap->timeout == )
adap->timeout = HZ; dev_set_name(&adap->dev, "i2c-%d", adap->nr); // i2c 设备名
adap->dev.bus = &i2c_bus_type;
adap->dev.type = &i2c_adapter_type;
res = device_register(&adap->dev);
if (res)
goto out_list; dev_dbg(&adap->dev, "adapter [%s] registered\n", adap->name); #ifdef CONFIG_I2C_COMPAT
res = class_compat_create_link(i2c_adapter_compat_class, &adap->dev,
adap->dev.parent);
if (res)
dev_warn(&adap->dev,
"Failed to create compatibility class link\n");
#endif /* create pre-declared device nodes */
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap); /* Notify drivers */
mutex_lock(&core_lock);
bus_for_each_drv(&i2c_bus_type, NULL, adap, __process_new_adapter);
mutex_unlock(&core_lock); return ; out_list:
mutex_lock(&core_lock);
idr_remove(&i2c_adapter_idr, adap->nr);
mutex_unlock(&core_lock);
return res;
}