-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
i2c: Add generic I2C multiplexer using GPIO API
Add an i2c mux driver providing access to i2c bus segments using a hardware MUX sitting on a master bus and controlled through gpio pins. E.G. something like: ---------- ---------- Bus segment 1 - - - - - | | SCL/SDA | |-------------- | | | |------------| | | | | | Bus segment 2 | | | Linux | GPIO 1..N | MUX |--------------- Devices | |------------| | | | | | | | Bus segment M | | | |---------------| | ---------- ---------- - - - - - SCL/SDA of the master I2C bus is multiplexed to bus segment 1..M according to the settings of the GPIO pins 1..N. Signed-off-by: Peter Korsgaard <[email protected]> Signed-off-by: Jean Delvare <[email protected]>
- Loading branch information
Showing
6 changed files
with
308 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
Kernel driver gpio-i2cmux | ||
|
||
Author: Peter Korsgaard <[email protected]> | ||
|
||
Description | ||
----------- | ||
|
||
gpio-i2cmux is an i2c mux driver providing access to I2C bus segments | ||
from a master I2C bus and a hardware MUX controlled through GPIO pins. | ||
|
||
E.G.: | ||
|
||
---------- ---------- Bus segment 1 - - - - - | ||
| | SCL/SDA | |-------------- | | | ||
| |------------| | | ||
| | | | Bus segment 2 | | | ||
| Linux | GPIO 1..N | MUX |--------------- Devices | ||
| |------------| | | | | ||
| | | | Bus segment M | ||
| | | |---------------| | | ||
---------- ---------- - - - - - | ||
|
||
SCL/SDA of the master I2C bus is multiplexed to bus segment 1..M | ||
according to the settings of the GPIO pins 1..N. | ||
|
||
Usage | ||
----- | ||
|
||
gpio-i2cmux uses the platform bus, so you need to provide a struct | ||
platform_device with the platform_data pointing to a struct | ||
gpio_i2cmux_platform_data with the I2C adapter number of the master | ||
bus, the number of bus segments to create and the GPIO pins used | ||
to control it. See include/linux/gpio-i2cmux.h for details. | ||
|
||
E.G. something like this for a MUX providing 4 bus segments | ||
controlled through 3 GPIO pins: | ||
|
||
#include <linux/gpio-i2cmux.h> | ||
#include <linux/platform_device.h> | ||
|
||
static const unsigned myboard_gpiomux_gpios[] = { | ||
AT91_PIN_PC26, AT91_PIN_PC25, AT91_PIN_PC24 | ||
}; | ||
|
||
static const unsigned myboard_gpiomux_values[] = { | ||
0, 1, 2, 3 | ||
}; | ||
|
||
static struct gpio_i2cmux_platform_data myboard_i2cmux_data = { | ||
.parent = 1, | ||
.base_nr = 2, /* optional */ | ||
.values = myboard_gpiomux_values, | ||
.n_values = ARRAY_SIZE(myboard_gpiomux_values), | ||
.gpios = myboard_gpiomux_gpios, | ||
.n_gpios = ARRAY_SIZE(myboard_gpiomux_gpios), | ||
.idle = 4, /* optional */ | ||
}; | ||
|
||
static struct platform_device myboard_i2cmux = { | ||
.name = "gpio-i2cmux", | ||
.id = 0, | ||
.dev = { | ||
.platform_data = &myboard_i2cmux_data, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2608,6 +2608,14 @@ S: Supported | |
F: drivers/i2c/busses/i2c-gpio.c | ||
F: include/linux/i2c-gpio.h | ||
|
||
GENERIC GPIO I2C MULTIPLEXER DRIVER | ||
M: Peter Korsgaard <[email protected]> | ||
L: [email protected] | ||
S: Supported | ||
F: drivers/i2c/muxes/gpio-i2cmux.c | ||
F: include/linux/gpio-i2cmux.h | ||
F: Documentation/i2c/muxes/gpio-i2cmux | ||
|
||
GENERIC HDLC (WAN) DRIVERS | ||
M: Krzysztof Halasa <[email protected]> | ||
W: http://www.kernel.org/pub/linux/utils/net/hdlc/ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
/* | ||
* I2C multiplexer using GPIO API | ||
* | ||
* Peter Korsgaard <[email protected]> | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 2 as | ||
* published by the Free Software Foundation. | ||
*/ | ||
|
||
#include <linux/i2c.h> | ||
#include <linux/i2c-mux.h> | ||
#include <linux/gpio-i2cmux.h> | ||
#include <linux/platform_device.h> | ||
#include <linux/init.h> | ||
#include <linux/module.h> | ||
#include <linux/slab.h> | ||
#include <linux/gpio.h> | ||
|
||
struct gpiomux { | ||
struct i2c_adapter *parent; | ||
struct i2c_adapter **adap; /* child busses */ | ||
struct gpio_i2cmux_platform_data data; | ||
}; | ||
|
||
static void gpiomux_set(const struct gpiomux *mux, unsigned val) | ||
{ | ||
int i; | ||
|
||
for (i = 0; i < mux->data.n_gpios; i++) | ||
gpio_set_value(mux->data.gpios[i], val & (1 << i)); | ||
} | ||
|
||
static int gpiomux_select(struct i2c_adapter *adap, void *data, u32 chan) | ||
{ | ||
struct gpiomux *mux = data; | ||
|
||
gpiomux_set(mux, mux->data.values[chan]); | ||
|
||
return 0; | ||
} | ||
|
||
static int gpiomux_deselect(struct i2c_adapter *adap, void *data, u32 chan) | ||
{ | ||
struct gpiomux *mux = data; | ||
|
||
gpiomux_set(mux, mux->data.idle); | ||
|
||
return 0; | ||
} | ||
|
||
static int __devinit gpiomux_probe(struct platform_device *pdev) | ||
{ | ||
struct gpiomux *mux; | ||
struct gpio_i2cmux_platform_data *pdata; | ||
struct i2c_adapter *parent; | ||
int (*deselect) (struct i2c_adapter *, void *, u32); | ||
unsigned initial_state; | ||
int i, ret; | ||
|
||
pdata = pdev->dev.platform_data; | ||
if (!pdata) { | ||
dev_err(&pdev->dev, "Missing platform data\n"); | ||
return -ENODEV; | ||
} | ||
|
||
parent = i2c_get_adapter(pdata->parent); | ||
if (!parent) { | ||
dev_err(&pdev->dev, "Parent adapter (%d) not found\n", | ||
pdata->parent); | ||
return -ENODEV; | ||
} | ||
|
||
mux = kzalloc(sizeof(*mux), GFP_KERNEL); | ||
if (!mux) { | ||
ret = -ENOMEM; | ||
goto alloc_failed; | ||
} | ||
|
||
mux->parent = parent; | ||
mux->data = *pdata; | ||
mux->adap = kzalloc(sizeof(struct i2c_adapter *) * pdata->n_values, | ||
GFP_KERNEL); | ||
if (!mux->adap) { | ||
ret = -ENOMEM; | ||
goto alloc_failed2; | ||
} | ||
|
||
if (pdata->idle != GPIO_I2CMUX_NO_IDLE) { | ||
initial_state = pdata->idle; | ||
deselect = gpiomux_deselect; | ||
} else { | ||
initial_state = pdata->values[0]; | ||
deselect = NULL; | ||
} | ||
|
||
for (i = 0; i < pdata->n_gpios; i++) { | ||
ret = gpio_request(pdata->gpios[i], "gpio-i2cmux"); | ||
if (ret) | ||
goto err_request_gpio; | ||
gpio_direction_output(pdata->gpios[i], | ||
initial_state & (1 << i)); | ||
} | ||
|
||
for (i = 0; i < pdata->n_values; i++) { | ||
u32 nr = pdata->base_nr ? (pdata->base_nr + i) : 0; | ||
|
||
mux->adap[i] = i2c_add_mux_adapter(parent, mux, nr, i, | ||
gpiomux_select, deselect); | ||
if (!mux->adap[i]) { | ||
ret = -ENODEV; | ||
dev_err(&pdev->dev, "Failed to add adapter %d\n", i); | ||
goto add_adapter_failed; | ||
} | ||
} | ||
|
||
dev_info(&pdev->dev, "%d port mux on %s adapter\n", | ||
pdata->n_values, parent->name); | ||
|
||
platform_set_drvdata(pdev, mux); | ||
|
||
return 0; | ||
|
||
add_adapter_failed: | ||
for (; i > 0; i--) | ||
i2c_del_mux_adapter(mux->adap[i - 1]); | ||
i = pdata->n_gpios; | ||
err_request_gpio: | ||
for (; i > 0; i--) | ||
gpio_free(pdata->gpios[i - 1]); | ||
kfree(mux->adap); | ||
alloc_failed2: | ||
kfree(mux); | ||
alloc_failed: | ||
i2c_put_adapter(parent); | ||
|
||
return ret; | ||
} | ||
|
||
static int __devexit gpiomux_remove(struct platform_device *pdev) | ||
{ | ||
struct gpiomux *mux = platform_get_drvdata(pdev); | ||
int i; | ||
|
||
for (i = 0; i < mux->data.n_values; i++) | ||
i2c_del_mux_adapter(mux->adap[i]); | ||
|
||
for (i = 0; i < mux->data.n_gpios; i++) | ||
gpio_free(mux->data.gpios[i]); | ||
|
||
platform_set_drvdata(pdev, NULL); | ||
i2c_put_adapter(mux->parent); | ||
kfree(mux->adap); | ||
kfree(mux); | ||
|
||
return 0; | ||
} | ||
|
||
static struct platform_driver gpiomux_driver = { | ||
.probe = gpiomux_probe, | ||
.remove = __devexit_p(gpiomux_remove), | ||
.driver = { | ||
.owner = THIS_MODULE, | ||
.name = "gpio-i2cmux", | ||
}, | ||
}; | ||
|
||
static int __init gpiomux_init(void) | ||
{ | ||
return platform_driver_register(&gpiomux_driver); | ||
} | ||
|
||
static void __exit gpiomux_exit(void) | ||
{ | ||
platform_driver_unregister(&gpiomux_driver); | ||
} | ||
|
||
module_init(gpiomux_init); | ||
module_exit(gpiomux_exit); | ||
|
||
MODULE_DESCRIPTION("GPIO-based I2C multiplexer driver"); | ||
MODULE_AUTHOR("Peter Korsgaard <[email protected]>"); | ||
MODULE_LICENSE("GPL"); | ||
MODULE_ALIAS("platform:gpio-i2cmux"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
/* | ||
* gpio-i2cmux interface to platform code | ||
* | ||
* Peter Korsgaard <[email protected]> | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License version 2 as | ||
* published by the Free Software Foundation. | ||
*/ | ||
|
||
#ifndef _LINUX_GPIO_I2CMUX_H | ||
#define _LINUX_GPIO_I2CMUX_H | ||
|
||
/* MUX has no specific idle mode */ | ||
#define GPIO_I2CMUX_NO_IDLE ((unsigned)-1) | ||
|
||
/** | ||
* struct gpio_i2cmux_platform_data - Platform-dependent data for gpio-i2cmux | ||
* @parent: Parent I2C bus adapter number | ||
* @base_nr: Base I2C bus number to number adapters from or zero for dynamic | ||
* @values: Array of bitmasks of GPIO settings (low/high) for each | ||
* position | ||
* @n_values: Number of multiplexer positions (busses to instantiate) | ||
* @gpios: Array of GPIO numbers used to control MUX | ||
* @n_gpios: Number of GPIOs used to control MUX | ||
* @idle: Bitmask to write to MUX when idle or GPIO_I2CMUX_NO_IDLE if not used | ||
*/ | ||
struct gpio_i2cmux_platform_data { | ||
int parent; | ||
int base_nr; | ||
const unsigned *values; | ||
int n_values; | ||
const unsigned *gpios; | ||
int n_gpios; | ||
unsigned idle; | ||
}; | ||
|
||
#endif /* _LINUX_GPIO_I2CMUX_H */ |