blob: 46775c11be0d6a9136838cd5fcdb711f47bd0df5 [file] [log] [blame]
/**
* linux/drivers/parrot/gpio/mux/p7-i2cmux.c - Parrot7 I2CM bus muxing
* implementation
*
* Copyright (C) 2012 Parrot S.A.
*
* author: Lionel Flandrin <lionel.flandrin@parrot.com>
* date: 19-Sep-2012
*
* This file is released under the GPL
*/
#include <linux/i2c.h>
#include <linux/i2c-mux.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/pinctrl/consumer.h>
#include <linux/module.h>
#include "p7-i2cmux.h"
struct p7i2cmux_channel {
/* I2C adapter for this channel */
struct i2c_adapter *adap;
/* PIN state for this adapter */
struct pinctrl_state *pstate;
};
struct p7i2cmux {
struct i2c_adapter *parent;
struct pinctrl *pctl;
struct pinctrl_state *default_pstate;
struct p7i2cmux_channel *channels;
unsigned nchannels;
};
static int p7i2cmux_select(struct i2c_adapter *adap, void *data, u32 chan)
{
struct p7i2cmux *mux = data;
BUG_ON(chan >= mux->nchannels);
/* Select the channel mux configuration */
return pinctrl_select_state(mux->pctl, mux->channels[chan].pstate);
}
static int p7i2cmux_deselect(struct i2c_adapter *adap, void *data, u32 chan)
{
struct p7i2cmux *mux = data;
BUG_ON(chan >= mux->nchannels);
/* Switch back to the default channel (i.e. the parent's channel) */
return pinctrl_select_state(mux->pctl, mux->default_pstate);
}
static int __devinit p7i2cmux_probe(struct platform_device *pdev)
{
struct p7i2cmux *mux;
const struct p7i2cmux_plat_data *pdata = pdev->dev.platform_data;
int i;
int ret = 0;
if (!pdata)
return -ENODEV;
mux = kzalloc(sizeof(*mux), GFP_KERNEL);
if (!mux)
return -ENOMEM;
mux->nchannels = pdata->nr_channels;
if (mux->nchannels == 0) {
ret = -ENODEV;
goto free_mux;
}
mux->channels = kzalloc(mux->nchannels * sizeof(*(mux->channels)),
GFP_KERNEL);
if (!mux->channels) {
ret = -ENOMEM;
goto free_mux;
}
mux->parent = i2c_get_adapter(pdata->parent);
if (!mux->parent) {
dev_err(&pdev->dev, "Couldn't find parent I2C adapter %d\n",
pdata->parent);
ret = -ENODEV;
goto free;
}
mux->pctl = pinctrl_get(&pdev->dev);
if (IS_ERR(mux->pctl)) {
dev_err(&pdev->dev, "Couldn't get pin controller\n");
ret = PTR_ERR(mux->pctl);
goto put_parent;
}
mux->default_pstate =
pinctrl_lookup_state(mux->pctl, PINCTRL_STATE_DEFAULT);
if (IS_ERR(mux->default_pstate)) {
dev_err(&pdev->dev, "Failed to find default pin state\n");
ret = PTR_ERR(mux->default_pstate);
/* Force the removal of this adapter */
goto put_pctl;
}
/* Add muxed adapters */
for (i = 0; i < mux->nchannels; i++) {
mux->channels[i].adap =
i2c_add_mux_adapter(mux->parent, mux,
pdata->parent * 10 + i,
i,
p7i2cmux_select, p7i2cmux_deselect);
if (mux->channels[i].adap == NULL) {
dev_err(&pdev->dev,
"Failed to add mux %s for I2C adapter %s\n",
pdata->channel_names[i], mux->parent->name);
ret = -EINVAL;
goto remove_mux;
}
mux->channels[i].pstate =
pinctrl_lookup_state(mux->pctl,
pdata->channel_names[i]);
if (IS_ERR(mux->channels[i].pstate)) {
dev_err(&pdev->dev, "Failed to find pin state for %s\n",
pdata->channel_names[i]);
ret = PTR_ERR(mux->channels[i].pstate);
/* Force the removal of this adapter */
i++;
goto remove_mux;
}
}
/* switch to default bus */
ret = pinctrl_select_state(mux->pctl, mux->default_pstate);
if (ret) {
dev_err(&pdev->dev, "Failed to switch to default mux config\n");
goto remove_mux;
}
dev_info(&pdev->dev, "Initialized %d mux channels for adapter %s\n",
mux->nchannels, mux->parent->name);
platform_set_drvdata(pdev, mux);
return 0;
remove_mux:
while (--i)
i2c_del_mux_adapter(mux->channels[i].adap);
put_pctl:
pinctrl_put(mux->pctl);
put_parent:
i2c_put_adapter(mux->parent);
free:
kfree(mux->channels);
free_mux:
kfree(mux);
return ret;
}
static int __devexit p7i2cmux_remove(struct platform_device *pdev)
{
struct p7i2cmux *mux = platform_get_drvdata(pdev);
int i;
if (!mux)
return -ENODEV;
for (i = 0; i < mux->nchannels; i++)
i2c_del_mux_adapter(mux->channels[i].adap);
pinctrl_put(mux->pctl);
i2c_put_adapter(mux->parent);
kfree(mux->channels);
kfree(mux);
return 0;
}
static struct platform_driver p7i2cmux_driver = {
.probe = p7i2cmux_probe,
.remove = __devexit_p(p7i2cmux_remove),
.driver = {
.owner = THIS_MODULE,
.name = P7I2CMUX_DRV_NAME,
},
};
#ifdef MODULE
module_platform_driver(p7i2cmux_driver);
#else
static int __init p7_i2cmux_init(void)
{
return platform_driver_register(&p7i2cmux_driver);
}
postcore_initcall(p7_i2cmux_init);
#endif /* MODULE */
MODULE_DESCRIPTION("Pin muxing support for multiple I2C buses "
"on the same master");
MODULE_AUTHOR("Lionel Flandrin <lionel.flandrin@parrot.com>");
MODULE_LICENSE("GPL");