|
| 1 | +From f1fb64b04bf414ab04e31ac107bb28137105c5fd Mon Sep 17 00:00:00 2001 |
| 2 | +From: Robert Shearman < [email protected]> |
| 3 | +Date: Thu, 28 Feb 2019 11:43:43 +0000 |
| 4 | +Subject: [PATCH] i2c: mux: pca954x: allow management of device idle state via |
| 5 | + sysfs |
| 6 | + |
| 7 | +The behaviour, by default, to not deselect after each transfer is |
| 8 | +unsafe when there is a device with an address that conflicts with |
| 9 | +another device on another mux on the same parent bus, and it |
| 10 | +may not be convenient to use devicetree to set the deselect mux, |
| 11 | +e.g. when running on x86_64 when ACPI is used to discover most of the |
| 12 | +device hierarchy. |
| 13 | + |
| 14 | +Therefore, provide the ability to set the idle state behaviour using a |
| 15 | +new sysfs file, idle_state as a complement to the method of |
| 16 | +instantiating the device via sysfs. The possible behaviours are |
| 17 | +disconnect, i.e. to deselect all channels from the mux, as-is (the |
| 18 | +default), i.e. leave the last channel selected, and set a |
| 19 | +predetermined channel. |
| 20 | + |
| 21 | +The current behaviour of leaving the channel as-is after each |
| 22 | +transaction is preserved. |
| 23 | + |
| 24 | +Signed-off-by: Robert Shearman < [email protected]> |
| 25 | +Signed-off-by: Peter Rosin < [email protected]> |
| 26 | +--- |
| 27 | + .../ABI/testing/sysfs-bus-i2c-devices-pca954x | 20 +++++ |
| 28 | + drivers/i2c/muxes/i2c-mux-pca954x.c | 85 +++++++++++++++++-- |
| 29 | + 2 files changed, 97 insertions(+), 8 deletions(-) |
| 30 | + create mode 100644 Documentation/ABI/testing/sysfs-bus-i2c-devices-pca954x |
| 31 | + |
| 32 | +diff --git a/Documentation/ABI/testing/sysfs-bus-i2c-devices-pca954x b/Documentation/ABI/testing/sysfs-bus-i2c-devices-pca954x |
| 33 | +new file mode 100644 |
| 34 | +index 000000000000..0b0de8cd0d13 |
| 35 | +--- /dev/null |
| 36 | ++++ b/Documentation/ABI/testing/sysfs-bus-i2c-devices-pca954x |
| 37 | +@@ -0,0 +1,20 @@ |
| 38 | ++What: /sys/bus/i2c/.../idle_state |
| 39 | ++Date: January 2019 |
| 40 | ++KernelVersion: 5.2 |
| 41 | ++Contact: Robert Shearman <[email protected]> |
| 42 | ++Description: |
| 43 | ++ Value that exists only for mux devices that can be |
| 44 | ++ written to control the behaviour of the multiplexer on |
| 45 | ++ idle. Possible values: |
| 46 | ++ -2 - disconnect on idle, i.e. deselect the last used |
| 47 | ++ channel, which is useful when there is a device |
| 48 | ++ with an address that conflicts with another |
| 49 | ++ device on another mux on the same parent bus. |
| 50 | ++ -1 - leave the mux as-is, which is the most optimal |
| 51 | ++ setting in terms of I2C operations and is the |
| 52 | ++ default mode. |
| 53 | ++ 0..<nchans> - set the mux to a predetermined channel, |
| 54 | ++ which is useful if there is one channel that is |
| 55 | ++ used almost always, and you want to reduce the |
| 56 | ++ latency for normal operations after rare |
| 57 | ++ transactions on other channels |
| 58 | +diff --git a/drivers/i2c/muxes/i2c-mux-pca954x.c b/drivers/i2c/muxes/i2c-mux-pca954x.c |
| 59 | +index e32fef560684..923aa3a5a3dc 100644 |
| 60 | +--- a/drivers/i2c/muxes/i2c-mux-pca954x.c |
| 61 | ++++ b/drivers/i2c/muxes/i2c-mux-pca954x.c |
| 62 | +@@ -49,6 +49,7 @@ |
| 63 | + #include <linux/pm.h> |
| 64 | + #include <linux/slab.h> |
| 65 | + #include <linux/spinlock.h> |
| 66 | ++#include <dt-bindings/mux/mux.h> |
| 67 | + |
| 68 | + #define PCA954X_MAX_NCHANS 8 |
| 69 | + |
| 70 | +@@ -84,7 +85,9 @@ struct pca954x { |
| 71 | + const struct chip_desc *chip; |
| 72 | + |
| 73 | + u8 last_chan; /* last register value */ |
| 74 | +- u8 deselect; |
| 75 | ++ /* MUX_IDLE_AS_IS, MUX_IDLE_DISCONNECT or >= 0 for channel */ |
| 76 | ++ s8 idle_state; |
| 77 | ++ |
| 78 | + struct i2c_client *client; |
| 79 | + |
| 80 | + struct irq_domain *irq; |
| 81 | +@@ -253,15 +256,71 @@ static int pca954x_deselect_mux(struct i2c_mux_core *muxc, u32 chan) |
| 82 | + { |
| 83 | + struct pca954x *data = i2c_mux_priv(muxc); |
| 84 | + struct i2c_client *client = data->client; |
| 85 | ++ s8 idle_state; |
| 86 | ++ |
| 87 | ++ idle_state = READ_ONCE(data->idle_state); |
| 88 | ++ if (idle_state >= 0) |
| 89 | ++ /* Set the mux back to a predetermined channel */ |
| 90 | ++ return pca954x_select_chan(muxc, idle_state); |
| 91 | ++ |
| 92 | ++ if (idle_state == MUX_IDLE_DISCONNECT) { |
| 93 | ++ /* Deselect active channel */ |
| 94 | ++ data->last_chan = 0; |
| 95 | ++ return pca954x_reg_write(muxc->parent, client, |
| 96 | ++ data->last_chan); |
| 97 | ++ } |
| 98 | + |
| 99 | +- if (!(data->deselect & (1 << chan))) |
| 100 | +- return 0; |
| 101 | ++ /* otherwise leave as-is */ |
| 102 | + |
| 103 | +- /* Deselect active channel */ |
| 104 | +- data->last_chan = 0; |
| 105 | +- return pca954x_reg_write(muxc->parent, client, data->last_chan); |
| 106 | ++ return 0; |
| 107 | ++} |
| 108 | ++ |
| 109 | ++static ssize_t idle_state_show(struct device *dev, |
| 110 | ++ struct device_attribute *attr, |
| 111 | ++ char *buf) |
| 112 | ++{ |
| 113 | ++ struct i2c_client *client = to_i2c_client(dev); |
| 114 | ++ struct i2c_mux_core *muxc = i2c_get_clientdata(client); |
| 115 | ++ struct pca954x *data = i2c_mux_priv(muxc); |
| 116 | ++ |
| 117 | ++ return sprintf(buf, "%d\n", READ_ONCE(data->idle_state)); |
| 118 | ++} |
| 119 | ++ |
| 120 | ++static ssize_t idle_state_store(struct device *dev, |
| 121 | ++ struct device_attribute *attr, |
| 122 | ++ const char *buf, size_t count) |
| 123 | ++{ |
| 124 | ++ struct i2c_client *client = to_i2c_client(dev); |
| 125 | ++ struct i2c_mux_core *muxc = i2c_get_clientdata(client); |
| 126 | ++ struct pca954x *data = i2c_mux_priv(muxc); |
| 127 | ++ int val; |
| 128 | ++ int ret; |
| 129 | ++ |
| 130 | ++ ret = kstrtoint(buf, 0, &val); |
| 131 | ++ if (ret < 0) |
| 132 | ++ return ret; |
| 133 | ++ |
| 134 | ++ if (val != MUX_IDLE_AS_IS && val != MUX_IDLE_DISCONNECT && |
| 135 | ++ (val < 0 || val >= data->chip->nchans)) |
| 136 | ++ return -EINVAL; |
| 137 | ++ |
| 138 | ++ i2c_lock_bus(muxc->parent, I2C_LOCK_SEGMENT); |
| 139 | ++ |
| 140 | ++ WRITE_ONCE(data->idle_state, val); |
| 141 | ++ /* |
| 142 | ++ * Set the mux into a state consistent with the new |
| 143 | ++ * idle_state. |
| 144 | ++ */ |
| 145 | ++ if (data->last_chan || val != MUX_IDLE_DISCONNECT) |
| 146 | ++ ret = pca954x_deselect_mux(muxc, 0); |
| 147 | ++ |
| 148 | ++ i2c_unlock_bus(muxc->parent, I2C_LOCK_SEGMENT); |
| 149 | ++ |
| 150 | ++ return ret < 0 ? ret : count; |
| 151 | + } |
| 152 | + |
| 153 | ++static DEVICE_ATTR_RW(idle_state); |
| 154 | ++ |
| 155 | + static irqreturn_t pca954x_irq_handler(int irq, void *dev_id) |
| 156 | + { |
| 157 | + struct pca954x *data = dev_id; |
| 158 | +@@ -328,8 +387,11 @@ static int pca954x_irq_setup(struct i2c_mux_core *muxc) |
| 159 | + static void pca954x_cleanup(struct i2c_mux_core *muxc) |
| 160 | + { |
| 161 | + struct pca954x *data = i2c_mux_priv(muxc); |
| 162 | ++ struct i2c_client *client = data->client; |
| 163 | + int c, irq; |
| 164 | + |
| 165 | ++ device_remove_file(&client->dev, &dev_attr_idle_state); |
| 166 | ++ |
| 167 | + if (data->irq) { |
| 168 | + for (c = 0; c < data->chip->nchans; c++) { |
| 169 | + irq = irq_find_mapping(data->irq, c); |
| 170 | +@@ -410,9 +472,12 @@ static int pca954x_probe(struct i2c_client *client, |
| 171 | + } |
| 172 | + |
| 173 | + data->last_chan = 0; /* force the first selection */ |
| 174 | ++ data->idle_state = MUX_IDLE_AS_IS; |
| 175 | + |
| 176 | + idle_disconnect_dt = np && |
| 177 | + of_property_read_bool(np, "i2c-mux-idle-disconnect"); |
| 178 | ++ if (idle_disconnect_dt) |
| 179 | ++ data->idle_state = MUX_IDLE_DISCONNECT; |
| 180 | + |
| 181 | + ret = pca954x_irq_setup(muxc); |
| 182 | + if (ret) |
| 183 | +@@ -420,8 +485,6 @@ static int pca954x_probe(struct i2c_client *client, |
| 184 | + |
| 185 | + /* Now create an adapter for each channel */ |
| 186 | + for (num = 0; num < data->chip->nchans; num++) { |
| 187 | +- data->deselect |= idle_disconnect_dt << num; |
| 188 | +- |
| 189 | + ret = i2c_mux_add_adapter(muxc, 0, num, 0); |
| 190 | + if (ret) |
| 191 | + goto fail_cleanup; |
| 192 | +@@ -436,6 +499,12 @@ static int pca954x_probe(struct i2c_client *client, |
| 193 | + goto fail_cleanup; |
| 194 | + } |
| 195 | + |
| 196 | ++ /* |
| 197 | ++ * The attr probably isn't going to be needed in most cases, |
| 198 | ++ * so don't fail completely on error. |
| 199 | ++ */ |
| 200 | ++ device_create_file(dev, &dev_attr_idle_state); |
| 201 | ++ |
| 202 | + dev_info(dev, "registered %d multiplexed busses for I2C %s %s\n", |
| 203 | + num, data->chip->muxtype == pca954x_ismux |
| 204 | + ? "mux" : "switch", client->name); |
| 205 | +-- |
| 206 | +2.25.1 |
| 207 | + |
0 commit comments