[U-Boot] [PATCH v2 13/16] I2C: adding new "i2c bus" Command to the I2C Subsystem.

With this Command it is possible to add new I2C Busses, which are behind 1 .. n I2C Muxes. Details see README.
Signed-off-by: Heiko Schocher hs@denx.de --- README | 47 ++++++++ common/cmd_i2c.c | 267 ++++++++++++++++++++++++++++++++++++++++++++++ cpu/mpc8260/i2c.c | 15 +++- drivers/i2c/soft_i2c.c | 15 +++- include/configs/mgcoge.h | 1 + include/configs/mgsuvd.h | 1 + include/i2c.h | 23 ++++ 7 files changed, 367 insertions(+), 2 deletions(-)
diff --git a/README b/README index b10b539..46353f4 100644 --- a/README +++ b/README @@ -1429,6 +1429,53 @@ The following options need to be configured: Define this option if you want to use Freescale's I2C driver in drivers/i2c/fsl_i2c.c.
+ CONFIG_I2C_MUX + + Define this option if you have I2C devices reached over 1 .. n + I2C Muxes like the pca9544a. This option addes a new I2C + Command "i2c bus [muxtype:muxaddr:muxchannel]" which adds a + new I2C Bus to the existing I2C Busses. If you select the + new Bus with "i2c dev", u-bbot sends first the commandos for + the muxes to activate this new "bus". + + CONFIG_I2C_MULTI_BUS must be also defined, to use this + feature! + + Example: + Adding a new I2C Bus reached over 2 pca9544a muxes + The First mux with address 70 and channel 6 + The Second mux with address 71 and channel 4 + + => i2c bus pca9544a:70:6:pca9544a:71:4 + + Use the "i2c bus" command without parameter, to get a list + of I2C Busses with muxes: + + => i2c bus + Busses reached over muxes: + Bus ID: 2 + reached over Mux(es): + pca9544a@70 ch: 4 + Bus ID: 3 + reached over Mux(es): + pca9544a@70 ch: 6 + pca9544a@71 ch: 4 + => + + If you now switch to the new I2C Bus 3 with "i2c dev 3" + u-boot sends First the Commando to the mux@70 to enable + channel 6, and then the Commando to the mux@71 to enable + the channel 4. + + After that, you can use the "normal" i2c commands as + usual, to communicate with your I2C devices behind + the 2 muxes. + + This option is actually implemented for the bitbanging + algorithm in common/soft_i2c.c and for the Hardware I2C + Bus on the MPC8260. But it should be not so difficult + to add this option to other architectures. +
- SPI Support: CONFIG_SPI
diff --git a/common/cmd_i2c.c b/common/cmd_i2c.c index c0afd11..17cdc9f 100644 --- a/common/cmd_i2c.c +++ b/common/cmd_i2c.c @@ -83,7 +83,9 @@
#include <common.h> #include <command.h> +#include <environment.h> #include <i2c.h> +#include <malloc.h> #include <asm/byteorder.h>
/* Display values from last command. @@ -125,6 +127,14 @@ static uchar i2c_no_probes[] = CFG_I2C_NOPROBES; #define NUM_ELEMENTS_NOPROBE (sizeof(i2c_no_probes)/sizeof(i2c_no_probes[0])) #endif
+#if defined(CONFIG_I2C_MUX) +static I2C_MUX_DEVICE *i2c_mux_devices = NULL; +static int i2c_mux_busid = CFG_MAX_I2C_BUS; + +DECLARE_GLOBAL_DATA_PTR; + +#endif + static int mod_i2c_mem(cmd_tbl_t *cmdtp, int incrflag, int flag, int argc, char *argv[]);
@@ -1188,6 +1198,37 @@ int do_i2c_reset(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[]) return 0; }
+#if defined(CONFIG_I2C_MUX) +int do_i2c_add_bus(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[]) +{ + int ret=0; + + if (argc == 1) { + /* show all busses */ + I2C_MUX *mux; + I2C_MUX_DEVICE *device = i2c_mux_devices; + + printf ("Busses reached over muxes:\n"); + while (device != NULL) { + printf ("Bus ID: %x\n", device->busid); + printf (" reached over Mux(es):\n"); + mux = device->mux; + while (mux != NULL) { + printf (" %s@%x ch: %x\n", mux->name, mux->chip, mux->channel); + mux = mux->next; + } + device = device->next; + } + } else { + I2C_MUX_DEVICE *dev; + + dev = i2c_mux_ident_muxstring ((uchar *)argv[1]); + ret = 0; + } + return ret; +} +#endif /* CONFIG_I2C_MUX */ + #if defined(CONFIG_I2C_MULTI_BUS) int do_i2c_bus_num(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[]) { @@ -1226,6 +1267,10 @@ int do_i2c_bus_speed(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[])
int do_i2c(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[]) { +#if defined(CONFIG_I2C_MUX) + if (!strncmp(argv[1], "bu", 2)) + return do_i2c_add_bus(cmdtp, flag, --argc, ++argv); +#endif /* CONFIG_I2C_MUX */ if (!strncmp(argv[1], "sp", 2)) return do_i2c_bus_speed(cmdtp, flag, --argc, ++argv); #if defined(CONFIG_I2C_MULTI_BUS) @@ -1264,6 +1309,9 @@ int do_i2c(cmd_tbl_t * cmdtp, int flag, int argc, char *argv[]) U_BOOT_CMD( i2c, 6, 1, do_i2c, "i2c - I2C sub-system\n", +#if defined(CONFIG_I2C_MUX) + "bus [muxtype:muxaddr:muxchannel] - add a new bus reached over muxes.\n" +#endif /* CONFIG_I2C_MUX */ "speed [speed] - show or set I2C bus speed\n" #if defined(CONFIG_I2C_MULTI_BUS) "i2c dev [dev] - show or set current I2C bus\n" @@ -1335,3 +1383,222 @@ U_BOOT_CMD( " (valid chip values 50..57)\n" ); #endif + +#if defined(CONFIG_I2C_MUX) + +int i2c_mux_add_device(I2C_MUX_DEVICE *dev) +{ + I2C_MUX_DEVICE *devtmp = i2c_mux_devices; + + if (i2c_mux_devices == NULL) { + i2c_mux_devices = dev; + return 0; + } + while (devtmp->next != NULL) + devtmp = devtmp->next; + + devtmp->next = dev; + return 0; +} + +I2C_MUX_DEVICE *i2c_mux_search_device(int id) +{ + I2C_MUX_DEVICE *device = i2c_mux_devices; + + while (device != NULL) { + if (device->busid == id) + return device; + device = device->next; + } + return NULL; +} + +/* searches in the buf from *pos the next ':'. + * returns: + * 0 if found (with *pos = where) + * < 0 if an error occured + * > 0 if the end of buf is reached + */ +static int i2c_mux_search_next (int *pos, uchar *buf, int len) +{ + while ((buf[*pos] != ':') && (*pos < len)) { + *pos += 1; + } + if (*pos >= len) + return 1; + if (buf[*pos] != ':') + return -1; + return 0; +} + +static int i2c_mux_get_busid (void) +{ + int tmp = i2c_mux_busid; + + i2c_mux_busid ++; + return tmp; +} + +/* Analyses a Muxstring and sends immediately the + Commands to the Muxes. Runs from Flash. + */ +int i2c_mux_ident_muxstring_f (uchar *buf) +{ + int pos = 0; + int oldpos; + int ret = 0; + int len = strlen((char *)buf); + int chip; + uchar channel; + int was = 0; + + while (ret == 0) { + oldpos = pos; + /* search name */ + ret = i2c_mux_search_next(&pos, buf, len); + if (ret != 0) + printf ("ERROR\n"); + /* search address */ + pos ++; + oldpos = pos; + ret = i2c_mux_search_next(&pos, buf, len); + if (ret != 0) + printf ("ERROR\n"); + buf[pos] = 0; + chip = simple_strtoul((char *)&buf[oldpos], NULL, 16); + buf[pos] = ':'; + /* search channel */ + pos ++; + oldpos = pos; + ret = i2c_mux_search_next(&pos, buf, len); + if (ret < 0) + printf ("ERROR\n"); + was = 0; + if (buf[pos] != 0) { + buf[pos] = 0; + was = 1; + } + channel = simple_strtoul((char *)&buf[oldpos], NULL, 16); + if (was) + buf[pos] = ':'; + if (i2c_write(chip, 0, 0, &channel, 1) != 0) { + printf ("Error setting Mux: chip:%x channel: \ + %x\n", chip, channel); + return -1; + } + pos ++; + oldpos = pos; + + } + + return 0; +} + +/* Analyses a Muxstring and if this String is correct + * adds a new I2C Bus. + */ +I2C_MUX_DEVICE *i2c_mux_ident_muxstring (uchar *buf) +{ + I2C_MUX_DEVICE *device; + I2C_MUX *mux; + int pos = 0; + int oldpos; + int ret = 0; + int len = strlen((char *)buf); + int was = 0; + + device = (I2C_MUX_DEVICE *)malloc (sizeof(I2C_MUX_DEVICE)); + device->mux = NULL; + device->busid = i2c_mux_get_busid (); + device->next = NULL; + while (ret == 0) { + mux = (I2C_MUX *)malloc (sizeof(I2C_MUX)); + mux->next = NULL; + /* search name of mux */ + oldpos = pos; + ret = i2c_mux_search_next(&pos, buf, len); + if (ret != 0) + printf ("%s no name.\n", __FUNCTION__); + mux->name = (char *)malloc (pos - oldpos + 1); + memcpy (mux->name, &buf[oldpos], pos - oldpos); + mux->name[pos - oldpos] = 0; + /* search address */ + pos ++; + oldpos = pos; + ret = i2c_mux_search_next(&pos, buf, len); + if (ret != 0) + printf ("%s no mux address.\n", __FUNCTION__); + buf[pos] = 0; + mux->chip = simple_strtoul((char *)&buf[oldpos], NULL, 16); + buf[pos] = ':'; + /* search channel */ + pos ++; + oldpos = pos; + ret = i2c_mux_search_next(&pos, buf, len); + if (ret < 0) + printf ("%s no mux channel.\n", __FUNCTION__); + was = 0; + if (buf[pos] != 0) { + buf[pos] = 0; + was = 1; + } + mux->channel = simple_strtoul((char *)&buf[oldpos], NULL, 16); + if (was) + buf[pos] = ':'; + if (device->mux == NULL) + device->mux = mux; + else { + I2C_MUX *muxtmp = device->mux; + while (muxtmp->next != NULL) { + muxtmp = muxtmp->next; + } + muxtmp->next = mux; + } + pos ++; + oldpos = pos; + } + if (ret > 0) { + /* Add Device */ + i2c_mux_add_device (device); + return device; + } + + return NULL; +} + +int i2x_mux_select_mux(int bus) +{ + I2C_MUX_DEVICE *dev; + I2C_MUX *mux; + + if ((gd->flags & GD_FLG_RELOC) != GD_FLG_RELOC) { + /* select Default Mux Bus */ +#if defined(CFG_I2C_IVM_BUS) + i2c_mux_ident_muxstring_f ((uchar *)CFG_I2C_IVM_BUS); +#else + { + unsigned char *buf; + buf = (unsigned char *) getenv("EEprom_ivm"); + if (buf != NULL) + i2c_mux_ident_muxstring_f (buf); + } +#endif + return 0; + } + dev = i2c_mux_search_device(bus); + if (dev == NULL) + return -1; + + mux = dev->mux; + while (mux != NULL) { + if (i2c_write(mux->chip, 0, 0, &mux->channel, 1) != 0) { + printf ("Error setting Mux: chip:%x channel: \ + %x\n", mux->chip, mux->channel); + return -1; + } + mux = mux->next; + } + return 0; +} +#endif /* CONFIG_I2C_MUX */ + diff --git a/cpu/mpc8260/i2c.c b/cpu/mpc8260/i2c.c index 335177f..a96fbf8 100644 --- a/cpu/mpc8260/i2c.c +++ b/cpu/mpc8260/i2c.c @@ -780,10 +780,23 @@ unsigned int i2c_get_bus_num(void)
int i2c_set_bus_num(unsigned int bus) { +#if defined(CONFIG_I2C_MUX) + if (bus < CFG_MAX_I2C_BUS) { + i2c_bus_num = bus; + } else { + int ret; + + ret = i2x_mux_select_mux(bus); + if (ret == 0) + i2c_bus_num = bus; + else + return ret; + } +#else if (bus >= CFG_MAX_I2C_BUS) return -1; i2c_bus_num = bus; - +#endif return 0; } /* TODO: add 100/400k switching */ diff --git a/drivers/i2c/soft_i2c.c b/drivers/i2c/soft_i2c.c index 63e6a7b..0a9feb6 100644 --- a/drivers/i2c/soft_i2c.c +++ b/drivers/i2c/soft_i2c.c @@ -223,10 +223,23 @@ unsigned int i2c_get_bus_num(void)
int i2c_set_bus_num(unsigned int bus) { +#if defined(CONFIG_I2C_MUX) + if (bus < CFG_MAX_I2C_BUS) { + i2c_bus_num = bus; + } else { + int ret; + + ret = i2x_mux_select_mux(bus); + if (ret == 0) + i2c_bus_num = bus; + else + return ret; + } +#else if (bus >= CFG_MAX_I2C_BUS) return -1; i2c_bus_num = bus; - +#endif return 0; }
diff --git a/include/configs/mgcoge.h b/include/configs/mgcoge.h index 398e092..6564c15 100644 --- a/include/configs/mgcoge.h +++ b/include/configs/mgcoge.h @@ -203,6 +203,7 @@ #define CONFIG_I2C_CMD_TREE 1 #define CFG_MAX_I2C_BUS 2 #define CFG_I2C_INIT_BOARD 1 +#define CONFIG_I2C_MUX 1
/* EEprom support */ #define CFG_I2C_EEPROM_ADDR_LEN 1 diff --git a/include/configs/mgsuvd.h b/include/configs/mgsuvd.h index 2048575..e2a7c07 100644 --- a/include/configs/mgsuvd.h +++ b/include/configs/mgsuvd.h @@ -374,6 +374,7 @@ #define CONFIG_I2C_CMD_TREE 1 #define CFG_MAX_I2C_BUS 2 #define CFG_I2C_INIT_BOARD 1 +#define CONFIG_I2C_MUX 1
/* EEprom support */ #define CFG_I2C_EEPROM_ADDR_LEN 1 diff --git a/include/i2c.h b/include/i2c.h index a6e797a..9f771dd 100644 --- a/include/i2c.h +++ b/include/i2c.h @@ -85,6 +85,29 @@ void i2c_init(int speed, int slaveaddr); void i2c_init_board(void); #endif
+#if defined(CONFIG_I2C_MUX) + +typedef struct _mux { + uchar chip; + uchar channel; + char *name; + struct _mux *next; +} I2C_MUX; + +typedef struct _mux_device { + int busid; + I2C_MUX *mux; /* List of muxes, to reach the device */ + struct _mux_device *next; +} I2C_MUX_DEVICE; + +int i2c_mux_add_device(I2C_MUX_DEVICE *dev); + +I2C_MUX_DEVICE *i2c_mux_search_device(int id); +I2C_MUX_DEVICE *i2c_mux_ident_muxstring (uchar *buf); +int i2x_mux_select_mux(int bus); +int i2c_mux_ident_muxstring_f (uchar *buf); +#endif + /* * Probe the given I2C chip address. Returns 0 if a chip responded, * not 0 on failure.

Heiko Schocher wrote:
With this Command it is possible to add new I2C Busses, which are behind 1 .. n I2C Muxes. Details see README.
While this is pretty cool, I'm curious what circumstances lead to needing to add I2C buses at run-time via command line. Presumably you have a real business need for this in a bootloader?
regards, Ben

Hello Ben,
Ben Warren wrote:
Heiko Schocher wrote:
With this Command it is possible to add new I2C Busses, which are behind 1 .. n I2C Muxes. Details see README.
While this is pretty cool, I'm curious what circumstances lead to needing to add I2C buses at run-time via command line. Presumably you have a real business need for this in a bootloader?
Yes. This boardmanufacturer has a lot of hardwareversions with different I2C bus wirings, but has for example always one EEProm with configdata. Now, he can use one u-boot image for all this boards and has just to define in the environment, in which way he can reach this EEprom.
bye Heiko

Hi Heiko,
Heiko Schocher wrote:
Hello Ben,
Ben Warren wrote:
Heiko Schocher wrote:
With this Command it is possible to add new I2C Busses, which are behind 1 .. n I2C Muxes. Details see README.
While this is pretty cool, I'm curious what circumstances lead to needing to add I2C buses at run-time via command line. Presumably you have a real business need for this in a bootloader?
Yes. This boardmanufacturer has a lot of hardwareversions with different I2C bus wirings, but has for example always one EEProm with configdata. Now, he can use one u-boot image for all this boards and has just to define in the environment, in which way he can reach this EEprom.
bye Heiko
Interesting. Thank you.
regards, Ben
participants (2)
-
Ben Warren
-
Heiko Schocher