Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.6.
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * General Purpose I2C multiplexer
  4 *
  5 * Copyright (C) 2017 Axentia Technologies AB
  6 *
  7 * Author: Peter Rosin <peda@axentia.se>
  8 */
  9
 10#include <linux/i2c.h>
 11#include <linux/i2c-mux.h>
 12#include <linux/module.h>
 13#include <linux/mux/consumer.h>
 14#include <linux/of.h>
 15#include <linux/platform_device.h>
 16
 17struct mux {
 18	struct mux_control *control;
 19
 20	bool do_not_deselect;
 21};
 22
 23static int i2c_mux_select(struct i2c_mux_core *muxc, u32 chan)
 24{
 25	struct mux *mux = i2c_mux_priv(muxc);
 26	int ret;
 27
 28	ret = mux_control_select(mux->control, chan);
 29	mux->do_not_deselect = ret < 0;
 30
 31	return ret;
 32}
 33
 34static int i2c_mux_deselect(struct i2c_mux_core *muxc, u32 chan)
 35{
 36	struct mux *mux = i2c_mux_priv(muxc);
 37
 38	if (mux->do_not_deselect)
 39		return 0;
 40
 41	return mux_control_deselect(mux->control);
 42}
 43
 44static struct i2c_adapter *mux_parent_adapter(struct device *dev)
 45{
 46	struct device_node *np = dev->of_node;
 47	struct device_node *parent_np;
 48	struct i2c_adapter *parent;
 49
 50	parent_np = of_parse_phandle(np, "i2c-parent", 0);
 51	if (!parent_np) {
 52		dev_err(dev, "Cannot parse i2c-parent\n");
 53		return ERR_PTR(-ENODEV);
 54	}
 55	parent = of_get_i2c_adapter_by_node(parent_np);
 56	of_node_put(parent_np);
 57	if (!parent)
 58		return ERR_PTR(-EPROBE_DEFER);
 59
 60	return parent;
 61}
 62
 63static const struct of_device_id i2c_mux_of_match[] = {
 64	{ .compatible = "i2c-mux", },
 65	{},
 66};
 67MODULE_DEVICE_TABLE(of, i2c_mux_of_match);
 68
 69static int i2c_mux_probe(struct platform_device *pdev)
 70{
 71	struct device *dev = &pdev->dev;
 72	struct device_node *np = dev->of_node;
 73	struct device_node *child;
 74	struct i2c_mux_core *muxc;
 75	struct mux *mux;
 76	struct i2c_adapter *parent;
 77	int children;
 78	int ret;
 79
 80	if (!np)
 81		return -ENODEV;
 82
 83	mux = devm_kzalloc(dev, sizeof(*mux), GFP_KERNEL);
 84	if (!mux)
 85		return -ENOMEM;
 86
 87	mux->control = devm_mux_control_get(dev, NULL);
 88	if (IS_ERR(mux->control))
 89		return dev_err_probe(dev, PTR_ERR(mux->control),
 90				     "failed to get control-mux\n");
 91
 92	parent = mux_parent_adapter(dev);
 93	if (IS_ERR(parent))
 94		return dev_err_probe(dev, PTR_ERR(parent),
 95				     "failed to get i2c-parent adapter\n");
 96
 97	children = of_get_child_count(np);
 98
 99	muxc = i2c_mux_alloc(parent, dev, children, 0, 0,
100			     i2c_mux_select, i2c_mux_deselect);
101	if (!muxc) {
102		ret = -ENOMEM;
103		goto err_parent;
104	}
105	muxc->priv = mux;
106
107	platform_set_drvdata(pdev, muxc);
108
109	muxc->mux_locked = of_property_read_bool(np, "mux-locked");
110
111	for_each_child_of_node(np, child) {
112		u32 chan;
113
114		ret = of_property_read_u32(child, "reg", &chan);
115		if (ret < 0) {
116			dev_err(dev, "no reg property for node '%pOFn'\n",
117				child);
118			goto err_children;
119		}
120
121		if (chan >= mux_control_states(mux->control)) {
122			dev_err(dev, "invalid reg %u\n", chan);
123			ret = -EINVAL;
124			goto err_children;
125		}
126
127		ret = i2c_mux_add_adapter(muxc, 0, chan);
128		if (ret)
129			goto err_children;
130	}
131
132	dev_info(dev, "%d-port mux on %s adapter\n", children, parent->name);
133
134	return 0;
135
136err_children:
137	of_node_put(child);
138	i2c_mux_del_adapters(muxc);
139err_parent:
140	i2c_put_adapter(parent);
141
142	return ret;
143}
144
145static void i2c_mux_remove(struct platform_device *pdev)
146{
147	struct i2c_mux_core *muxc = platform_get_drvdata(pdev);
148
149	i2c_mux_del_adapters(muxc);
150	i2c_put_adapter(muxc->parent);
151}
152
153static struct platform_driver i2c_mux_driver = {
154	.probe	= i2c_mux_probe,
155	.remove = i2c_mux_remove,
156	.driver	= {
157		.name	= "i2c-mux-gpmux",
158		.of_match_table = i2c_mux_of_match,
159	},
160};
161module_platform_driver(i2c_mux_driver);
162
163MODULE_DESCRIPTION("General Purpose I2C multiplexer driver");
164MODULE_AUTHOR("Peter Rosin <peda@axentia.se>");
165MODULE_LICENSE("GPL v2");