From 298d4de1ed003ad91cb49e068d744db0343cacb6 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Mon, 7 Nov 2016 12:47:38 +0000 Subject: i2c: Match using traditional OF methods, then by vendor-less compatible strings This function provides a single call for all I2C devices which need to match firstly using traditional OF means i.e by of_node, then if that fails we attempt to match using the supplied I2C client name with a list of supplied compatible strings with the ',' string removed. The latter is required due to the unruly naming conventions used currently by I2C devices. Acked-by: Grant Likely Signed-off-by: Lee Jones [Kieran: Fix static inline usage on !CONFIG_OF] Tested-by: Kieran Bingham Reviewed-by: Javier Martinez Canillas Tested-by: Javier Martinez Canillas Signed-off-by: Kieran Bingham Signed-off-by: Wolfram Sang --- include/linux/i2c.h | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'include') diff --git a/include/linux/i2c.h b/include/linux/i2c.h index 6422eef428c4..c0a4a12815aa 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -774,6 +774,10 @@ extern struct i2c_adapter *of_find_i2c_adapter_by_node(struct device_node *node) /* must call i2c_put_adapter() when done with returned i2c_adapter device */ struct i2c_adapter *of_get_i2c_adapter_by_node(struct device_node *node); +extern const struct of_device_id +*i2c_of_match_device(const struct of_device_id *matches, + struct i2c_client *client); + #else static inline struct i2c_client *of_find_i2c_device_by_node(struct device_node *node) @@ -790,6 +794,14 @@ static inline struct i2c_adapter *of_get_i2c_adapter_by_node(struct device_node { return NULL; } + +static inline const struct of_device_id +*i2c_of_match_device(const struct of_device_id *matches, + struct i2c_client *client) +{ + return NULL; +} + #endif /* CONFIG_OF */ #if IS_ENABLED(CONFIG_ACPI) -- cgit v1.2.3 From 5f441fcaa3ce54681923475cf0040216d190e646 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Mon, 7 Nov 2016 12:47:40 +0000 Subject: i2c: Export i2c_match_id() for direct use by device drivers When there was no other way to match a I2C device to driver i2c_match_id() was exclusively used. However, now there are other types of tables which are commonly supplied, matching on an i2c_device_id table is used less frequently. Instead of _always_ calling i2c_match_id() from within the framework, we only need to do so from drivers which have no other way of matching. This patch makes i2c_match_id() available to the aforementioned device drivers. Acked-by: Grant Likely Signed-off-by: Lee Jones Tested-by: Kieran Bingham Reviewed-by: Javier Martinez Canillas Tested-by: Javier Martinez Canillas Signed-off-by: Kieran Bingham Signed-off-by: Wolfram Sang --- drivers/i2c/i2c-core.c | 3 ++- include/linux/i2c.h | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c index a97f7a0b0d6e..52b2cd4ac8b2 100644 --- a/drivers/i2c/i2c-core.c +++ b/drivers/i2c/i2c-core.c @@ -676,7 +676,7 @@ static inline int i2c_acpi_install_space_handler(struct i2c_adapter *adapter) /* ------------------------------------------------------------------------- */ -static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id, +const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id, const struct i2c_client *client) { if (!(id && client)) @@ -689,6 +689,7 @@ static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id, } return NULL; } +EXPORT_SYMBOL_GPL(i2c_match_id); static int i2c_device_match(struct device *dev, struct device_driver *drv) { diff --git a/include/linux/i2c.h b/include/linux/i2c.h index c0a4a12815aa..7e00efd6a62f 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -243,6 +243,8 @@ struct i2c_client { extern struct i2c_client *i2c_verify_client(struct device *dev); extern struct i2c_adapter *i2c_verify_adapter(struct device *dev); +extern const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id, + const struct i2c_client *client); static inline struct i2c_client *kobj_to_i2c_client(struct kobject *kobj) { -- cgit v1.2.3 From b8a1a4cd5a98a2adf8dfd6902cd98e57d910ee12 Mon Sep 17 00:00:00 2001 From: Lee Jones Date: Mon, 7 Nov 2016 12:47:41 +0000 Subject: i2c: Provide a temporary .probe_new() call-back type This will aid the seamless removal of the current probe()'s, more commonly unused than used second parameter. Most I2C drivers can simply switch over to the new interface, others which have DT support can use its own matching instead and others can call i2c_match_id() themselves. This brings I2C's device probe method into line with other similar interfaces in the kernel and prevents the requirement to pass an i2c_device_id table. Suggested-by: Grant Likely Signed-off-by: Lee Jones [Kieran: fix rebase conflicts and adapt for dev_pm_domain_{attach,detach}] Tested-by: Kieran Bingham Reviewed-by: Javier Martinez Canillas Tested-by: Javier Martinez Canillas Signed-off-by: Kieran Bingham Signed-off-by: Wolfram Sang --- drivers/i2c/i2c-core.c | 15 ++++++++++++--- include/linux/i2c.h | 8 +++++++- 2 files changed, 19 insertions(+), 4 deletions(-) (limited to 'include') diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c index 52b2cd4ac8b2..8b93a262e237 100644 --- a/drivers/i2c/i2c-core.c +++ b/drivers/i2c/i2c-core.c @@ -924,8 +924,6 @@ static int i2c_device_probe(struct device *dev) } driver = to_i2c_driver(dev->driver); - if (!driver->probe) - return -EINVAL; /* * An I2C ID table is not mandatory, if and only if, a suitable Device @@ -967,7 +965,18 @@ static int i2c_device_probe(struct device *dev) if (status == -EPROBE_DEFER) goto err_clear_wakeup_irq; - status = driver->probe(client, i2c_match_id(driver->id_table, client)); + /* + * When there are no more users of probe(), + * rename probe_new to probe. + */ + if (driver->probe_new) + status = driver->probe_new(client); + else if (driver->probe) + status = driver->probe(client, + i2c_match_id(driver->id_table, client)); + else + status = -EINVAL; + if (status) goto err_detach_pm_domain; diff --git a/include/linux/i2c.h b/include/linux/i2c.h index 7e00efd6a62f..82cf90945bb8 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -135,7 +135,8 @@ enum i2c_alert_protocol { * struct i2c_driver - represent an I2C device driver * @class: What kind of i2c device we instantiate (for detect) * @attach_adapter: Callback for bus addition (deprecated) - * @probe: Callback for device binding + * @probe: Callback for device binding - soon to be deprecated + * @probe_new: New callback for device binding * @remove: Callback for device unbinding * @shutdown: Callback for device shutdown * @alert: Alert callback, for example for the SMBus alert protocol @@ -178,6 +179,11 @@ struct i2c_driver { int (*probe)(struct i2c_client *, const struct i2c_device_id *); int (*remove)(struct i2c_client *); + /* New driver model interface to aid the seamless removal of the + * current probe()'s, more commonly unused than used second parameter. + */ + int (*probe_new)(struct i2c_client *); + /* driver model interfaces that don't relate to enumeration */ void (*shutdown)(struct i2c_client *); -- cgit v1.2.3 From c02b7bf532f7e46f1f9a0e9c3c27ca3f6f134e8d Mon Sep 17 00:00:00 2001 From: Vadim Pasternak Date: Thu, 10 Nov 2016 21:26:23 +0000 Subject: i2c: mux: mellanox: add driver This driver allows I2C routing controlled through CPLD select registers on a wide range of Mellanox systems (CPLD Lattice device). MUX selection is provided by digital and analog HW. Analog part is not under SW control. Digital part is under CPLD control (channel selection/de-selection). Connectivity schema. .---. .-------------. | l | | |-- i2cx1 -- i2cx8 | i |-- i2cn --+--| mlxcpld mux | | n | | | |-- i2cy1 -- i2cy8 | u | | '-------------' | x | | | '---' '---------' i2c-mux-mlxpcld does not necessarily require i2c-mlxcpld. It can be used along with another bus driver, and still control i2c routing through CPLD mux selection, in case the system is equipped with CPLD capable of mux selection control. The Kconfig currently controlling compilation of this code is: drivers/i2c/muxes/Kconfig:config I2C_MUX_MLXCPLD Signed-off-by: Michael Shych Signed-off-by: Vadim Pasternak Reviewed-by: Jiri Pirko Acked-by: Peter Rosin Signed-off-by: Wolfram Sang --- MAINTAINERS | 7 ++ drivers/i2c/muxes/Kconfig | 11 ++ drivers/i2c/muxes/Makefile | 1 + drivers/i2c/muxes/i2c-mux-mlxcpld.c | 220 ++++++++++++++++++++++++++++++++++++ include/linux/i2c/mlxcpld.h | 52 +++++++++ 5 files changed, 291 insertions(+) create mode 100644 drivers/i2c/muxes/i2c-mux-mlxcpld.c create mode 100644 include/linux/i2c/mlxcpld.h (limited to 'include') diff --git a/MAINTAINERS b/MAINTAINERS index 411e3b87b8c2..22ee29a8f403 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7881,6 +7881,13 @@ W: http://www.mellanox.com Q: http://patchwork.ozlabs.org/project/netdev/list/ F: drivers/net/ethernet/mellanox/mlxsw/ +MELLANOX MLXCPLD I2C MUX DRIVER +M: Vadim Pasternak +M: Michael Shych +L: linux-i2c@vger.kernel.org +S: Supported +F: drivers/i2c/muxes/i2c-mux-mlxcpld.c + MELLANOX MLXCPLD LED DRIVER M: Vadim Pasternak L: linux-leds@vger.kernel.org diff --git a/drivers/i2c/muxes/Kconfig b/drivers/i2c/muxes/Kconfig index e280c8ecc0b5..d03340203615 100644 --- a/drivers/i2c/muxes/Kconfig +++ b/drivers/i2c/muxes/Kconfig @@ -81,4 +81,15 @@ config I2C_DEMUX_PINCTRL demultiplexer that uses the pinctrl subsystem. This is useful if you want to change the I2C master at run-time depending on features. +config I2C_MUX_MLXCPLD + tristate "Mellanox CPLD based I2C multiplexer" + help + If you say yes to this option, support will be included for a + CPLD based I2C multiplexer. This driver provides access to + I2C busses connected through a MUX, which is controlled + by a CPLD register. + + This driver can also be built as a module. If so, the module + will be called i2c-mux-mlxcpld. + endmenu diff --git a/drivers/i2c/muxes/Makefile b/drivers/i2c/muxes/Makefile index 7c267c29b191..9948fa45037f 100644 --- a/drivers/i2c/muxes/Makefile +++ b/drivers/i2c/muxes/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_I2C_ARB_GPIO_CHALLENGE) += i2c-arb-gpio-challenge.o obj-$(CONFIG_I2C_DEMUX_PINCTRL) += i2c-demux-pinctrl.o obj-$(CONFIG_I2C_MUX_GPIO) += i2c-mux-gpio.o +obj-$(CONFIG_I2C_MUX_MLXCPLD) += i2c-mux-mlxcpld.o obj-$(CONFIG_I2C_MUX_PCA9541) += i2c-mux-pca9541.o obj-$(CONFIG_I2C_MUX_PCA954x) += i2c-mux-pca954x.o obj-$(CONFIG_I2C_MUX_PINCTRL) += i2c-mux-pinctrl.o diff --git a/drivers/i2c/muxes/i2c-mux-mlxcpld.c b/drivers/i2c/muxes/i2c-mux-mlxcpld.c new file mode 100644 index 000000000000..3ab654bbfab5 --- /dev/null +++ b/drivers/i2c/muxes/i2c-mux-mlxcpld.c @@ -0,0 +1,220 @@ +/* + * drivers/i2c/muxes/i2c-mux-mlxcpld.c + * Copyright (c) 2016 Mellanox Technologies. All rights reserved. + * Copyright (c) 2016 Michael Shych + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CPLD_MUX_MAX_NCHANS 8 + +/* mlxcpld_mux - mux control structure: + * @last_chan - last register value + * @client - I2C device client + */ +struct mlxcpld_mux { + u8 last_chan; + struct i2c_client *client; +}; + +/* MUX logic description. + * Driver can support different mux control logic, according to CPLD + * implementation. + * + * Connectivity schema. + * + * i2c-mlxcpld Digital Analog + * driver + * *--------* * -> mux1 (virt bus2) -> mux -> | + * | I2CLPC | i2c physical * -> mux2 (virt bus3) -> mux -> | + * | bridge | bus 1 *---------* | + * | logic |---------------------> * mux reg * | + * | in CPLD| *---------* | + * *--------* i2c-mux-mlxpcld ^ * -> muxn (virt busn) -> mux -> | + * | driver | | + * | *---------------* | Devices + * | * CPLD (i2c bus)* select | + * | * registers for *--------* + * | * mux selection * deselect + * | *---------------* + * | | + * <--------> <-----------> + * i2c cntrl Board cntrl reg + * reg space space (mux select, + * IO, LED, WD, info) + * + */ + +static const struct i2c_device_id mlxcpld_mux_id[] = { + { "mlxcpld_mux_module", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, mlxcpld_mux_id); + +/* Write to mux register. Don't use i2c_transfer() and i2c_smbus_xfer() + * for this as they will try to lock adapter a second time. + */ +static int mlxcpld_mux_reg_write(struct i2c_adapter *adap, + struct i2c_client *client, u8 val) +{ + struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(&client->dev); + + if (adap->algo->master_xfer) { + struct i2c_msg msg; + u8 msgbuf[] = {pdata->sel_reg_addr, val}; + + msg.addr = client->addr; + msg.flags = 0; + msg.len = 2; + msg.buf = msgbuf; + return __i2c_transfer(adap, &msg, 1); + } else if (adap->algo->smbus_xfer) { + union i2c_smbus_data data; + + data.byte = val; + return adap->algo->smbus_xfer(adap, client->addr, + client->flags, I2C_SMBUS_WRITE, + pdata->sel_reg_addr, + I2C_SMBUS_BYTE_DATA, &data); + } else + return -ENODEV; +} + +static int mlxcpld_mux_select_chan(struct i2c_mux_core *muxc, u32 chan) +{ + struct mlxcpld_mux *data = i2c_mux_priv(muxc); + struct i2c_client *client = data->client; + u8 regval = chan + 1; + int err = 0; + + /* Only select the channel if its different from the last channel */ + if (data->last_chan != regval) { + err = mlxcpld_mux_reg_write(muxc->parent, client, regval); + if (err) + data->last_chan = 0; + else + data->last_chan = regval; + } + + return err; +} + +static int mlxcpld_mux_deselect(struct i2c_mux_core *muxc, u32 chan) +{ + struct mlxcpld_mux *data = i2c_mux_priv(muxc); + struct i2c_client *client = data->client; + + /* Deselect active channel */ + data->last_chan = 0; + + return mlxcpld_mux_reg_write(muxc->parent, client, data->last_chan); +} + +/* Probe/reomove functions */ +static int mlxcpld_mux_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adap = to_i2c_adapter(client->dev.parent); + struct mlxcpld_mux_plat_data *pdata = dev_get_platdata(&client->dev); + struct i2c_mux_core *muxc; + int num, force; + struct mlxcpld_mux *data; + int err; + + if (!pdata) + return -EINVAL; + + if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_WRITE_BYTE_DATA)) + return -ENODEV; + + muxc = i2c_mux_alloc(adap, &client->dev, CPLD_MUX_MAX_NCHANS, + sizeof(*data), 0, mlxcpld_mux_select_chan, + mlxcpld_mux_deselect); + if (!muxc) + return -ENOMEM; + + data = i2c_mux_priv(muxc); + i2c_set_clientdata(client, muxc); + data->client = client; + data->last_chan = 0; /* force the first selection */ + + /* Create an adapter for each channel. */ + for (num = 0; num < CPLD_MUX_MAX_NCHANS; num++) { + if (num >= pdata->num_adaps) + /* discard unconfigured channels */ + break; + + force = pdata->adap_ids[num]; + + err = i2c_mux_add_adapter(muxc, force, num, 0); + if (err) + goto virt_reg_failed; + } + + return 0; + +virt_reg_failed: + i2c_mux_del_adapters(muxc); + return err; +} + +static int mlxcpld_mux_remove(struct i2c_client *client) +{ + struct i2c_mux_core *muxc = i2c_get_clientdata(client); + + i2c_mux_del_adapters(muxc); + return 0; +} + +static struct i2c_driver mlxcpld_mux_driver = { + .driver = { + .name = "mlxcpld-mux", + }, + .probe = mlxcpld_mux_probe, + .remove = mlxcpld_mux_remove, + .id_table = mlxcpld_mux_id, +}; + +module_i2c_driver(mlxcpld_mux_driver); + +MODULE_AUTHOR("Michael Shych (michaels@mellanox.com)"); +MODULE_DESCRIPTION("Mellanox I2C-CPLD-MUX driver"); +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_ALIAS("platform:i2c-mux-mlxcpld"); diff --git a/include/linux/i2c/mlxcpld.h b/include/linux/i2c/mlxcpld.h new file mode 100644 index 000000000000..b08dcb183fca --- /dev/null +++ b/include/linux/i2c/mlxcpld.h @@ -0,0 +1,52 @@ +/* + * mlxcpld.h - Mellanox I2C multiplexer support in CPLD + * + * Copyright (c) 2016 Mellanox Technologies. All rights reserved. + * Copyright (c) 2016 Michael Shych + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _LINUX_I2C_MLXCPLD_H +#define _LINUX_I2C_MLXCPLD_H + +/* Platform data for the CPLD I2C multiplexers */ + +/* mlxcpld_mux_plat_data - per mux data, used with i2c_register_board_info + * @adap_ids - adapter array + * @num_adaps - number of adapters + * @sel_reg_addr - mux select register offset in CPLD space + */ +struct mlxcpld_mux_plat_data { + int *adap_ids; + int num_adaps; + int sel_reg_addr; +}; + +#endif /* _LINUX_I2C_MLXCPLD_H */ -- cgit v1.2.3 From 4d5538f5882a6b67eefbab0f0a3a67ce811621aa Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 13 Oct 2016 14:10:40 +0200 Subject: i2c: use an IRQ to report Host Notify events, not alert The current SMBus Host Notify implementation relies on .alert() to relay its notifications. However, the use cases where SMBus Host Notify is needed currently is to signal data ready on touchpads. This is closer to an IRQ than a custom API through .alert(). Given that the 2 touchpad manufacturers (Synaptics and Elan) that use SMBus Host Notify don't put any data in the SMBus payload, the concept actually matches one to one. Benefits are multiple: - simpler code and API: the client will just have an IRQ, and nothing needs to be added in the adapter beside internally enabling it. - no more specific workqueue, the threading is handled by IRQ core directly (when required) - no more races when removing the device (the drivers are already required to disable irq on remove) - simpler handling for drivers: use plain regular IRQs - no more dependency on i2c-smbus for i2c-i801 (and any other adapter) - the IRQ domain is created automatically when the adapter exports the Host Notify capability - the IRQ are assign only if ACPI, OF and the caller did not assign one already - the domain is automatically destroyed on remove - fewer lines of code (minus 20, yeah!) Signed-off-by: Benjamin Tissoires Signed-off-by: Wolfram Sang --- Documentation/i2c/smbus-protocol | 12 +++-- drivers/i2c/Kconfig | 1 + drivers/i2c/busses/i2c-i801.c | 32 +++-------- drivers/i2c/i2c-core.c | 113 +++++++++++++++++++++++++++++++++++++++ drivers/i2c/i2c-smbus.c | 102 ----------------------------------- include/linux/i2c-smbus.h | 27 ---------- include/linux/i2c.h | 4 ++ 7 files changed, 133 insertions(+), 158 deletions(-) (limited to 'include') diff --git a/Documentation/i2c/smbus-protocol b/Documentation/i2c/smbus-protocol index 14d4ec1be245..092d474f5843 100644 --- a/Documentation/i2c/smbus-protocol +++ b/Documentation/i2c/smbus-protocol @@ -200,10 +200,14 @@ alerting device's address. [S] [HostAddr] [Wr] A [DevAddr] A [DataLow] A [DataHigh] A [P] This is implemented in the following way in the Linux kernel: -* I2C bus drivers which support SMBus Host Notify should call - i2c_setup_smbus_host_notify() to setup SMBus Host Notify support. -* I2C drivers for devices which can trigger SMBus Host Notify should implement - the optional alert() callback. +* I2C bus drivers which support SMBus Host Notify should report + I2C_FUNC_SMBUS_HOST_NOTIFY. +* I2C bus drivers trigger SMBus Host Notify by a call to + i2c_handle_smbus_host_notify(). +* I2C drivers for devices which can trigger SMBus Host Notify will have + client->irq assigned to a Host Notify IRQ if noone else specified an other. + +There is currently no way to retrieve the data parameter from the client. Packet Error Checking (PEC) diff --git a/drivers/i2c/Kconfig b/drivers/i2c/Kconfig index d223650a97e4..de305f89a659 100644 --- a/drivers/i2c/Kconfig +++ b/drivers/i2c/Kconfig @@ -7,6 +7,7 @@ menu "I2C support" config I2C tristate "I2C support" select RT_MUTEXES + select IRQ_DOMAIN ---help--- I2C (pronounce: I-squared-C) is a slow serial bus protocol used in many micro controller applications and developed by Philips. SMBus, diff --git a/drivers/i2c/busses/i2c-i801.c b/drivers/i2c/busses/i2c-i801.c index c0c0cac9950c..e242db43774b 100644 --- a/drivers/i2c/busses/i2c-i801.c +++ b/drivers/i2c/busses/i2c-i801.c @@ -269,7 +269,6 @@ struct i801_priv { */ bool acpi_reserved; struct mutex acpi_lock; - struct smbus_host_notify *host_notify; }; #define FEATURE_SMBUS_PEC BIT(0) @@ -585,10 +584,10 @@ static irqreturn_t i801_host_notify_isr(struct i801_priv *priv) /* * With the tested platforms, reading SMBNTFDDAT (22 + (p)->smba) - * always returns 0 and is safe to read. - * We just use 0 given we have no use of the data right now. + * always returns 0. Our current implementation doesn't provide + * data, so we just ignore it. */ - i2c_handle_smbus_host_notify(priv->host_notify, addr, 0); + i2c_handle_smbus_host_notify(&priv->adapter, addr); /* clear Host Notify bit and return */ outb_p(SMBSLVSTS_HST_NTFY_STS, SMBSLVSTS(priv)); @@ -951,17 +950,12 @@ static u32 i801_func(struct i2c_adapter *adapter) I2C_FUNC_SMBUS_HOST_NOTIFY : 0); } -static int i801_enable_host_notify(struct i2c_adapter *adapter) +static void i801_enable_host_notify(struct i2c_adapter *adapter) { struct i801_priv *priv = i2c_get_adapdata(adapter); if (!(priv->features & FEATURE_HOST_NOTIFY)) - return -ENOTSUPP; - - if (!priv->host_notify) - priv->host_notify = i2c_setup_smbus_host_notify(adapter); - if (!priv->host_notify) - return -ENOMEM; + return; priv->original_slvcmd = inb_p(SMBSLVCMD(priv)); @@ -971,8 +965,6 @@ static int i801_enable_host_notify(struct i2c_adapter *adapter) /* clear Host Notify bit to allow a new notification */ outb_p(SMBSLVSTS_HST_NTFY_STS, SMBSLVSTS(priv)); - - return 0; } static void i801_disable_host_notify(struct i801_priv *priv) @@ -1647,14 +1639,7 @@ static int i801_probe(struct pci_dev *dev, const struct pci_device_id *id) return err; } - /* - * Enable Host Notify for chips that supports it. - * It is done after i2c_add_adapter() so that we are sure the work queue - * is not used if i2c_add_adapter() fails. - */ - err = i801_enable_host_notify(&priv->adapter); - if (err && err != -ENOTSUPP) - dev_warn(&dev->dev, "Unable to enable SMBus Host Notify\n"); + i801_enable_host_notify(&priv->adapter); i801_probe_optional_slaves(priv); /* We ignore errors - multiplexing is optional */ @@ -1705,11 +1690,8 @@ static int i801_resume(struct device *dev) { struct pci_dev *pci_dev = to_pci_dev(dev); struct i801_priv *priv = pci_get_drvdata(pci_dev); - int err; - err = i801_enable_host_notify(&priv->adapter); - if (err && err != -ENOTSUPP) - dev_warn(dev, "Unable to enable SMBus Host Notify\n"); + i801_enable_host_notify(&priv->adapter); return 0; } diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c index 8b93a262e237..3a1bc9c4efc7 100644 --- a/drivers/i2c/i2c-core.c +++ b/drivers/i2c/i2c-core.c @@ -65,6 +65,9 @@ #define I2C_ADDR_OFFSET_TEN_BIT 0xa000 #define I2C_ADDR_OFFSET_SLAVE 0x1000 +#define I2C_ADDR_7BITS_MAX 0x77 +#define I2C_ADDR_7BITS_COUNT (I2C_ADDR_7BITS_MAX + 1) + /* core_lock protects i2c_adapter_idr, and guarantees that device detection, deletion of detected devices, and attach_adapter calls are serialized */ @@ -896,6 +899,25 @@ static void i2c_init_recovery(struct i2c_adapter *adap) adap->bus_recovery_info = NULL; } +static int i2c_smbus_host_notify_to_irq(const struct i2c_client *client) +{ + struct i2c_adapter *adap = client->adapter; + unsigned int irq; + + if (!adap->host_notify_domain) + return -ENXIO; + + if (client->flags & I2C_CLIENT_TEN) + return -EINVAL; + + irq = irq_find_mapping(adap->host_notify_domain, client->addr); + if (!irq) + irq = irq_create_mapping(adap->host_notify_domain, + client->addr); + + return irq > 0 ? irq : -ENXIO; +} + static int i2c_device_probe(struct device *dev) { struct i2c_client *client = i2c_verify_client(dev); @@ -917,6 +939,14 @@ static int i2c_device_probe(struct device *dev) } if (irq == -EPROBE_DEFER) return irq; + /* + * ACPI and OF did not find any useful IRQ, try to see + * if Host Notify can be used. + */ + if (irq < 0) { + dev_dbg(dev, "Using Host Notify IRQ\n"); + irq = i2c_smbus_host_notify_to_irq(client); + } if (irq < 0) irq = 0; @@ -1866,6 +1896,79 @@ static const struct i2c_lock_operations i2c_adapter_lock_ops = { .unlock_bus = i2c_adapter_unlock_bus, }; +static void i2c_host_notify_irq_teardown(struct i2c_adapter *adap) +{ + struct irq_domain *domain = adap->host_notify_domain; + irq_hw_number_t hwirq; + + if (!domain) + return; + + for (hwirq = 0 ; hwirq < I2C_ADDR_7BITS_COUNT ; hwirq++) + irq_dispose_mapping(irq_find_mapping(domain, hwirq)); + + irq_domain_remove(domain); + adap->host_notify_domain = NULL; +} + +static int i2c_host_notify_irq_map(struct irq_domain *h, + unsigned int virq, + irq_hw_number_t hw_irq_num) +{ + irq_set_chip_and_handler(virq, &dummy_irq_chip, handle_simple_irq); + + return 0; +} + +static const struct irq_domain_ops i2c_host_notify_irq_ops = { + .map = i2c_host_notify_irq_map, +}; + +static int i2c_setup_host_notify_irq_domain(struct i2c_adapter *adap) +{ + struct irq_domain *domain; + + if (!i2c_check_functionality(adap, I2C_FUNC_SMBUS_HOST_NOTIFY)) + return 0; + + domain = irq_domain_create_linear(adap->dev.fwnode, + I2C_ADDR_7BITS_COUNT, + &i2c_host_notify_irq_ops, adap); + if (!domain) + return -ENOMEM; + + adap->host_notify_domain = domain; + + return 0; +} + +/** + * i2c_handle_smbus_host_notify - Forward a Host Notify event to the correct + * I2C client. + * @adap: the adapter + * @addr: the I2C address of the notifying device + * Context: can't sleep + * + * Helper function to be called from an I2C bus driver's interrupt + * handler. It will schedule the Host Notify IRQ. + */ +int i2c_handle_smbus_host_notify(struct i2c_adapter *adap, unsigned short addr) +{ + int irq; + + if (!adap) + return -EINVAL; + + irq = irq_find_mapping(adap->host_notify_domain, addr); + if (irq <= 0) + return -ENXIO; + + generic_handle_irq(irq); + + return 0; +} +EXPORT_SYMBOL_GPL(i2c_handle_smbus_host_notify); + static int i2c_register_adapter(struct i2c_adapter *adap) { int res = -EINVAL; @@ -1897,6 +2000,14 @@ static int i2c_register_adapter(struct i2c_adapter *adap) if (adap->timeout == 0) adap->timeout = HZ; + /* register soft irqs for Host Notify */ + res = i2c_setup_host_notify_irq_domain(adap); + if (res) { + pr_err("adapter '%s': can't create Host Notify IRQs (%d)\n", + adap->name, res); + goto out_list; + } + dev_set_name(&adap->dev, "i2c-%d", adap->nr); adap->dev.bus = &i2c_bus_type; adap->dev.type = &i2c_adapter_type; @@ -2134,6 +2245,8 @@ void i2c_del_adapter(struct i2c_adapter *adap) pm_runtime_disable(&adap->dev); + i2c_host_notify_irq_teardown(adap); + /* wait until all references to the device are gone * * FIXME: This is old code and should ideally be replaced by an diff --git a/drivers/i2c/i2c-smbus.c b/drivers/i2c/i2c-smbus.c index b0d2679c60d1..f9271c713d20 100644 --- a/drivers/i2c/i2c-smbus.c +++ b/drivers/i2c/i2c-smbus.c @@ -241,108 +241,6 @@ int i2c_handle_smbus_alert(struct i2c_client *ara) } EXPORT_SYMBOL_GPL(i2c_handle_smbus_alert); -static void smbus_host_notify_work(struct work_struct *work) -{ - struct alert_data alert; - struct i2c_adapter *adapter; - unsigned long flags; - u16 payload; - u8 addr; - struct smbus_host_notify *data; - - data = container_of(work, struct smbus_host_notify, work); - - spin_lock_irqsave(&data->lock, flags); - payload = data->payload; - addr = data->addr; - adapter = data->adapter; - - /* clear the pending bit and release the spinlock */ - data->pending = false; - spin_unlock_irqrestore(&data->lock, flags); - - if (!adapter || !addr) - return; - - alert.type = I2C_PROTOCOL_SMBUS_HOST_NOTIFY; - alert.addr = addr; - alert.data = payload; - - device_for_each_child(&adapter->dev, &alert, smbus_do_alert); -} - -/** - * i2c_setup_smbus_host_notify - Allocate a new smbus_host_notify for the given - * I2C adapter. - * @adapter: the adapter we want to associate a Host Notify function - * - * Returns a struct smbus_host_notify pointer on success, and NULL on failure. - * The resulting smbus_host_notify must not be freed afterwards, it is a - * managed resource already. - */ -struct smbus_host_notify *i2c_setup_smbus_host_notify(struct i2c_adapter *adap) -{ - struct smbus_host_notify *host_notify; - - host_notify = devm_kzalloc(&adap->dev, sizeof(struct smbus_host_notify), - GFP_KERNEL); - if (!host_notify) - return NULL; - - host_notify->adapter = adap; - - spin_lock_init(&host_notify->lock); - INIT_WORK(&host_notify->work, smbus_host_notify_work); - - return host_notify; -} -EXPORT_SYMBOL_GPL(i2c_setup_smbus_host_notify); - -/** - * i2c_handle_smbus_host_notify - Forward a Host Notify event to the correct - * I2C client. - * @host_notify: the struct host_notify attached to the relevant adapter - * @addr: the I2C address of the notifying device - * @data: the payload of the notification - * Context: can't sleep - * - * Helper function to be called from an I2C bus driver's interrupt - * handler. It will schedule the Host Notify work, in turn calling the - * corresponding I2C device driver's alert function. - * - * host_notify should be a valid pointer previously returned by - * i2c_setup_smbus_host_notify(). - */ -int i2c_handle_smbus_host_notify(struct smbus_host_notify *host_notify, - unsigned short addr, unsigned int data) -{ - unsigned long flags; - struct i2c_adapter *adapter; - - if (!host_notify || !host_notify->adapter) - return -EINVAL; - - adapter = host_notify->adapter; - - spin_lock_irqsave(&host_notify->lock, flags); - - if (host_notify->pending) { - spin_unlock_irqrestore(&host_notify->lock, flags); - dev_warn(&adapter->dev, "Host Notify already scheduled.\n"); - return -EBUSY; - } - - host_notify->payload = data; - host_notify->addr = addr; - - /* Mark that there is a pending notification and release the lock */ - host_notify->pending = true; - spin_unlock_irqrestore(&host_notify->lock, flags); - - return schedule_work(&host_notify->work); -} -EXPORT_SYMBOL_GPL(i2c_handle_smbus_host_notify); - module_i2c_driver(smbalert_driver); MODULE_AUTHOR("Jean Delvare "); diff --git a/include/linux/i2c-smbus.h b/include/linux/i2c-smbus.h index c2e3324f9468..a1385023a29b 100644 --- a/include/linux/i2c-smbus.h +++ b/include/linux/i2c-smbus.h @@ -50,31 +50,4 @@ struct i2c_client *i2c_setup_smbus_alert(struct i2c_adapter *adapter, struct i2c_smbus_alert_setup *setup); int i2c_handle_smbus_alert(struct i2c_client *ara); -/** - * smbus_host_notify - internal structure used by the Host Notify mechanism. - * @adapter: the I2C adapter associated with this struct - * @work: worker used to schedule the IRQ in the slave device - * @lock: spinlock to check if a notification is already pending - * @pending: flag set when a notification is pending (any new notification will - * be rejected if pending is true) - * @payload: the actual payload of the Host Notify event - * @addr: the address of the slave device which raised the notification - * - * This struct needs to be allocated by i2c_setup_smbus_host_notify() and does - * not need to be freed. Internally, i2c_setup_smbus_host_notify() uses a - * managed resource to clean this up when the adapter get released. - */ -struct smbus_host_notify { - struct i2c_adapter *adapter; - struct work_struct work; - spinlock_t lock; - bool pending; - u16 payload; - u8 addr; -}; - -struct smbus_host_notify *i2c_setup_smbus_host_notify(struct i2c_adapter *adap); -int i2c_handle_smbus_host_notify(struct smbus_host_notify *host_notify, - unsigned short addr, unsigned int data); - #endif /* _LINUX_I2C_SMBUS_H */ diff --git a/include/linux/i2c.h b/include/linux/i2c.h index 82cf90945bb8..b2109c522dec 100644 --- a/include/linux/i2c.h +++ b/include/linux/i2c.h @@ -30,6 +30,7 @@ #include /* for struct device */ #include /* for completion */ #include +#include /* for Host Notify IRQ */ #include /* for struct device_node */ #include /* for swab16 */ #include @@ -575,6 +576,8 @@ struct i2c_adapter { struct i2c_bus_recovery_info *bus_recovery_info; const struct i2c_adapter_quirks *quirks; + + struct irq_domain *host_notify_domain; }; #define to_i2c_adapter(d) container_of(d, struct i2c_adapter, dev) @@ -747,6 +750,7 @@ static inline u8 i2c_8bit_addr_from_msg(const struct i2c_msg *msg) return (msg->addr << 1) | (msg->flags & I2C_M_RD ? 1 : 0); } +int i2c_handle_smbus_host_notify(struct i2c_adapter *adap, unsigned short addr); /** * module_i2c_driver() - Helper macro for registering a modular I2C driver * @__i2c_driver: i2c_driver struct -- cgit v1.2.3