OK335xS I2C device registe hacking
/*************************************************************************** * OK335xS I2C device registe hacking * 声明: * 1. 本文是对OK335xS Linux内核中I2C设备注册代码进行跟踪; * 2. 本人在文中采用函数调用线路图进行标记,方便阅读; * 3. 代码跟踪的用的是vim+ctags; * 2015-7-1 晴 深圳 南山平山村 曾剑锋 **************************************************************************/ 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_mux_init(board_mux); --+ omap_serial_init(); | am335x_evm_i2c_init(); ------*--------------------------------------+ ...... | | } | | V | static struct omap_board_mux board_mux[] __initdata = { | AM33XX_MUX(XDMA_EVENT_INTR0, OMAP_MUX_MODE3 | AM33XX_PIN_OUTPUT),--+ | AM33XX_MUX(I2C0_SDA, OMAP_MUX_MODE0 | AM33XX_SLEWCTRL_SLOW | | | AM33XX_INPUT_EN | AM33XX_PIN_OUTPUT), | | AM33XX_MUX(I2C0_SCL, OMAP_MUX_MODE0 | AM33XX_SLEWCTRL_SLOW | | | AM33XX_INPUT_EN | AM33XX_PIN_OUTPUT), | | { .reg_offset = OMAP_MUX_TERMINATOR }, | | }; +--------------------------------------------------------+ | V | #define AM33XX_MUX(mode0, mux_value) \ | { \ | // 若果mode0 = I2C0_SDA; | // AM33XX_CONTROL_PADCONF_I2C0_SDA_OFFSET ----------------------+ | .reg_offset = (AM33XX_CONTROL_PADCONF_##mode0##_OFFSET), \ | | .value = (((mux_value) & AM33XX_INPUT_EN) ? (mux_value)\ | | : ((mux_value) | AM33XX_PULL_DISA)), \ | | } | | | | /** | | * 参考文档:AM335x ARM Cortex-A8 Microprocessors (MPUs) Technical | | * Reference Manual (Rev. H).pdf | | * 参考文档:Sitara AM335x ARM Cortex-A8 Microprocessors (MPUs) | | * (Rev. F).pdf | | * ------------------------------------------------------------------ | | * | Offset | Acronym | Register Description | Section | | | * +--------+---------------+----------------------|----------------+ | | * | 988h | conf_i2c0_sda | | Section 9.3.51 | | | */ ------------------------------------------------------------------ | | #define AM33XX_CONTROL_PADCONF_I2C0_SDA_OFFSET 0x0988 <-----+ | | static void __init am335x_evm_i2c_init(void) <------------+ { /* Initially assume General Purpose EVM Config */ am335x_evm_id = GEN_PURP_EVM; evm_init_cpld(); --------------+ | setup_pin_mux(pmic_irq_pin_mux); | omap_register_i2c_bus(1, 100, am335x_i2c0_boardinfo, --*--------+ ARRAY_SIZE(am335x_i2c0_boardinfo)); | | } | | | | static void evm_init_cpld(void) <---------------+ | { | i2c_add_driver(&cpld_reg_driver); | } | | V | static struct i2c_driver cpld_reg_driver = { | .driver = { | .name = "cpld_reg", | }, | .probe = cpld_reg_probe, ---+ | .remove = cpld_reg_remove, | | .id_table = cpld_reg_id, | | }; +---------------------+ | V | static int cpld_reg_probe(struct i2c_client *client, | const struct i2c_device_id *id) | { | cpld_client = client; | return 0; | } +-----------------+ V | static struct i2c_board_info __initdata am335x_i2c0_boardinfo[] = { | { | /* Daughter Board EEPROM */ | I2C_BOARD_INFO("24c256", DAUG_BOARD_I2C_ADDR), | .platform_data = &am335x_daughter_board_eeprom_info, | }, | { | /* Baseboard board EEPROM */ | I2C_BOARD_INFO("24c256", BASEBOARD_I2C_ADDR), | .platform_data = &am335x_baseboard_eeprom_info, | }, | { | I2C_BOARD_INFO("cpld_reg", 0x35), | }, | { | I2C_BOARD_INFO("tlc59108", 0x40), | }, | { | I2C_BOARD_INFO("tps65910", TPS65910_I2C_ID1), | .platform_data = &am335x_tps65910_info, | }, | { | I2C_BOARD_INFO("tlv320aic3x", 0x1b), | }, | }; +---------------------------------------------+ V 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; | | return omap_i2c_add_bus(bus_id); --------------------*----------+ } +---------------------------------------+ | V | int __init i2c_register_board_info(int busnum, | struct i2c_board_info const *info, unsigned len) | { | int status; | | down_write(&__i2c_board_lock); | | /* dynamic bus numbers will be assigned after the last static one */ | if (busnum >= __i2c_first_dynamic_bus_num) | __i2c_first_dynamic_bus_num = busnum + 1; | | for (status = 0; len; len--, info++) { | struct i2c_devinfo *devinfo; | | devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL); | if (!devinfo) { | pr_debug("i2c-core: can't register boardinfo!\n"); | status = -ENOMEM; | break; | } | | devinfo->busnum = busnum; | devinfo->board_info = *info; | list_add_tail(&devinfo->list, &__i2c_board_list); | } | | up_write(&__i2c_board_lock); | | return status; | } | | static int __init omap_i2c_add_bus(int bus_id) <------------------+ { if (cpu_class_is_omap1()) return omap1_i2c_add_bus(bus_id); else return omap2_i2c_add_bus(bus_id); ----------+ } | | static inline int omap2_i2c_add_bus(int bus_id) <------+ { int l; struct omap_hwmod *oh; struct platform_device *pdev; char oh_name[MAX_OMAP_I2C_HWMOD_NAME_LEN]; struct omap_i2c_bus_platform_data *pdata; struct omap_i2c_dev_attr *dev_attr; if (!cpu_is_am33xx()) omap2_i2c_mux_pins(bus_id); l = snprintf(oh_name, MAX_OMAP_I2C_HWMOD_NAME_LEN, "i2c%d", bus_id); WARN(l >= MAX_OMAP_I2C_HWMOD_NAME_LEN, "String buffer overflow in I2C%d device setup\n", bus_id); oh = omap_hwmod_lookup(oh_name); ---------------------------------+ if (!oh) { | pr_err("Could not look up %s\n", oh_name); | return -EEXIST; | } | | pdata = &i2c_pdata[bus_id - 1]; | /* | * pass the hwmod class's CPU-specific knowledge of I2C IP revision in | * use, and functionality implementation flags, up to the OMAP I2C | * driver via platform data | */ | pdata->rev = oh->class->rev; | | dev_attr = (struct omap_i2c_dev_attr *)oh->dev_attr; | pdata->flags = dev_attr->flags; | | /* | * When waiting for completion of a i2c transfer, we need to | * set a wake up latency constraint for the MPU. This is to | * ensure quick enough wakeup from idle, when transfer | * completes. | * Only omap3 has support for constraints | */ | if (cpu_is_omap34xx()) | pdata->set_mpu_wkup_lat = omap_pm_set_max_mpu_wakeup_lat_compat; | | pdata->device_reset = omap_device_reset; | pdev = omap_device_build(name, bus_id, oh, pdata, ---------+ | sizeof(struct omap_i2c_bus_platform_data), | | NULL, 0, 0); | | WARN(IS_ERR(pdev), "Could not build omap_device for %s\n", name); | | | | return PTR_RET(pdev); | | } +--------------------------------------------*--+ V | struct omap_hwmod *omap_hwmod_lookup(const char *name) | { | struct omap_hwmod *oh; | | if (!name) | return NULL; | | oh = _lookup(name); -----+ | | | return oh; | | } | | V | static struct omap_hwmod *_lookup(const char *name) | { | struct omap_hwmod *oh, *temp_oh; | | oh = NULL; | | list_for_each_entry(temp_oh, &omap_hwmod_list, node) { | if (!strcmp(name, temp_oh->name)) { | oh = temp_oh; | break; | } | } +---------------------------------------+ | return oh; | } | V struct platform_device *omap_device_build(const char *pdev_name, int pdev_id, struct omap_hwmod *oh, void *pdata, int pdata_len, struct omap_device_pm_latency *pm_lats, int pm_lats_cnt, int is_early_device) { struct omap_hwmod *ohs[] = { oh }; if (!oh) return ERR_PTR(-EINVAL); return omap_device_build_ss(pdev_name, pdev_id, ohs, 1, pdata, ---+ pdata_len, pm_lats, pm_lats_cnt, | is_early_device); | } +-----------------------------------+ V struct platform_device *omap_device_build_ss(const char *pdev_name, int pdev_id, struct omap_hwmod **ohs, int oh_cnt, void *pdata, int pdata_len, struct omap_device_pm_latency *pm_lats, int pm_lats_cnt, int is_early_device) { int ret = -ENOMEM; struct platform_device *pdev; struct omap_device *od; if (!ohs || oh_cnt == 0 || !pdev_name) return ERR_PTR(-EINVAL); if (!pdata && pdata_len > 0) return ERR_PTR(-EINVAL); pdev = platform_device_alloc(pdev_name, pdev_id); if (!pdev) { ret = -ENOMEM; goto odbs_exit; } /* Set the dev_name early to allow dev_xxx in omap_device_alloc */ if (pdev->id != -1) dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id); else dev_set_name(&pdev->dev, "%s", pdev->name); od = omap_device_alloc(pdev, ohs, oh_cnt, pm_lats, pm_lats_cnt); if (!od) goto odbs_exit1; ret = platform_device_add_data(pdev, pdata, pdata_len); if (ret) goto odbs_exit2; if (is_early_device) ret = omap_early_device_register(pdev); ---------------------+ else | ret = omap_device_register(pdev); ---------------------*--+ if (ret) | | goto odbs_exit2; | | | | return pdev; | | | | odbs_exit2: | | omap_device_delete(od); | | odbs_exit1: | | platform_device_put(pdev); | | odbs_exit: | | | | pr_err("omap_device: %s: build failed (%d)\n", pdev_name, ret); | | | | return ERR_PTR(ret); | | } +--------------------------------------------+ | V | static int omap_early_device_register(struct platform_device *pdev) | { | struct platform_device *devices[1]; | | devices[0] = pdev; | early_platform_add_devices(devices, 1); | return 0; | } +----------------------------------------------------------+ V int omap_device_register(struct platform_device *pdev) { pr_debug("omap_device: %s: registering\n", pdev->name); pdev->dev.parent = &omap_device_parent; pdev->dev.pm_domain = &omap_device_pm_domain; return platform_device_add(pdev); }