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