am335x i2c分析

/*****************************************************************************
 *                     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  
**************************************************************************
*/ 1. 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(1, 100, 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 < 1 || 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 - 1].clkrate) | i2c_pdata[bus_id - 1].clkrate = clkrate; | | i2c_pdata[bus_id - 1].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[0].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, 0, 0}, | }; | //初始化i2c1 | static void i2c1_init(int evm_id, int profile) <----+ { setup_pin_mux(i2c1_pin_mux); //设置i2c引脚复用 -----------+ omap_register_i2c_bus(2, 100, 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, 0}, }; 2. 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 = 0; /* NOTE: driver uses the static register mapping */ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!mem) { dev_err(&pdev->dev, "no mem resource?\n"); return -ENODEV; } irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); 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 = 100; /* 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) & 3; 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) >> 14) & 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 / 2); if (dev->rev >= OMAP_I2C_REV_ON_3530_4430) dev->b_hw = 0; /* Disable hardware fixes */ else dev->b_hw = 1; /* Enable hardware fixes */ /* calculate wakeup latency constraint for MPU */ if (dev->set_mpu_wkup_lat != NULL) dev->latency = (1000000 * dev->fifo_size) / (1000 * speed / 8); } /* 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 >> 4, 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 0; | | | | err_free_irq: | | free_irq(dev->irq, dev); | | err_unuse_clocks: | | omap_i2c_write_reg(dev, OMAP_I2C_CON_REG, 0); | | 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 < 0) | goto out; | | if (dev->set_mpu_wkup_lat != NULL) | dev->set_mpu_wkup_lat(dev->dev, dev->latency); | | for (i = 0; i < num; i++) { | r = omap_i2c_xfer_msg(adap, &msgs[i], (i == (num - 1))); -----+ | if (r != 0) | | break; | | } | | | | if (dev->set_mpu_wkup_lat != NULL) | | dev->set_mpu_wkup_lat(dev->dev, -1); | | | | if (r == 0) | | 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 == 0) | 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 = 0; | | w = OMAP_I2C_CON_EN | OMAP_I2C_CON_MST | OMAP_I2C_CON_STT; | | /* High speed configuration */ | if (dev->speed > 400) | 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 = 0; | if (r < 0) | return r; | if (r == 0) { | dev_err(dev->dev, "controller timed out\n"); | omap_i2c_init(dev); | return -ETIMEDOUT; | } | | if (likely(!dev->cmd_err)) | return 0; | | /* 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 0; | 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) /* -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) == 0) 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 == 0 && id != adap->nr) { status = -EBUSY; idr_remove(&i2c_adapter_idr, id); } mutex_unlock(&core_lock); if (status == -EAGAIN) goto retry; if (status == 0) 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 = 0; /* 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] == '\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 == 0) 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 0; out_list: mutex_lock(&core_lock); idr_remove(&i2c_adapter_idr, adap->nr); mutex_unlock(&core_lock); return res; }

 

posted @ 2016-05-02 13:43  SuperTao1024  阅读(1840)  评论(0编辑  收藏  举报