/***************************************************************************
* 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);
}